v-r to drano merge conflicts wip
commit
74d4f8af01
22
.hgtags
22
.hgtags
|
|
@ -110,6 +110,7 @@ bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 DRTVWR-52_2.6.6-beta1
|
|||
bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 2.6.6-beta1
|
||||
5e349dbe9cc84ea5795af8aeb6d473a0af9d4953 2.6.8-start
|
||||
beafa8a9bd1d1b670b7523d865204dc4a4b38eef DRTVWR-55_2.6.8-beta1
|
||||
bb9932a7a5fd00edf52d95f354e3b37ae6a942db DRTVWR-156
|
||||
beafa8a9bd1d1b670b7523d865204dc4a4b38eef 2.6.8-beta1
|
||||
11d5d8080e67c3955914caf98f2eb116af30e55a 2.6.9-start
|
||||
e67da2c6e3125966dd49eef98b36317afac1fcfe 2.6.9-start
|
||||
|
|
@ -149,6 +150,7 @@ a9abb9633a266c8d2fe62411cfd1c86d32da72bf 2.7.1-release
|
|||
09984bfa6cae17e0f72d02b75c1b7393c65eecfc 2.7.5-beta1
|
||||
e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-start
|
||||
502f6a5deca9365ddae57db4f1e30172668e171e 2.8.1-start
|
||||
2a3965b3ad202df7ea25d2be689291bb14a1280e DRTVWR-155
|
||||
6866d9df6efbd441c66451debd376d21211de39c DRTVWR-68_2.7.5-release
|
||||
6866d9df6efbd441c66451debd376d21211de39c 2.7.5-release
|
||||
e1ed60913230dd64269a7f7fc52cbc6004f6d52c DRTVWR-71_2.8.0-beta1
|
||||
|
|
@ -260,12 +262,12 @@ c6175c955a19e9b9353d242889ec1779b5762522 3.2.5-release
|
|||
3d75c836d178c7c7e788f256afe195f6cab764a2 3.2.7-beta1
|
||||
89980333c99dbaf1787fe20784f1d8849e9b5d4f 3.2.8-start
|
||||
16f8e2915f3f2e4d732fb3125daf229cb0fd1875 DRTVWR-114_3.2.8-beta1
|
||||
37dd400ad721e2a89ee820ffc1e7e433c68f3ca2 3.2.9-start
|
||||
16f8e2915f3f2e4d732fb3125daf229cb0fd1875 3.2.8-beta1
|
||||
987425b1acf4752379b2e1eb20944b4b35d67a85 DRTVWR-115_3.2.8-beta2
|
||||
987425b1acf4752379b2e1eb20944b4b35d67a85 3.2.8-beta2
|
||||
51b2fd52e36aab8f670e0874e7e1472434ec4b4a DRTVWR-113_3.2.8-release
|
||||
51b2fd52e36aab8f670e0874e7e1472434ec4b4a 3.2.8-release
|
||||
37dd400ad721e2a89ee820ffc1e7e433c68f3ca2 3.2.9-start
|
||||
e9c82fca5ae6fb8a8af29012d78fb194a29323f3 DRTVWR-117_3.2.9-beta1
|
||||
e9c82fca5ae6fb8a8af29012d78fb194a29323f3 3.2.9-beta1
|
||||
a01ef9bed28627f4ca543fbc1d70c79cc297a90f DRTVWR-118_3.2.9-beta2
|
||||
|
|
@ -297,3 +299,21 @@ d29a260119f8d5a5d168e25fed0c7ea6b3f40161 3.3.2-beta1
|
|||
c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d 3.3.2-release
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate
|
||||
600f3b3920d94de805ac6dc8bb6def9c069dd360 DRTVWR-162
|
||||
24a7281bef42bd4430ceb25db8b195449c2c7de3 DRTVWR-153
|
||||
15e90b52dc0297921b022b90d10d797436b8a1bd viewer-release-candidate
|
||||
6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1
|
||||
5910f8063a7e1ddddf504c2f35ca831cc5e8f469 DRTVWR-160
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1
|
||||
f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144
|
||||
f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144
|
||||
2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164
|
||||
80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165
|
||||
fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2
|
||||
af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169
|
||||
4b3c68199a86cabaa5d9466d7b0f7e141e901d7a 3.3.3-beta3
|
||||
6428242e124b523813bfaf4c45b3d422f0298c81 3.3.3-release
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>9868bfa0b6954e4884c49c6f30068c80</string>
|
||||
<string>2dfcd809e747f714b3fe0bf82a175812</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-darwin-20110217.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin</string>
|
||||
|
|
@ -102,9 +102,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>ff62946c518a247c86e1066c1e9a5855</string>
|
||||
<string>f38c966a430012dc157fdc104f23a59b</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-linux-20110309.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Linux/installer/apr_suite-1.4.5-linux-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux</string>
|
||||
|
|
@ -114,9 +114,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>73785c200a5b4ef74a1230b028bb680d</string>
|
||||
<string>4a9d040582342699c58c886c5ccd2caf</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-windows-20110217.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
|
|||
|
|
@ -1160,9 +1160,9 @@ Tofu Buzzard
|
|||
CTS-411
|
||||
STORM-546
|
||||
VWR-24509
|
||||
STORM-1684
|
||||
SH-2477
|
||||
STORM-1684
|
||||
STORM-1819
|
||||
Tony Kembia
|
||||
Torben Trautman
|
||||
TouchaHoney Perhaps
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ if (VIEWER)
|
|||
add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llplugin)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llui)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llxuixml)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}viewer_components)
|
||||
|
||||
# Legacy C++ tests. Build always, run if LL_TESTS is true.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ MACRO(LL_TEST_COMMAND OUTVAR LD_LIBRARY_PATH)
|
|||
FOREACH(dir ${LD_LIBRARY_PATH})
|
||||
LIST(APPEND value "-l${dir}")
|
||||
ENDFOREACH(dir)
|
||||
# Enough different tests want to be able to find CMake's PYTHON_EXECUTABLE
|
||||
# that we should just pop it into the environment for everybody.
|
||||
LIST(APPEND value "-DPYTHON=${PYTHON_EXECUTABLE}")
|
||||
LIST(APPEND value ${ARGN})
|
||||
SET(${OUTVAR} ${value})
|
||||
##IF(LL_TEST_VERBOSE)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ $/LicenseInfo$
|
|||
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
def main(command, libpath=[], vars={}):
|
||||
|
|
@ -113,6 +114,33 @@ def main(command, libpath=[], vars={}):
|
|||
sys.stdout.flush()
|
||||
return subprocess.call(command)
|
||||
|
||||
# swiped from vita, sigh, seems like a Bad Idea to introduce dependency
|
||||
def translate_rc(rc):
|
||||
"""
|
||||
Accept an rc encoded as for subprocess.Popen.returncode:
|
||||
None means still running
|
||||
int >= 0 means terminated voluntarily with specified rc
|
||||
int < 0 means terminated by signal (-rc)
|
||||
|
||||
Return a string explaining the outcome. In case of a signal, try to
|
||||
name the corresponding symbol from the 'signal' module.
|
||||
"""
|
||||
if rc is None:
|
||||
return "still running"
|
||||
|
||||
if rc >= 0:
|
||||
return "terminated with rc %s" % rc
|
||||
|
||||
# Negative rc means the child was terminated by signal -rc.
|
||||
rc = -rc
|
||||
for attr in dir(signal):
|
||||
if attr.startswith('SIG') and getattr(signal, attr) == rc:
|
||||
strc = attr
|
||||
break
|
||||
else:
|
||||
strc = str(rc)
|
||||
return "terminated by signal %s" % strc
|
||||
|
||||
if __name__ == "__main__":
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: %prog [options] command args...")
|
||||
|
|
@ -140,5 +168,5 @@ if __name__ == "__main__":
|
|||
vars=dict([(pair.split('=', 1) + [""])[:2] for pair in opts.vars]))
|
||||
if rc not in (None, 0):
|
||||
print >>sys.stderr, "Failure running: %s" % " ".join(args)
|
||||
print >>sys.stderr, "Error: %s" % rc
|
||||
print >>sys.stderr, "Error %s: %s" % (rc, translate_rc(rc))
|
||||
sys.exit((rc < 0) and 255 or rc)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ include(LLWindow)
|
|||
include(LLUI)
|
||||
include(LLVFS) # ugh, needed for LLDir
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(Linking)
|
||||
# include(Tut)
|
||||
|
||||
|
|
@ -32,7 +31,6 @@ include_directories(
|
|||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLWINDOW_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(llui_libtest_SOURCE_FILES
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ import tarfile
|
|||
import errno
|
||||
import subprocess
|
||||
|
||||
class ManifestError(RuntimeError):
|
||||
"""Use an exception more specific than generic Python RuntimeError"""
|
||||
pass
|
||||
|
||||
class MissingError(ManifestError):
|
||||
"""You specified a file that doesn't exist"""
|
||||
pass
|
||||
|
||||
def path_ancestors(path):
|
||||
drive, path = os.path.splitdrive(os.path.normpath(path))
|
||||
result = []
|
||||
|
|
@ -180,6 +188,9 @@ def usage(srctree=""):
|
|||
arg['description'] % nd)
|
||||
|
||||
def main():
|
||||
## import itertools
|
||||
## print ' '.join((("'%s'" % item) if ' ' in item else item)
|
||||
## for item in itertools.chain([sys.executable], sys.argv))
|
||||
option_names = [arg['name'] + '=' for arg in ARGUMENTS]
|
||||
option_names.append('help')
|
||||
options, remainder = getopt.getopt(sys.argv[1:], "", option_names)
|
||||
|
|
@ -385,7 +396,7 @@ class LLManifest(object):
|
|||
child.stdout.close()
|
||||
status = child.wait()
|
||||
if status:
|
||||
raise RuntimeError(
|
||||
raise ManifestError(
|
||||
"Command %s returned non-zero status (%s) \noutput:\n%s"
|
||||
% (command, status, output) )
|
||||
return output
|
||||
|
|
@ -395,7 +406,7 @@ class LLManifest(object):
|
|||
a) verify that you really have created it
|
||||
b) schedule it for cleanup"""
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError, "Should be something at path " + path
|
||||
raise ManifestError, "Should be something at path " + path
|
||||
self.created_paths.append(path)
|
||||
|
||||
def put_in_file(self, contents, dst):
|
||||
|
|
@ -550,7 +561,7 @@ class LLManifest(object):
|
|||
except (IOError, os.error), why:
|
||||
errors.append((srcname, dstname, why))
|
||||
if errors:
|
||||
raise RuntimeError, errors
|
||||
raise ManifestError, errors
|
||||
|
||||
|
||||
def cmakedirs(self, path):
|
||||
|
|
@ -598,11 +609,10 @@ class LLManifest(object):
|
|||
|
||||
def check_file_exists(self, path):
|
||||
if not os.path.exists(path) and not os.path.islink(path):
|
||||
raise RuntimeError("Path %s doesn't exist" % (
|
||||
os.path.normpath(os.path.join(os.getcwd(), path)),))
|
||||
raise MissingError("Path %s doesn't exist" % (os.path.abspath(path),))
|
||||
|
||||
|
||||
wildcard_pattern = re.compile('\*')
|
||||
wildcard_pattern = re.compile(r'\*')
|
||||
def expand_globs(self, src, dst):
|
||||
src_list = glob.glob(src)
|
||||
src_re, d_template = self.wildcard_regex(src.replace('\\', '/'),
|
||||
|
|
@ -615,7 +625,7 @@ class LLManifest(object):
|
|||
sys.stdout.write("Processing %s => %s ... " % (src, dst))
|
||||
sys.stdout.flush()
|
||||
if src == None:
|
||||
raise RuntimeError("No source file, dst is " + dst)
|
||||
raise ManifestError("No source file, dst is " + dst)
|
||||
if dst == None:
|
||||
dst = src
|
||||
dst = os.path.join(self.get_dst_prefix(), dst)
|
||||
|
|
@ -637,13 +647,23 @@ class LLManifest(object):
|
|||
else:
|
||||
count += self.process_file(src, dst)
|
||||
return count
|
||||
try:
|
||||
count = try_path(os.path.join(self.get_src_prefix(), src))
|
||||
except RuntimeError:
|
||||
|
||||
for pfx in self.get_src_prefix(), self.get_artwork_prefix(), self.get_build_prefix():
|
||||
try:
|
||||
count = try_path(os.path.join(self.get_artwork_prefix(), src))
|
||||
except RuntimeError:
|
||||
count = try_path(os.path.join(self.get_build_prefix(), src))
|
||||
count = try_path(os.path.join(pfx, src))
|
||||
except MissingError:
|
||||
# If src isn't a wildcard, and if that file doesn't exist in
|
||||
# this pfx, try next pfx.
|
||||
count = 0
|
||||
continue
|
||||
|
||||
# Here try_path() didn't raise MissingError. Did it process any files?
|
||||
if count:
|
||||
break
|
||||
# Even though try_path() didn't raise MissingError, it returned 0
|
||||
# files. src is probably a wildcard meant for some other pfx. Loop
|
||||
# back to try the next.
|
||||
|
||||
print "%d files" % count
|
||||
|
||||
def do(self, *actions):
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ include(UI)
|
|||
include(LLCommon)
|
||||
include(LLVFS)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(LLUI)
|
||||
include(Linking)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
${LLUI_INCLUDE_DIRS}
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${CARES_INCLUDE_DIRS}
|
||||
${OPENSSL_INCLUDE_DIRS}
|
||||
|
|
@ -42,7 +42,7 @@ target_link_libraries(linux-updater
|
|||
${CRYPTO_LIBRARIES}
|
||||
${UI_LIBRARIES}
|
||||
${LLXML_LIBRARIES}
|
||||
${LLXUIXML_LIBRARIES}
|
||||
${LLUI_LIBRARIES}
|
||||
${LLVFS_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file linux_updater.cpp
|
||||
* @author Kyle Ambroff <ambroff@lindenlab.com>, Tofu Linden
|
||||
* @brief Viewer update program for unix platforms that support GTK+
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
|
@ -34,10 +34,30 @@
|
|||
#include "llfile.h"
|
||||
#include "lldir.h"
|
||||
#include "lldiriterator.h"
|
||||
|
||||
/*==========================================================================*|
|
||||
// IQA-490: Use of LLTrans -- by this program at least -- appears to be buggy.
|
||||
// With it, the 3.3.2 beta 1 linux-updater.bin crashes; without it seems stable.
|
||||
#include "llxmlnode.h"
|
||||
#include "lltrans.h"
|
||||
|*==========================================================================*/
|
||||
|
||||
static class LLTrans
|
||||
{
|
||||
public:
|
||||
LLTrans();
|
||||
static std::string getString(const std::string& key);
|
||||
|
||||
private:
|
||||
std::string _getString(const std::string& key) const;
|
||||
|
||||
typedef std::map<std::string, std::string> MessageMap;
|
||||
MessageMap mMessages;
|
||||
} sLLTransInstance;
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <map>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <gtk/gtk.h>
|
||||
|
|
@ -85,6 +105,8 @@ void init_default_trans_args()
|
|||
bool translate_init(std::string comma_delim_path_list,
|
||||
std::string base_xml_name)
|
||||
{
|
||||
return true;
|
||||
/*==========================================================================*|
|
||||
init_default_trans_args();
|
||||
|
||||
// extract paths string vector from comma-delimited flat string
|
||||
|
|
@ -112,6 +134,7 @@ bool translate_init(std::string comma_delim_path_list,
|
|||
LLTrans::parseStrings(root, default_trans_args);
|
||||
return true;
|
||||
}
|
||||
|*==========================================================================*/
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -151,7 +174,7 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
GTK_WIN_POS_CENTER_ALWAYS);
|
||||
|
||||
gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12);
|
||||
g_signal_connect(G_OBJECT(app_state->window), "delete-event",
|
||||
g_signal_connect(G_OBJECT(app_state->window), "delete-event",
|
||||
G_CALLBACK(on_window_closed), app_state);
|
||||
|
||||
vbox = gtk_vbox_new(FALSE, 6);
|
||||
|
|
@ -165,7 +188,7 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
|
||||
summary_label = gtk_label_new(NULL);
|
||||
gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE);
|
||||
gtk_label_set_markup(GTK_LABEL(summary_label),
|
||||
gtk_label_set_markup(GTK_LABEL(summary_label),
|
||||
label_ostr.str().c_str());
|
||||
gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0);
|
||||
|
|
@ -195,9 +218,9 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
|
||||
// set up progress bar, and update it roughly every 1/10 of a second
|
||||
app_state->progress_bar = gtk_progress_bar_new();
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
|
||||
LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str());
|
||||
gtk_box_pack_start(GTK_BOX(vbox),
|
||||
gtk_box_pack_start(GTK_BOX(vbox),
|
||||
app_state->progress_bar, FALSE, TRUE, 0);
|
||||
app_state->progress_update_timeout_id = g_timeout_add
|
||||
(UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state);
|
||||
|
|
@ -299,7 +322,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
g_error_free(error);
|
||||
throw 0;
|
||||
}
|
||||
|
||||
|
||||
if(tmp_local_filename != NULL)
|
||||
{
|
||||
app_state->file = tmp_local_filename;
|
||||
|
|
@ -342,7 +365,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
|
||||
&download_progress_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state);
|
||||
|
||||
|
|
@ -352,8 +375,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
if (result)
|
||||
{
|
||||
llerrs << "Failed to download update: "
|
||||
<< app_state->url
|
||||
llerrs << "Failed to download update: "
|
||||
<< app_state->url
|
||||
<< llendl;
|
||||
|
||||
gdk_threads_enter();
|
||||
|
|
@ -365,7 +388,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
throw 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now pulse the progres bar back and forth while the package is
|
||||
// being unpacked
|
||||
gdk_threads_enter();
|
||||
|
|
@ -386,8 +409,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
gdk_threads_enter();
|
||||
display_error(app_state->window,
|
||||
LLTrans::getString("UpdaterFailInstallTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
LLTrans::getString("UpdaterFailInstallTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
//"Failed to update " + app_state->app_name,
|
||||
gdk_threads_leave();
|
||||
throw 0;
|
||||
|
|
@ -402,8 +425,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
gdk_threads_enter();
|
||||
display_error(app_state->window,
|
||||
LLTrans::getString("UpdaterFailStartTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
LLTrans::getString("UpdaterFailStartTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
gdk_threads_leave();
|
||||
throw 0;
|
||||
}
|
||||
|
|
@ -448,7 +471,7 @@ gboolean less_anal_gspawnsync(gchar **argv,
|
|||
|
||||
// restore SIGCHLD handler
|
||||
sigaction(SIGCHLD, &sigchld_backup, NULL);
|
||||
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
|
@ -477,7 +500,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
{
|
||||
char *src_string_copy = g_strdup(filename.c_str());
|
||||
char *dst_string_copy = g_strdup(newname.c_str());
|
||||
char* argv[] =
|
||||
char* argv[] =
|
||||
{
|
||||
sudo_cmd,
|
||||
mv_cmd,
|
||||
|
|
@ -492,8 +515,8 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
if (!less_anal_gspawnsync(argv, &stderr_output,
|
||||
&child_exit_status, &spawn_error))
|
||||
{
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< spawn_error->message
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< spawn_error->message
|
||||
<< llendl;
|
||||
}
|
||||
else if (child_exit_status)
|
||||
|
|
@ -506,7 +529,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
{
|
||||
// everything looks good, clear the error code
|
||||
rtncode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
g_free(src_string_copy);
|
||||
g_free(dst_string_copy);
|
||||
|
|
@ -531,7 +554,7 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
}
|
||||
llinfos << "Found tar command: " << tar_cmd << llendl;
|
||||
|
||||
// Unpack the tarball in a temporary place first, then move it to
|
||||
// Unpack the tarball in a temporary place first, then move it to
|
||||
// its final destination
|
||||
std::string tmp_dest_dir = gDirUtilp->getTempFilename();
|
||||
if (LLFile::mkdir(tmp_dest_dir, 0744))
|
||||
|
|
@ -571,8 +594,8 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
if (!less_anal_gspawnsync(argv, &stderr_output,
|
||||
&child_exit_status, &untar_error))
|
||||
{
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< untar_error->message
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< untar_error->message
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -605,8 +628,8 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
|
||||
if (rename_with_sudo_fallback(destination, backup_dir))
|
||||
{
|
||||
llwarns << "Failed to move directory: '"
|
||||
<< destination << "' -> '" << backup_dir
|
||||
llwarns << "Failed to move directory: '"
|
||||
<< destination << "' -> '" << backup_dir
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -617,7 +640,7 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
if (rename_with_sudo_fallback(tmp_dest_dir, destination))
|
||||
{
|
||||
llwarns << "Failed to move installation to the destination: "
|
||||
<< destination
|
||||
<< destination
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -713,7 +736,7 @@ BOOL spawn_viewer(UpdaterAppState *app_state)
|
|||
|
||||
if (!success)
|
||||
{
|
||||
llwarns << "Failed to launch viewer: " << error->message
|
||||
llwarns << "Failed to launch viewer: " << error->message
|
||||
<< llendl;
|
||||
}
|
||||
|
||||
|
|
@ -751,7 +774,7 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
|
|||
else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc))
|
||||
{
|
||||
app_state->image_dir = argv[i];
|
||||
app_state->image_dir_iter = new LLDirIterator(argv[i], "/*.jpg");
|
||||
app_state->image_dir_iter = new LLDirIterator(argv[i], "*.jpg");
|
||||
}
|
||||
else if ((!strcmp(argv[i], "--dest")) && (++i < argc))
|
||||
{
|
||||
|
|
@ -772,8 +795,8 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
|
|||
}
|
||||
}
|
||||
|
||||
if (app_state->app_name.empty()
|
||||
|| (app_state->url.empty() && app_state->file.empty())
|
||||
if (app_state->app_name.empty()
|
||||
|| (app_state->url.empty() && app_state->file.empty())
|
||||
|| app_state->dest_dir.empty())
|
||||
{
|
||||
show_usage_and_exit();
|
||||
|
|
@ -799,7 +822,7 @@ int main(int argc, char **argv)
|
|||
(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
|
||||
std::string old_log_file = gDirUtilp->getExpandedFilename
|
||||
(LL_PATH_LOGS, "updater.log.old");
|
||||
std::string log_file =
|
||||
std::string log_file =
|
||||
gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
|
||||
LLFile::rename(log_file, old_log_file);
|
||||
LLError::logToFile(log_file);
|
||||
|
|
@ -841,3 +864,63 @@ int main(int argc, char **argv)
|
|||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Dummy LLTrans implementation (IQA-490)
|
||||
*****************************************************************************/
|
||||
static LLTrans sStaticStrings;
|
||||
|
||||
// lookup
|
||||
std::string LLTrans::_getString(const std::string& key) const
|
||||
{
|
||||
MessageMap::const_iterator found = mMessages.find(key);
|
||||
if (found != mMessages.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
LL_WARNS("linux_updater") << "No message for key '" << key
|
||||
<< "' -- add to LLTrans::LLTrans() in linux_updater.cpp"
|
||||
<< LL_ENDL;
|
||||
return key;
|
||||
}
|
||||
|
||||
// static lookup
|
||||
std::string LLTrans::getString(const std::string& key)
|
||||
{
|
||||
return sLLTransInstance._getString(key);
|
||||
}
|
||||
|
||||
// initialization
|
||||
LLTrans::LLTrans()
|
||||
{
|
||||
typedef std::pair<const char*, const char*> Pair;
|
||||
static const Pair data[] =
|
||||
{
|
||||
Pair("UpdaterFailDownloadTitle",
|
||||
"Failed to download update"),
|
||||
Pair("UpdaterFailInstallTitle",
|
||||
"Failed to install update"),
|
||||
Pair("UpdaterFailStartTitle",
|
||||
"Failed to start viewer"),
|
||||
Pair("UpdaterFailUpdateDescriptive",
|
||||
"An error occurred while updating Second Life. "
|
||||
"Please download the latest version from www.secondlife.com."),
|
||||
Pair("UpdaterNowInstalling",
|
||||
"Installing Second Life..."),
|
||||
Pair("UpdaterNowUpdating",
|
||||
"Now updating Second Life..."),
|
||||
Pair("UpdaterProgressBarText",
|
||||
"Downloading update"),
|
||||
Pair("UpdaterProgressBarTextWithEllipses",
|
||||
"Downloading update..."),
|
||||
Pair("UpdaterUpdatingDescriptive",
|
||||
"Your Second Life Viewer is being updated to the latest release. "
|
||||
"This may take some time, so please be patient."),
|
||||
Pair("UpdaterWindowTitle",
|
||||
"Second Life Update")
|
||||
};
|
||||
|
||||
BOOST_FOREACH(Pair pair, data)
|
||||
{
|
||||
mMessages[pair.first] = pair.second;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -571,7 +571,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl;
|
||||
mCurrentDecodep->flushBadFile();
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
adp->setHasValidData(FALSE);
|
||||
adp->setHasValidData(false);
|
||||
adp->setHasCompletedDecode(true);
|
||||
mCurrentDecodep = NULL;
|
||||
done = TRUE;
|
||||
}
|
||||
|
|
@ -586,11 +587,16 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
if (mCurrentDecodep->finishDecode())
|
||||
{
|
||||
// We finished!
|
||||
if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone())
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
if (!adp)
|
||||
{
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
adp->setHasDecodedData(TRUE);
|
||||
adp->setHasValidData(TRUE);
|
||||
llwarns << "Missing LLAudioData for decode of " << mCurrentDecodep->getUUID() << llendl;
|
||||
}
|
||||
else if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone())
|
||||
{
|
||||
adp->setHasCompletedDecode(true);
|
||||
adp->setHasDecodedData(true);
|
||||
adp->setHasValidData(true);
|
||||
|
||||
// At this point, we could see if anyone needs this sound immediately, but
|
||||
// I'm not sure that there's a reason to - we need to poll all of the playing
|
||||
|
|
@ -599,7 +605,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
}
|
||||
else
|
||||
{
|
||||
llinfos << "Vorbis decode failed!!!" << llendl;
|
||||
adp->setHasCompletedDecode(true);
|
||||
llinfos << "Vorbis decode failed for " << mCurrentDecodep->getUUID() << llendl;
|
||||
}
|
||||
mCurrentDecodep = NULL;
|
||||
}
|
||||
|
|
@ -667,16 +674,19 @@ BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
|
|||
if (gAudiop->hasDecodedFile(uuid))
|
||||
{
|
||||
// Already have a decoded version, don't need to decode it.
|
||||
//llinfos << "addDecodeRequest for " << uuid << " has decoded file already" << llendl;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
|
||||
{
|
||||
// Just put it on the decode queue.
|
||||
//llinfos << "addDecodeRequest for " << uuid << " has local asset file already" << llendl;
|
||||
mImpl->mDecodeQueue.push(uuid);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//llinfos << "addDecodeRequest for " << uuid << " no file available" << llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1221,10 +1221,11 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
|
|||
// Need to mark data as bad to avoid constant rerequests.
|
||||
LLAudioData *adp = gAudiop->getAudioData(uuid);
|
||||
if (adp)
|
||||
{
|
||||
{ // Make sure everything is cleared
|
||||
adp->setHasValidData(false);
|
||||
adp->setHasLocalData(false);
|
||||
adp->setHasDecodedData(false);
|
||||
adp->setHasCompletedDecode(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1237,6 +1238,7 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
|
|||
}
|
||||
else
|
||||
{
|
||||
// llinfos << "Got asset callback with good audio data for " << uuid << ", making decode request" << llendl;
|
||||
adp->setHasValidData(true);
|
||||
adp->setHasLocalData(true);
|
||||
gAudioDecodeMgrp->addDecodeRequest(uuid);
|
||||
|
|
@ -1304,16 +1306,18 @@ void LLAudioSource::update()
|
|||
|
||||
if (!getCurrentBuffer())
|
||||
{
|
||||
if (getCurrentData())
|
||||
LLAudioData *adp = getCurrentData();
|
||||
if (adp)
|
||||
{
|
||||
// Hack - try and load the sound. Will do this as a callback
|
||||
// on decode later.
|
||||
if (getCurrentData()->load() && getCurrentData()->getBuffer())
|
||||
if (adp->load() && adp->getBuffer())
|
||||
{
|
||||
play(getCurrentData()->getID());
|
||||
play(adp->getID());
|
||||
}
|
||||
else
|
||||
else if (adp->hasCompletedDecode()) // Only mark corrupted after decode is done
|
||||
{
|
||||
llwarns << "Marking LLAudioSource corrupted for " << adp->getID() << llendl;
|
||||
mCorrupted = true ;
|
||||
}
|
||||
}
|
||||
|
|
@ -1731,6 +1735,7 @@ LLAudioData::LLAudioData(const LLUUID &uuid) :
|
|||
mBufferp(NULL),
|
||||
mHasLocalData(false),
|
||||
mHasDecodedData(false),
|
||||
mHasCompletedDecode(false),
|
||||
mHasValidData(true)
|
||||
{
|
||||
if (uuid.isNull())
|
||||
|
|
@ -1742,12 +1747,13 @@ LLAudioData::LLAudioData(const LLUUID &uuid) :
|
|||
if (gAudiop && gAudiop->hasDecodedFile(uuid))
|
||||
{
|
||||
// Already have a decoded version, don't need to decode it.
|
||||
mHasLocalData = true;
|
||||
mHasDecodedData = true;
|
||||
setHasLocalData(true);
|
||||
setHasDecodedData(true);
|
||||
setHasCompletedDecode(true);
|
||||
}
|
||||
else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
|
||||
{
|
||||
mHasLocalData = true;
|
||||
setHasLocalData(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -372,10 +372,12 @@ public:
|
|||
|
||||
bool hasLocalData() const { return mHasLocalData; }
|
||||
bool hasDecodedData() const { return mHasDecodedData; }
|
||||
bool hasCompletedDecode() const { return mHasCompletedDecode; }
|
||||
bool hasValidData() const { return mHasValidData; }
|
||||
|
||||
void setHasLocalData(const bool hld) { mHasLocalData = hld; }
|
||||
void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; }
|
||||
void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; }
|
||||
void setHasValidData(const bool hvd) { mHasValidData = hvd; }
|
||||
|
||||
friend class LLAudioEngine; // Severe laziness, bad.
|
||||
|
|
@ -383,9 +385,10 @@ public:
|
|||
protected:
|
||||
LLUUID mID;
|
||||
LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here.
|
||||
bool mHasLocalData;
|
||||
bool mHasDecodedData;
|
||||
bool mHasValidData;
|
||||
bool mHasLocalData; // Set true if the sound asset file is available locally
|
||||
bool mHasDecodedData; // Set true if the sound file has been decoded
|
||||
bool mHasCompletedDecode; // Set true when the sound is decoded
|
||||
bool mHasValidData; // Set false if decoding failed, meaning the sound asset is bad
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,10 @@ set(llcommon_SOURCE_FILES
|
|||
llformat.cpp
|
||||
llframetimer.cpp
|
||||
llheartbeat.cpp
|
||||
llinitparam.cpp
|
||||
llinstancetracker.cpp
|
||||
llleap.cpp
|
||||
llleaplistener.cpp
|
||||
llliveappconfig.cpp
|
||||
lllivefile.cpp
|
||||
lllog.cpp
|
||||
|
|
@ -74,13 +77,14 @@ set(llcommon_SOURCE_FILES
|
|||
llmortician.cpp
|
||||
lloptioninterface.cpp
|
||||
llptrto.cpp
|
||||
llprocesslauncher.cpp
|
||||
llprocess.cpp
|
||||
llprocessor.cpp
|
||||
llqueuedthread.cpp
|
||||
llrand.cpp
|
||||
llrefcount.cpp
|
||||
llrun.cpp
|
||||
llsd.cpp
|
||||
llsdparam.cpp
|
||||
llsdserialize.cpp
|
||||
llsdserialize_xml.cpp
|
||||
llsdutil.cpp
|
||||
|
|
@ -88,6 +92,7 @@ set(llcommon_SOURCE_FILES
|
|||
llsingleton.cpp
|
||||
llstat.cpp
|
||||
llstacktrace.cpp
|
||||
llstreamqueue.cpp
|
||||
llstreamtools.cpp
|
||||
llstring.cpp
|
||||
llstringtable.cpp
|
||||
|
|
@ -173,9 +178,12 @@ set(llcommon_HEADER_FILES
|
|||
llheartbeat.h
|
||||
llhttpstatuscodes.h
|
||||
llindexedqueue.h
|
||||
llinitparam.h
|
||||
llinstancetracker.h
|
||||
llkeythrottle.h
|
||||
lllazy.h
|
||||
llleap.h
|
||||
llleaplistener.h
|
||||
lllistenerwrapper.h
|
||||
lllinkedqueue.h
|
||||
llliveappconfig.h
|
||||
|
|
@ -196,7 +204,7 @@ set(llcommon_HEADER_FILES
|
|||
llpointer.h
|
||||
llpreprocessor.h
|
||||
llpriqueuemap.h
|
||||
llprocesslauncher.h
|
||||
llprocess.h
|
||||
llprocessor.h
|
||||
llptrskiplist.h
|
||||
llptrskipmap.h
|
||||
|
|
@ -204,10 +212,12 @@ set(llcommon_HEADER_FILES
|
|||
llqueuedthread.h
|
||||
llrand.h
|
||||
llrefcount.h
|
||||
llregistry.h
|
||||
llrun.h
|
||||
llrefcount.h
|
||||
llsafehandle.h
|
||||
llsd.h
|
||||
llsdparam.h
|
||||
llsdserialize.h
|
||||
llsdserialize_xml.h
|
||||
llsdutil.h
|
||||
|
|
@ -216,11 +226,13 @@ set(llcommon_HEADER_FILES
|
|||
llsingleton.h
|
||||
llskiplist.h
|
||||
llskipmap.h
|
||||
llsortedvector.h
|
||||
llstack.h
|
||||
llstacktrace.h
|
||||
llstat.h
|
||||
llstatenums.h
|
||||
llstl.h
|
||||
llstreamqueue.h
|
||||
llstreamtools.h
|
||||
llstrider.h
|
||||
llstring.h
|
||||
|
|
@ -230,6 +242,7 @@ set(llcommon_HEADER_FILES
|
|||
llthreadsafequeue.h
|
||||
lltimer.h
|
||||
lltreeiterators.h
|
||||
lltypeinfolookup.h
|
||||
lluri.h
|
||||
lluuid.h
|
||||
lluuidhashmap.h
|
||||
|
|
@ -317,8 +330,7 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}"
|
||||
"${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py")
|
||||
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
|
||||
|
|
@ -326,6 +338,9 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
|
||||
|
||||
# *TODO - reenable these once tcmalloc libs no longer break the build.
|
||||
#ADD_BUILD_TEST(llallocator llcommon)
|
||||
|
|
|
|||
|
|
@ -654,9 +654,7 @@ namespace LLError
|
|||
g.invalidateCallSites();
|
||||
s.tagLevelMap[tag_name] = level;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
LLError::ELevel decodeLevel(std::string name)
|
||||
{
|
||||
static LevelMap level_names;
|
||||
|
|
@ -681,7 +679,9 @@ namespace {
|
|||
|
||||
return i->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
|
||||
{
|
||||
LLSD::array_const_iterator i, end;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llerrorcontrol.h
|
||||
* @date December 2006
|
||||
* @brief error message system control
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $LicenseInfo:firstyear=2007&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$
|
||||
*/
|
||||
|
|
@ -38,7 +38,7 @@ class LLSD;
|
|||
This is the part of the LLError namespace that manages the messages
|
||||
produced by the logging. The logging support is defined in llerror.h.
|
||||
Most files do not need to include this.
|
||||
|
||||
|
||||
These implementations are in llerror.cpp.
|
||||
*/
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ namespace LLError
|
|||
Settings that control what is logged.
|
||||
Setting a level means log messages at that level or above.
|
||||
*/
|
||||
|
||||
|
||||
LL_COMMON_API void setPrintLocation(bool);
|
||||
LL_COMMON_API void setDefaultLevel(LLError::ELevel);
|
||||
LL_COMMON_API ELevel getDefaultLevel();
|
||||
|
|
@ -80,7 +80,8 @@ namespace LLError
|
|||
LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel);
|
||||
LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel);
|
||||
LL_COMMON_API void setTagLevel(const std::string& file_name, LLError::ELevel);
|
||||
|
||||
|
||||
LL_COMMON_API LLError::ELevel decodeLevel(std::string name);
|
||||
LL_COMMON_API void configure(const LLSD&);
|
||||
// the LLSD can configure all of the settings
|
||||
// usually read automatically from the live errorlog.xml file
|
||||
|
|
@ -100,31 +101,31 @@ namespace LLError
|
|||
// (by, for example, setting a class level to LEVEL_NONE), will keep
|
||||
// the that message from causing the fatal funciton to be invoked.
|
||||
|
||||
LL_COMMON_API FatalFunction getFatalFunction();
|
||||
// Retrieve the previously-set FatalFunction
|
||||
LL_COMMON_API FatalFunction getFatalFunction();
|
||||
// Retrieve the previously-set FatalFunction
|
||||
|
||||
/// temporarily override the FatalFunction for the duration of a
|
||||
/// particular scope, e.g. for unit tests
|
||||
class LL_COMMON_API OverrideFatalFunction
|
||||
{
|
||||
public:
|
||||
OverrideFatalFunction(const FatalFunction& func):
|
||||
mPrev(getFatalFunction())
|
||||
{
|
||||
setFatalFunction(func);
|
||||
}
|
||||
~OverrideFatalFunction()
|
||||
{
|
||||
setFatalFunction(mPrev);
|
||||
}
|
||||
/// temporarily override the FatalFunction for the duration of a
|
||||
/// particular scope, e.g. for unit tests
|
||||
class LL_COMMON_API OverrideFatalFunction
|
||||
{
|
||||
public:
|
||||
OverrideFatalFunction(const FatalFunction& func):
|
||||
mPrev(getFatalFunction())
|
||||
{
|
||||
setFatalFunction(func);
|
||||
}
|
||||
~OverrideFatalFunction()
|
||||
{
|
||||
setFatalFunction(mPrev);
|
||||
}
|
||||
|
||||
private:
|
||||
FatalFunction mPrev;
|
||||
};
|
||||
private:
|
||||
FatalFunction mPrev;
|
||||
};
|
||||
|
||||
typedef std::string (*TimeFunction)();
|
||||
LL_COMMON_API std::string utcTime();
|
||||
|
||||
|
||||
LL_COMMON_API void setTimeFunction(TimeFunction);
|
||||
// The function is use to return the current time, formatted for
|
||||
// display by those error recorders that want the time included.
|
||||
|
|
@ -136,19 +137,27 @@ namespace LLError
|
|||
// An object that handles the actual output or error messages.
|
||||
public:
|
||||
virtual ~Recorder();
|
||||
|
||||
|
||||
virtual void recordMessage(LLError::ELevel, const std::string& message) = 0;
|
||||
// use the level for better display, not for filtering
|
||||
|
||||
|
||||
virtual bool wantsTime(); // default returns false
|
||||
// override and return true if the recorder wants the time string
|
||||
// included in the text of the message
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @NOTE: addRecorder() conveys ownership to the underlying Settings
|
||||
* object -- when destroyed, it will @em delete the passed Recorder*!
|
||||
*/
|
||||
LL_COMMON_API void addRecorder(Recorder*);
|
||||
/**
|
||||
* @NOTE: removeRecorder() reclaims ownership of the Recorder*: its
|
||||
* lifespan becomes the caller's problem.
|
||||
*/
|
||||
LL_COMMON_API void removeRecorder(Recorder*);
|
||||
// each error message is passed to each recorder via recordMessage()
|
||||
|
||||
|
||||
LL_COMMON_API void logToFile(const std::string& filename);
|
||||
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
|
||||
// Utilities to add recorders for logging to a file or a fixed buffer
|
||||
|
|
@ -166,10 +175,9 @@ namespace LLError
|
|||
class Settings;
|
||||
LL_COMMON_API Settings* saveAndResetSettings();
|
||||
LL_COMMON_API void restoreSettings(Settings *);
|
||||
|
||||
|
||||
LL_COMMON_API std::string abbreviateFile(const std::string& filePath);
|
||||
LL_COMMON_API int shouldLogCallCount();
|
||||
|
||||
};
|
||||
|
||||
#endif // LL_LLERRORCONTROL_H
|
||||
|
|
|
|||
|
|
@ -112,13 +112,8 @@ void LLErrorThread::run()
|
|||
#if !LL_WINDOWS
|
||||
U32 last_sig_child_count = 0;
|
||||
#endif
|
||||
while (1)
|
||||
while (! (LLApp::isError() || LLApp::isStopped()))
|
||||
{
|
||||
if (LLApp::isError() || LLApp::isStopped())
|
||||
{
|
||||
// The application has stopped running, time to take action (maybe)
|
||||
break;
|
||||
}
|
||||
#if !LL_WINDOWS
|
||||
// Check whether or not the main thread had a sig child we haven't handled.
|
||||
U32 current_sig_child_count = LLApp::getSigChildCount();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llfile.cpp
|
||||
* @author Michael Schlachter
|
||||
* @date 2006-03-23
|
||||
|
|
@ -8,60 +8,194 @@
|
|||
* $LicenseInfo:firstyear=2006&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$
|
||||
*/
|
||||
|
||||
#if LL_WINDOWS
|
||||
#include <windows.h>
|
||||
#include <stdlib.h> // Windows errno
|
||||
#else
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "llfile.h"
|
||||
#include "llstring.h"
|
||||
#include "llerror.h"
|
||||
#include "stringize.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static std::string empty;
|
||||
|
||||
// Many of the methods below use OS-level functions that mess with errno. Wrap
|
||||
// variants of strerror() to report errors.
|
||||
|
||||
#if LL_WINDOWS
|
||||
// On Windows, use strerror_s().
|
||||
std::string strerr(int errn)
|
||||
{
|
||||
char buffer[256];
|
||||
strerror_s(buffer, errn); // infers sizeof(buffer) -- love it!
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#else
|
||||
// On Posix we want to call strerror_r(), but alarmingly, there are two
|
||||
// different variants. The one that returns int always populates the passed
|
||||
// buffer (except in case of error), whereas the other one always returns a
|
||||
// valid char* but might or might not populate the passed buffer. How do we
|
||||
// know which one we're getting? Define adapters for each and let the compiler
|
||||
// select the applicable adapter.
|
||||
|
||||
// strerror_r() returns char*
|
||||
std::string message_from(int /*orig_errno*/, const char* /*buffer*/, size_t /*bufflen*/,
|
||||
const char* strerror_ret)
|
||||
{
|
||||
return strerror_ret;
|
||||
}
|
||||
|
||||
// strerror_r() returns int
|
||||
std::string message_from(int orig_errno, const char* buffer, size_t bufflen,
|
||||
int strerror_ret)
|
||||
{
|
||||
if (strerror_ret == 0)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
// Here strerror_r() has set errno. Since strerror_r() has already failed,
|
||||
// seems like a poor bet to call it again to diagnose its own error...
|
||||
int stre_errno = errno;
|
||||
if (stre_errno == ERANGE)
|
||||
{
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (" << bufflen << "-byte buffer too small)");
|
||||
}
|
||||
if (stre_errno == EINVAL)
|
||||
{
|
||||
return STRINGIZE("unknown errno " << orig_errno);
|
||||
}
|
||||
// Here we don't even understand the errno from strerror_r()!
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (error " << stre_errno << ')');
|
||||
}
|
||||
|
||||
std::string strerr(int errn)
|
||||
{
|
||||
char buffer[256];
|
||||
// Select message_from() function matching the strerror_r() we have on hand.
|
||||
return message_from(errn, buffer, sizeof(buffer),
|
||||
strerror_r(errn, buffer, sizeof(buffer)));
|
||||
}
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// On either system, shorthand call just infers global 'errno'.
|
||||
std::string strerr()
|
||||
{
|
||||
return strerr(errno);
|
||||
}
|
||||
|
||||
int warnif(const std::string& desc, const std::string& filename, int rc, int accept=0)
|
||||
{
|
||||
if (rc < 0)
|
||||
{
|
||||
// Capture errno before we start emitting output
|
||||
int errn = errno;
|
||||
// For certain operations, a particular errno value might be
|
||||
// acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit
|
||||
// EEXIST. Don't warn if caller explicitly says this errno is okay.
|
||||
if (errn != accept)
|
||||
{
|
||||
LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename
|
||||
<< "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
|
||||
}
|
||||
#if 0 && LL_WINDOWS // turn on to debug file-locking problems
|
||||
// If the problem is "Permission denied," maybe it's because another
|
||||
// process has the file open. Try to find out.
|
||||
if (errn == EACCES) // *not* EPERM
|
||||
{
|
||||
// Only do any of this stuff (before LL_ENDL) if it will be logged.
|
||||
LL_DEBUGS("LLFile") << empty;
|
||||
const char* TEMP = getenv("TEMP");
|
||||
if (! TEMP)
|
||||
{
|
||||
LL_CONT << "No $TEMP, not running 'handle'";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string tf(TEMP);
|
||||
tf += "\\handle.tmp";
|
||||
// http://technet.microsoft.com/en-us/sysinternals/bb896655
|
||||
std::string cmd(STRINGIZE("handle \"" << filename
|
||||
// "openfiles /query /v | fgrep -i \"" << filename
|
||||
<< "\" > \"" << tf << '"'));
|
||||
LL_CONT << cmd;
|
||||
if (system(cmd.c_str()) != 0)
|
||||
{
|
||||
LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ifstream inf(tf);
|
||||
std::string line;
|
||||
while (std::getline(inf, line))
|
||||
{
|
||||
LL_CONT << '\n' << line;
|
||||
}
|
||||
}
|
||||
LLFile::remove(tf);
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
}
|
||||
#endif // LL_WINDOWS hack to identify processes holding file open
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// static
|
||||
int LLFile::mkdir(const std::string& dirname, int perms)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
#if LL_WINDOWS
|
||||
// permissions are ignored on Windows
|
||||
std::string utf8dirname = dirname;
|
||||
llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
|
||||
return _wmkdir(utf16dirname.c_str());
|
||||
int rc = _wmkdir(utf16dirname.c_str());
|
||||
#else
|
||||
return ::mkdir(dirname.c_str(), (mode_t)perms);
|
||||
int rc = ::mkdir(dirname.c_str(), (mode_t)perms);
|
||||
#endif
|
||||
// We often use mkdir() to ensure the existence of a directory that might
|
||||
// already exist. Don't spam the log if it does.
|
||||
return warnif("mkdir", dirname, rc, EEXIST);
|
||||
}
|
||||
|
||||
// static
|
||||
int LLFile::rmdir(const std::string& dirname)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
#if LL_WINDOWS
|
||||
// permissions are ignored on Windows
|
||||
std::string utf8dirname = dirname;
|
||||
llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
|
||||
return _wrmdir(utf16dirname.c_str());
|
||||
int rc = _wrmdir(utf16dirname.c_str());
|
||||
#else
|
||||
return ::rmdir(dirname.c_str());
|
||||
int rc = ::rmdir(dirname.c_str());
|
||||
#endif
|
||||
return warnif("rmdir", dirname, rc);
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -108,10 +242,11 @@ int LLFile::remove(const std::string& filename)
|
|||
#if LL_WINDOWS
|
||||
std::string utf8filename = filename;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
return _wremove(utf16filename.c_str());
|
||||
int rc = _wremove(utf16filename.c_str());
|
||||
#else
|
||||
return ::remove(filename.c_str());
|
||||
int rc = ::remove(filename.c_str());
|
||||
#endif
|
||||
return warnif("remove", filename, rc);
|
||||
}
|
||||
|
||||
int LLFile::rename(const std::string& filename, const std::string& newname)
|
||||
|
|
@ -121,10 +256,11 @@ int LLFile::rename(const std::string& filename, const std::string& newname)
|
|||
std::string utf8newname = newname;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
llutf16string utf16newname = utf8str_to_utf16str(utf8newname);
|
||||
return _wrename(utf16filename.c_str(),utf16newname.c_str());
|
||||
int rc = _wrename(utf16filename.c_str(),utf16newname.c_str());
|
||||
#else
|
||||
return ::rename(filename.c_str(),newname.c_str());
|
||||
int rc = ::rename(filename.c_str(),newname.c_str());
|
||||
#endif
|
||||
return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc);
|
||||
}
|
||||
|
||||
int LLFile::stat(const std::string& filename, llstat* filestatus)
|
||||
|
|
@ -132,23 +268,26 @@ int LLFile::stat(const std::string& filename, llstat* filestatus)
|
|||
#if LL_WINDOWS
|
||||
std::string utf8filename = filename;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
return _wstat(utf16filename.c_str(),filestatus);
|
||||
int rc = _wstat(utf16filename.c_str(),filestatus);
|
||||
#else
|
||||
return ::stat(filename.c_str(),filestatus);
|
||||
int rc = ::stat(filename.c_str(),filestatus);
|
||||
#endif
|
||||
// We use stat() to determine existence (see isfile(), isdir()).
|
||||
// Don't spam the log if the subject pathname doesn't exist.
|
||||
return warnif("stat", filename, rc, ENOENT);
|
||||
}
|
||||
|
||||
bool LLFile::isdir(const std::string& filename)
|
||||
{
|
||||
llstat st;
|
||||
|
||||
|
||||
return stat(filename, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
bool LLFile::isfile(const std::string& filename)
|
||||
{
|
||||
llstat st;
|
||||
|
||||
|
||||
return stat(filename, &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +399,7 @@ void llifstream::open(const std::string& _Filename, /* Flawfinder: ignore */
|
|||
ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
{ // open a C stream with specified mode
|
||||
|
||||
|
||||
LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::in, _Prot);
|
||||
if(filep == NULL)
|
||||
{
|
||||
|
|
@ -280,7 +419,7 @@ bool llifstream::is_open() const
|
|||
return false;
|
||||
}
|
||||
llifstream::~llifstream()
|
||||
{
|
||||
{
|
||||
if (_ShouldClose)
|
||||
{
|
||||
close();
|
||||
|
|
@ -309,7 +448,7 @@ bool llofstream::is_open() const
|
|||
|
||||
void llofstream::open(const std::string& _Filename, /* Flawfinder: ignore */
|
||||
ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
int _Prot)
|
||||
{ // open a C stream with specified mode
|
||||
|
||||
LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::out, _Prot);
|
||||
|
|
@ -340,14 +479,14 @@ void llofstream::close()
|
|||
|
||||
llofstream::llofstream(const std::string& _Filename,
|
||||
std::ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
int _Prot)
|
||||
: std::basic_ostream<char,std::char_traits < char > >(NULL,true),_Filebuffer(NULL),_ShouldClose(false)
|
||||
{ // construct with named file and specified mode
|
||||
open(_Filename, _Mode , _Prot); /* Flawfinder: ignore */
|
||||
}
|
||||
|
||||
llofstream::~llofstream()
|
||||
{
|
||||
{
|
||||
// destroy the object
|
||||
if (_ShouldClose)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "llerror.h"
|
||||
#include "lltypeinfolookup.h"
|
||||
|
||||
namespace LLInitParam
|
||||
{
|
||||
|
|
@ -205,7 +206,7 @@ namespace LLInitParam
|
|||
mutable std::string mValueName;
|
||||
};
|
||||
|
||||
class Parser
|
||||
class LL_COMMON_API Parser
|
||||
{
|
||||
LOG_CLASS(Parser);
|
||||
|
||||
|
|
@ -227,9 +228,9 @@ namespace LLInitParam
|
|||
typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&);
|
||||
typedef boost::function<void (name_stack_t&, S32, S32, const possible_values_t*)> parser_inspect_func_t;
|
||||
|
||||
typedef std::map<const std::type_info*, parser_read_func_t, CompareTypeID> parser_read_func_map_t;
|
||||
typedef std::map<const std::type_info*, parser_write_func_t, CompareTypeID> parser_write_func_map_t;
|
||||
typedef std::map<const std::type_info*, parser_inspect_func_t, CompareTypeID> parser_inspect_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_read_func_t> parser_read_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_write_func_t> parser_write_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_inspect_func_t> parser_inspect_func_map_t;
|
||||
|
||||
Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map)
|
||||
: mParseSilently(false),
|
||||
|
|
@ -301,7 +302,7 @@ namespace LLInitParam
|
|||
class Param;
|
||||
|
||||
// various callbacks and constraints associated with an individual param
|
||||
struct ParamDescriptor
|
||||
struct LL_COMMON_API ParamDescriptor
|
||||
{
|
||||
struct UserData
|
||||
{
|
||||
|
|
@ -341,7 +342,7 @@ namespace LLInitParam
|
|||
typedef boost::shared_ptr<ParamDescriptor> ParamDescriptorPtr;
|
||||
|
||||
// each derived Block class keeps a static data structure maintaining offsets to various params
|
||||
class BlockDescriptor
|
||||
class LL_COMMON_API BlockDescriptor
|
||||
{
|
||||
public:
|
||||
BlockDescriptor();
|
||||
|
|
@ -369,7 +370,7 @@ namespace LLInitParam
|
|||
class BaseBlock* mCurrentBlockPtr; // pointer to block currently being constructed
|
||||
};
|
||||
|
||||
class BaseBlock
|
||||
class LL_COMMON_API BaseBlock
|
||||
{
|
||||
public:
|
||||
//TODO: implement in terms of owned_ptr
|
||||
|
|
@ -566,7 +567,7 @@ namespace LLInitParam
|
|||
static bool equals(const BaseBlock::Lazy<T>& a, const BaseBlock::Lazy<T>& b) { return !a.empty() || !b.empty(); }
|
||||
};
|
||||
|
||||
class Param
|
||||
class LL_COMMON_API Param
|
||||
{
|
||||
public:
|
||||
void setProvided(bool is_provided = true)
|
||||
|
|
@ -2062,8 +2063,8 @@ namespace LLInitParam
|
|||
|
||||
|
||||
// block param interface
|
||||
bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name);
|
||||
void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;
|
||||
LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name);
|
||||
LL_COMMON_API void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;
|
||||
bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const
|
||||
{
|
||||
//TODO: implement LLSD params as schema type Any
|
||||
|
|
@ -167,8 +167,9 @@ public:
|
|||
|
||||
static T* getInstance(const KEY& k)
|
||||
{
|
||||
typename InstanceMap::const_iterator found = getMap_().find(k);
|
||||
return (found == getMap_().end()) ? NULL : found->second;
|
||||
const InstanceMap& map(getMap_());
|
||||
typename InstanceMap::const_iterator found = map.find(k);
|
||||
return (found == map.end()) ? NULL : found->second;
|
||||
}
|
||||
|
||||
static instance_iter beginInstances()
|
||||
|
|
@ -239,8 +240,20 @@ class LLInstanceTracker<T, T*> : public LLInstanceTrackerBase
|
|||
|
||||
public:
|
||||
|
||||
/// for completeness of analogy with the generic implementation
|
||||
static T* getInstance(T* k) { return k; }
|
||||
/**
|
||||
* Does a particular instance still exist? Of course, if you already have
|
||||
* a T* in hand, you need not call getInstance() to @em locate the
|
||||
* instance -- unlike the case where getInstance() accepts some kind of
|
||||
* key. Nonetheless this method is still useful to @em validate a
|
||||
* particular T*, since each instance's destructor removes itself from the
|
||||
* underlying set.
|
||||
*/
|
||||
static T* getInstance(T* k)
|
||||
{
|
||||
const InstanceSet& set(getSet_());
|
||||
typename InstanceSet::const_iterator found = set.find(k);
|
||||
return (found == set.end())? NULL : *found;
|
||||
}
|
||||
static S32 instanceCount() { return getSet_().size(); }
|
||||
|
||||
class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,459 @@
|
|||
/**
|
||||
* @file llleap.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-20
|
||||
* @brief Implementation for llleap.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleap.h"
|
||||
// STL headers
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
#include "llstring.h"
|
||||
#include "llprocess.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llerrorcontrol.h"
|
||||
#include "lltimer.h"
|
||||
#include "lluuid.h"
|
||||
#include "llleaplistener.h"
|
||||
|
||||
#if LL_MSVC
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
LLLeap::LLLeap() {}
|
||||
LLLeap::~LLLeap() {}
|
||||
|
||||
class LLLeapImpl: public LLLeap
|
||||
{
|
||||
LOG_CLASS(LLLeap);
|
||||
public:
|
||||
// Called only by LLLeap::create()
|
||||
LLLeapImpl(const std::string& desc, const std::vector<std::string>& plugin):
|
||||
// We might reassign mDesc in the constructor body if it's empty here.
|
||||
mDesc(desc),
|
||||
// We expect multiple LLLeapImpl instances. Definitely tweak
|
||||
// mDonePump's name for uniqueness.
|
||||
mDonePump("LLLeap", true),
|
||||
// Troubling thought: what if one plugin intentionally messes with
|
||||
// another plugin? LLEventPump names are in a single global namespace.
|
||||
// Try to make that more difficult by generating a UUID for the reply-
|
||||
// pump name -- so it should NOT need tweaking for uniqueness.
|
||||
mReplyPump(LLUUID::generateNewID().asString()),
|
||||
mExpect(0),
|
||||
mPrevFatalFunction(LLError::getFatalFunction()),
|
||||
// Instantiate a distinct LLLeapListener for this plugin. (Every
|
||||
// plugin will want its own collection of managed listeners, etc.)
|
||||
// Pass it a callback to our connect() method, so it can send events
|
||||
// from a particular LLEventPump to the plugin without having to know
|
||||
// this class or method name.
|
||||
mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))
|
||||
{
|
||||
// Rule out empty vector
|
||||
if (plugin.empty())
|
||||
{
|
||||
throw Error("no plugin command");
|
||||
}
|
||||
|
||||
// Don't leave desc empty either, but in this case, if we weren't
|
||||
// given one, we'll fake one.
|
||||
if (desc.empty())
|
||||
{
|
||||
mDesc = LLProcess::basename(plugin[0]);
|
||||
// how about a toLower() variant that returns the transformed string?!
|
||||
std::string desclower(mDesc);
|
||||
LLStringUtil::toLower(desclower);
|
||||
// If we're running a Python script, use the script name for the
|
||||
// desc instead of just 'python'. Arguably we should check for
|
||||
// more different interpreters as well, but there's a reason to
|
||||
// notice Python specially: we provide Python LLSD serialization
|
||||
// support, so there's a pretty good reason to implement plugins
|
||||
// in that language.
|
||||
if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe"))
|
||||
{
|
||||
mDesc = LLProcess::basename(plugin[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for child "termination" right away to catch launch errors.
|
||||
mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));
|
||||
|
||||
// Okay, launch child.
|
||||
LLProcess::Params params;
|
||||
params.desc = mDesc;
|
||||
std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end());
|
||||
params.executable = *pi++;
|
||||
for ( ; pi != pend; ++pi)
|
||||
{
|
||||
params.args.add(*pi);
|
||||
}
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stdin
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stdout
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stderr
|
||||
params.postend = mDonePump.getName();
|
||||
mChild = LLProcess::create(params);
|
||||
// If that didn't work, no point in keeping this LLLeap object.
|
||||
if (! mChild)
|
||||
{
|
||||
throw Error(STRINGIZE("failed to run " << mDesc));
|
||||
}
|
||||
|
||||
// Okay, launch apparently worked. Change our mDonePump listener.
|
||||
mDonePump.stopListening("LLLeap");
|
||||
mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1));
|
||||
|
||||
// Child might pump large volumes of data through either stdout or
|
||||
// stderr. Don't bother copying all that data into notification event.
|
||||
LLProcess::ReadPipe
|
||||
&childout(mChild->getReadPipe(LLProcess::STDOUT)),
|
||||
&childerr(mChild->getReadPipe(LLProcess::STDERR));
|
||||
childout.setLimit(20);
|
||||
childerr.setLimit(20);
|
||||
|
||||
// Serialize any event received on mReplyPump to our child's stdin.
|
||||
mStdinConnection = connect(mReplyPump, "LLLeap");
|
||||
|
||||
// Listening on stdout is stateful. In general, we're either waiting
|
||||
// for the length prefix or waiting for the specified length of data.
|
||||
// We address that with two different listener methods -- one of which
|
||||
// is blocked at any given time.
|
||||
mStdoutConnection = childout.getPump()
|
||||
.listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1));
|
||||
mStdoutDataConnection = childout.getPump()
|
||||
.listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1));
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
|
||||
|
||||
// Log anything sent up through stderr. When a typical program
|
||||
// encounters an error, it writes its error message to stderr and
|
||||
// terminates with nonzero exit code. In particular, the Python
|
||||
// interpreter behaves that way. More generally, though, a plugin
|
||||
// author can log whatever s/he wants to the viewer log using stderr.
|
||||
mStderrConnection = childerr.getPump()
|
||||
.listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1));
|
||||
|
||||
// For our lifespan, intercept any LL_ERRS so we can notify plugin
|
||||
LLError::setFatalFunction(boost::bind(&LLLeapImpl::fatalFunction, this, _1));
|
||||
|
||||
// Send child a preliminary event reporting our own reply-pump name --
|
||||
// which would otherwise be pretty tricky to guess!
|
||||
wstdin(mReplyPump.getName(),
|
||||
LLSDMap
|
||||
("command", mListener->getName())
|
||||
// Include LLLeap features -- this may be important for child to
|
||||
// construct (or recognize) current protocol.
|
||||
("features", LLLeapListener::getFeatures()));
|
||||
}
|
||||
|
||||
// Normally we'd expect to arrive here only via done()
|
||||
virtual ~LLLeapImpl()
|
||||
{
|
||||
LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL;
|
||||
// Restore original FatalFunction
|
||||
LLError::setFatalFunction(mPrevFatalFunction);
|
||||
}
|
||||
|
||||
// Listener for failed launch attempt
|
||||
bool bad_launch(const LLSD& data)
|
||||
{
|
||||
LL_WARNS("LLLeap") << data["string"].asString() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listener for child-process termination
|
||||
bool done(const LLSD& data)
|
||||
{
|
||||
// Log the termination
|
||||
LL_INFOS("LLLeap") << data["string"].asString() << LL_ENDL;
|
||||
|
||||
// Any leftover data at this moment are because protocol was not
|
||||
// satisfied. Possibly the child was interrupted in the middle of
|
||||
// sending a message, possibly the child didn't flush stdout before
|
||||
// terminating, possibly it's just garbage. Log its existence but
|
||||
// discard it.
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
if (childout.size())
|
||||
{
|
||||
LLProcess::ReadPipe::size_type
|
||||
peeklen((std::min)(LLProcess::ReadPipe::size_type(50), childout.size()));
|
||||
LL_WARNS("LLLeap") << "Discarding final " << childout.size() << " bytes: "
|
||||
<< childout.peek(0, peeklen) << "..." << LL_ENDL;
|
||||
}
|
||||
|
||||
// Kill this instance. MUST BE LAST before return!
|
||||
delete this;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listener for events on mReplyPump: send to child stdin
|
||||
bool wstdin(const std::string& pump, const LLSD& data)
|
||||
{
|
||||
LLSD packet(LLSDMap("pump", pump)("data", data));
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << LLSDNotationStreamer(packet);
|
||||
|
||||
/*==========================================================================*|
|
||||
// DEBUGGING ONLY: don't copy str() if we can avoid it.
|
||||
std::string strdata(buffer.str());
|
||||
if (std::size_t(buffer.tellp()) != strdata.length())
|
||||
{
|
||||
LL_ERRS("LLLeap") << "tellp() -> " << buffer.tellp() << " != "
|
||||
<< "str().length() -> " << strdata.length() << LL_ENDL;
|
||||
}
|
||||
// DEBUGGING ONLY: reading back is terribly inefficient.
|
||||
std::istringstream readback(strdata);
|
||||
LLSD echo;
|
||||
LLPointer<LLSDParser> parser(new LLSDNotationParser());
|
||||
S32 parse_status(parser->parse(readback, echo, strdata.length()));
|
||||
if (parse_status == LLSDParser::PARSE_FAILURE)
|
||||
{
|
||||
LL_ERRS("LLLeap") << "LLSDNotationParser() cannot parse output of "
|
||||
<< "LLSDNotationStreamer()" << LL_ENDL;
|
||||
}
|
||||
if (! llsd_equals(echo, packet))
|
||||
{
|
||||
LL_ERRS("LLLeap") << "LLSDNotationParser() produced different LLSD "
|
||||
<< "than passed to LLSDNotationStreamer()" << LL_ENDL;
|
||||
}
|
||||
|*==========================================================================*/
|
||||
|
||||
LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':';
|
||||
std::string::size_type truncate(80);
|
||||
if (buffer.tellp() <= truncate)
|
||||
{
|
||||
LL_CONT << buffer.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_CONT << buffer.str().substr(0, truncate) << "...";
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
|
||||
LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN));
|
||||
childin.get_ostream() << buffer.tellp() << ':' << buffer.str() << std::flush;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initial state of stateful listening on child stdout: wait for a length
|
||||
// prefix, followed by ':'.
|
||||
bool rstdout(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
// It's possible we got notified of a couple digit characters without
|
||||
// seeing the ':' -- unlikely, but still. Until we see ':', keep
|
||||
// waiting.
|
||||
if (childout.contains(':'))
|
||||
{
|
||||
std::istream& childstream(childout.get_istream());
|
||||
// Saw ':', read length prefix and store in mExpect.
|
||||
size_t expect;
|
||||
childstream >> expect;
|
||||
int colon(childstream.get());
|
||||
if (colon != ':')
|
||||
{
|
||||
// Protocol failure. Clear out the rest of the pending data in
|
||||
// childout (well, up to a max length) to log what was wrong.
|
||||
LLProcess::ReadPipe::size_type
|
||||
readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80)));
|
||||
bad_protocol(STRINGIZE(expect << char(colon) << childout.read(readlen)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Saw length prefix, saw colon, life is good. Now wait for
|
||||
// that length of data to arrive.
|
||||
mExpect = expect;
|
||||
LL_DEBUGS("LLLeap") << "got length, waiting for "
|
||||
<< mExpect << " bytes of data" << LL_ENDL;
|
||||
// Block calls to this method; resetting mBlocker unblocks
|
||||
// calls to the other method.
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection));
|
||||
// Go check if we've already received all the advertised data.
|
||||
if (childout.size())
|
||||
{
|
||||
LLSD updata(data);
|
||||
updata["len"] = LLSD::Integer(childout.size());
|
||||
rstdoutData(updata);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childout.contains('\n'))
|
||||
{
|
||||
// Since this is the initial listening state, this is where we'd
|
||||
// arrive if the child isn't following protocol at all -- say
|
||||
// because the user specified 'ls' or some darn thing.
|
||||
bad_protocol(childout.getline());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// State in which we listen on stdout for the specified length of data to
|
||||
// arrive.
|
||||
bool rstdoutData(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
// Until we've accumulated the promised length of data, keep waiting.
|
||||
if (childout.size() >= mExpect)
|
||||
{
|
||||
// Ready to rock and roll.
|
||||
LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got "
|
||||
<< childout.size() << ", parsing LLSD" << LL_ENDL;
|
||||
LLSD data;
|
||||
LLPointer<LLSDParser> parser(new LLSDNotationParser());
|
||||
S32 parse_status(parser->parse(childout.get_istream(), data, mExpect));
|
||||
if (parse_status == LLSDParser::PARSE_FAILURE)
|
||||
{
|
||||
bad_protocol("unparseable LLSD data");
|
||||
}
|
||||
else if (! (data.isMap() && data["pump"].isString() && data.has("data")))
|
||||
{
|
||||
// we got an LLSD object, but it lacks required keys
|
||||
bad_protocol("missing 'pump' or 'data'");
|
||||
}
|
||||
else
|
||||
{
|
||||
// The LLSD object we got from our stream contains the keys we
|
||||
// need.
|
||||
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
|
||||
// Block calls to this method; resetting mBlocker unblocks calls
|
||||
// to the other method.
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
|
||||
// Go check for any more pending events in the buffer.
|
||||
if (childout.size())
|
||||
{
|
||||
LLSD updata(data);
|
||||
data["len"] = LLSD::Integer(childout.size());
|
||||
rstdout(updata);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_protocol(const std::string& data)
|
||||
{
|
||||
LL_WARNS("LLLeap") << mDesc << ": invalid protocol: " << data << LL_ENDL;
|
||||
// No point in continuing to run this child.
|
||||
mChild->kill();
|
||||
}
|
||||
|
||||
// Listen on child stderr and log everything that arrives
|
||||
bool rstderr(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childerr(mChild->getReadPipe(LLProcess::STDERR));
|
||||
// We might have gotten a notification involving only a partial line
|
||||
// -- or multiple lines. Read all complete lines; stop when there's
|
||||
// only a partial line left.
|
||||
while (childerr.contains('\n'))
|
||||
{
|
||||
// DO NOT make calls with side effects in a logging statement! If
|
||||
// that log level is suppressed, your side effects WON'T HAPPEN.
|
||||
std::string line(childerr.getline());
|
||||
// Log the received line. Prefix it with the desc so we know which
|
||||
// plugin it's from. This method name rstderr() is intentionally
|
||||
// chosen to further qualify the log output.
|
||||
LL_INFOS("LLLeap") << mDesc << ": " << line << LL_ENDL;
|
||||
}
|
||||
// What if child writes a final partial line to stderr?
|
||||
if (data["eof"].asBoolean() && childerr.size())
|
||||
{
|
||||
std::string rest(childerr.read(childerr.size()));
|
||||
// Read all remaining bytes and log.
|
||||
LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void fatalFunction(const std::string& error)
|
||||
{
|
||||
// Notify plugin
|
||||
LLSD event;
|
||||
event["type"] = "error";
|
||||
event["error"] = error;
|
||||
mReplyPump.post(event);
|
||||
|
||||
// All the above really accomplished was to buffer the serialized
|
||||
// event in our WritePipe. Have to pump mainloop a couple times to
|
||||
// really write it out there... but time out in case we can't write.
|
||||
LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN));
|
||||
LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
|
||||
LLSD nop;
|
||||
F64 until(LLTimer::getElapsedSeconds() + 2);
|
||||
while (childin.size() && LLTimer::getElapsedSeconds() < until)
|
||||
{
|
||||
mainloop.post(nop);
|
||||
}
|
||||
|
||||
// forward the call to the previous FatalFunction
|
||||
mPrevFatalFunction(error);
|
||||
}
|
||||
|
||||
private:
|
||||
/// We always want to listen on mReplyPump with wstdin(); under some
|
||||
/// circumstances we'll also echo other LLEventPumps to the plugin.
|
||||
LLBoundListener connect(LLEventPump& pump, const std::string& listener)
|
||||
{
|
||||
// Serialize any event received on the specified LLEventPump to our
|
||||
// child's stdin, suitably enriched with the pump name on which it was
|
||||
// received.
|
||||
return pump.listen(listener,
|
||||
boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1));
|
||||
}
|
||||
|
||||
std::string mDesc;
|
||||
LLEventStream mDonePump;
|
||||
LLEventStream mReplyPump;
|
||||
LLProcessPtr mChild;
|
||||
LLTempBoundListener
|
||||
mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection;
|
||||
boost::scoped_ptr<LLEventPump::Blocker> mBlocker;
|
||||
LLProcess::ReadPipe::size_type mExpect;
|
||||
LLError::FatalFunction mPrevFatalFunction;
|
||||
boost::scoped_ptr<LLLeapListener> mListener;
|
||||
};
|
||||
|
||||
// This must follow the declaration of LLLeapImpl, so it may as well be last.
|
||||
LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc)
|
||||
{
|
||||
// If caller is willing to permit exceptions, just instantiate.
|
||||
if (exc)
|
||||
return new LLLeapImpl(desc, plugin);
|
||||
|
||||
// Caller insists on suppressing LLLeap::Error. Very well, catch it.
|
||||
try
|
||||
{
|
||||
return new LLLeapImpl(desc, plugin);
|
||||
}
|
||||
catch (const LLLeap::Error&)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc)
|
||||
{
|
||||
// Use LLStringUtil::getTokens() to parse the command line
|
||||
return create(desc,
|
||||
LLStringUtil::getTokens(plugin,
|
||||
" \t\r\n", // drop_delims
|
||||
"", // no keep_delims
|
||||
"\"'", // either kind of quotes
|
||||
"\\"), // backslash escape
|
||||
exc);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @file llleap.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-20
|
||||
* @brief Class that implements "LLSD Event API Plugin"
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLLEAP_H)
|
||||
#define LL_LLLEAP_H
|
||||
|
||||
#include "llinstancetracker.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* LLSD Event API Plugin class. Because instances are managed by
|
||||
* LLInstanceTracker, you can instantiate LLLeap and forget the instance
|
||||
* unless you need it later. Each instance manages an LLProcess; when the
|
||||
* child process terminates, LLLeap deletes itself. We don't require a unique
|
||||
* LLInstanceTracker key.
|
||||
*
|
||||
* The fact that a given LLLeap instance vanishes when its child process
|
||||
* terminates makes it problematic to store an LLLeap* anywhere. Any stored
|
||||
* LLLeap* pointer should be validated before use by
|
||||
* LLLeap::getInstance(LLLeap*) (see LLInstanceTracker).
|
||||
*/
|
||||
class LL_COMMON_API LLLeap: public LLInstanceTracker<LLLeap>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Pass a brief string description, mostly for logging purposes. The desc
|
||||
* need not be unique, but obviously the clearer we can make it, the
|
||||
* easier these things will be to debug. The strings are the command line
|
||||
* used to launch the desired plugin process.
|
||||
*
|
||||
* Pass exc=false to suppress LLLeap::Error exception. Obviously in that
|
||||
* case the caller cannot discover the nature of the error, merely that an
|
||||
* error of some kind occurred (because create() returned NULL). Either
|
||||
* way, the error is logged.
|
||||
*/
|
||||
static LLLeap* create(const std::string& desc, const std::vector<std::string>& plugin,
|
||||
bool exc=true);
|
||||
|
||||
/**
|
||||
* Pass a brief string description, mostly for logging purposes. The desc
|
||||
* need not be unique, but obviously the clearer we can make it, the
|
||||
* easier these things will be to debug. Pass a command-line string
|
||||
* to launch the desired plugin process.
|
||||
*
|
||||
* Pass exc=false to suppress LLLeap::Error exception. Obviously in that
|
||||
* case the caller cannot discover the nature of the error, merely that an
|
||||
* error of some kind occurred (because create() returned NULL). Either
|
||||
* way, the error is logged.
|
||||
*/
|
||||
static LLLeap* create(const std::string& desc, const std::string& plugin,
|
||||
bool exc=true);
|
||||
|
||||
/**
|
||||
* Exception thrown for invalid create() arguments, e.g. no plugin
|
||||
* program. This is more resiliant than an LL_ERRS failure, because the
|
||||
* string(s) passed to create() might come from an external source. This
|
||||
* way the caller can catch LLLeap::Error and try to recover.
|
||||
*/
|
||||
struct Error: public std::runtime_error
|
||||
{
|
||||
Error(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
virtual ~LLLeap();
|
||||
|
||||
protected:
|
||||
LLLeap();
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLLEAP_H) */
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* @file llleaplistener.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-03-16
|
||||
* @brief Implementation for llleaplistener.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleaplistener.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "lluuid.h"
|
||||
#include "llsdutil.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* LEAP FEATURE STRINGS
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Implement "getFeatures" command. The LLSD map thus obtained is intended to
|
||||
* be machine-readable (read: easily-parsed, if parsing be necessary) and to
|
||||
* highlight the differences between this version of the LEAP protocol and
|
||||
* the baseline version. A client may thus determine whether or not the
|
||||
* running viewer supports some recent feature of interest.
|
||||
*
|
||||
* This method is defined at the top of this implementation file so it's easy
|
||||
* to find, easy to spot, easy to update as we enhance the LEAP protocol.
|
||||
*/
|
||||
/*static*/ LLSD LLLeapListener::getFeatures()
|
||||
{
|
||||
static LLSD features;
|
||||
if (features.isUndefined())
|
||||
{
|
||||
features = LLSD::emptyMap();
|
||||
|
||||
// This initial implementation IS the baseline LEAP protocol; thus the
|
||||
// set of differences is empty; thus features is initially empty.
|
||||
// features["featurename"] = "value";
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
LLLeapListener::LLLeapListener(const ConnectFunc& connect):
|
||||
// Each LEAP plugin has an instance of this listener. Make the command
|
||||
// pump name difficult for other such plugins to guess.
|
||||
LLEventAPI(LLUUID::generateNewID().asString(),
|
||||
"Operations relating to the LLSD Event API Plugin (LEAP) protocol"),
|
||||
mConnect(connect)
|
||||
{
|
||||
LLSD need_name(LLSDMap("name", LLSD()));
|
||||
add("newpump",
|
||||
"Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
|
||||
"If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
|
||||
"Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
|
||||
"Returns actual name in [\"name\"] (may be different if collision).",
|
||||
&LLLeapListener::newpump,
|
||||
need_name);
|
||||
add("killpump",
|
||||
"Delete LLEventPump [\"name\"] created by \"newpump\".\n"
|
||||
"Returns [\"status\"] boolean indicating whether such a pump existed.",
|
||||
&LLLeapListener::killpump,
|
||||
need_name);
|
||||
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
|
||||
add("listen",
|
||||
"Listen to an existing LLEventPump named [\"source\"], with listener name\n"
|
||||
"[\"listener\"].\n"
|
||||
"By default, send events on [\"source\"] to the plugin, decorated\n"
|
||||
"with [\"pump\"]=[\"source\"].\n"
|
||||
"If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n"
|
||||
"LLEventPump named [\"dest\"].\n"
|
||||
"Returns [\"status\"] boolean indicating whether the connection was made.",
|
||||
&LLLeapListener::listen,
|
||||
need_source_listener);
|
||||
add("stoplistening",
|
||||
"Disconnect a connection previously established by \"listen\".\n"
|
||||
"Pass same [\"source\"] and [\"listener\"] arguments.\n"
|
||||
"Returns [\"status\"] boolean indicating whether such a listener existed.",
|
||||
&LLLeapListener::stoplistening,
|
||||
need_source_listener);
|
||||
add("ping",
|
||||
"No arguments, just a round-trip sanity check.",
|
||||
&LLLeapListener::ping);
|
||||
add("getAPIs",
|
||||
"Enumerate all LLEventAPI instances by name and description.",
|
||||
&LLLeapListener::getAPIs);
|
||||
add("getAPI",
|
||||
"Get name, description, dispatch key and operations for LLEventAPI [\"api\"].",
|
||||
&LLLeapListener::getAPI,
|
||||
LLSD().with("api", LLSD()));
|
||||
add("getFeatures",
|
||||
"Return an LLSD map of feature strings (deltas from baseline LEAP protocol)",
|
||||
static_cast<void (LLLeapListener::*)(const LLSD&) const>(&LLLeapListener::getFeatures));
|
||||
add("getFeature",
|
||||
"Return the feature value with key [\"feature\"]",
|
||||
&LLLeapListener::getFeature,
|
||||
LLSD().with("feature", LLSD()));
|
||||
}
|
||||
|
||||
LLLeapListener::~LLLeapListener()
|
||||
{
|
||||
// We'd have stored a map of LLTempBoundListener instances, save that the
|
||||
// operation of inserting into a std::map necessarily copies the
|
||||
// value_type, and Bad Things would happen if you copied an
|
||||
// LLTempBoundListener. (Destruction of the original would disconnect the
|
||||
// listener, invalidating every stored connection.)
|
||||
BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
|
||||
{
|
||||
pair.second.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::newpump(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string name = request["name"];
|
||||
LLSD const & type = request["type"];
|
||||
|
||||
LLEventPump * new_pump = NULL;
|
||||
if (type.asString() == "LLEventQueue")
|
||||
{
|
||||
new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! (type.isUndefined() || type.asString() == "LLEventStream"))
|
||||
{
|
||||
reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
|
||||
}
|
||||
new_pump = new LLEventStream(name, true); // tweak name for uniqueness
|
||||
}
|
||||
|
||||
name = new_pump->getName();
|
||||
|
||||
mEventPumps.insert(name, new_pump);
|
||||
|
||||
// Now listen on this new pump with our plugin listener
|
||||
std::string myname("llleap");
|
||||
saveListener(name, myname, mConnect(*new_pump, myname));
|
||||
|
||||
reply["name"] = name;
|
||||
}
|
||||
|
||||
void LLLeapListener::killpump(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string name = request["name"];
|
||||
// success == (nonzero number of entries were erased)
|
||||
reply["status"] = bool(mEventPumps.erase(name));
|
||||
}
|
||||
|
||||
void LLLeapListener::listen(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string source_name = request["source"];
|
||||
std::string dest_name = request["dest"];
|
||||
std::string listener_name = request["listener"];
|
||||
|
||||
LLEventPump & source = LLEventPumps::instance().obtain(source_name);
|
||||
|
||||
reply["status"] = false;
|
||||
if (mListeners.find(ListenersMap::key_type(source_name, listener_name)) == mListeners.end())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request["dest"].isDefined())
|
||||
{
|
||||
// If we're asked to connect the "source" pump to a
|
||||
// specific "dest" pump, find dest pump and connect it.
|
||||
LLEventPump & dest = LLEventPumps::instance().obtain(dest_name);
|
||||
saveListener(source_name, listener_name,
|
||||
source.listen(listener_name,
|
||||
boost::bind(&LLEventPump::post, &dest, _1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// "dest" unspecified means to direct events on "source"
|
||||
// to our plugin listener.
|
||||
saveListener(source_name, listener_name, mConnect(source, listener_name));
|
||||
}
|
||||
reply["status"] = true;
|
||||
}
|
||||
catch (const LLEventPump::DupListenerName &)
|
||||
{
|
||||
// pass - status already set to false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::stoplistening(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string source_name = request["source"];
|
||||
std::string listener_name = request["listener"];
|
||||
|
||||
ListenersMap::iterator finder =
|
||||
mListeners.find(ListenersMap::key_type(source_name, listener_name));
|
||||
|
||||
reply["status"] = false;
|
||||
if(finder != mListeners.end())
|
||||
{
|
||||
reply["status"] = true;
|
||||
finder->second.disconnect();
|
||||
mListeners.erase(finder);
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::ping(const LLSD& request) const
|
||||
{
|
||||
// do nothing, default reply suffices
|
||||
Response(LLSD(), request);
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPIs(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()),
|
||||
eaend(LLEventAPI::endInstances());
|
||||
eai != eaend; ++eai)
|
||||
{
|
||||
LLSD info;
|
||||
info["desc"] = eai->getDesc();
|
||||
reply[eai->getName()] = info;
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPI(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
LLEventAPI* found = LLEventAPI::getInstance(request["api"]);
|
||||
if (found)
|
||||
{
|
||||
reply["name"] = found->getName();
|
||||
reply["desc"] = found->getDesc();
|
||||
reply["key"] = found->getDispatchKey();
|
||||
LLSD ops;
|
||||
for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
|
||||
oi != oend; ++oi)
|
||||
{
|
||||
ops.append(found->getMetadata(oi->first));
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::getFeatures(const LLSD& request) const
|
||||
{
|
||||
// Merely constructing and destroying a Response object suffices here.
|
||||
// Giving it a name would only produce fatal 'unreferenced variable'
|
||||
// warnings.
|
||||
Response(getFeatures(), request);
|
||||
}
|
||||
|
||||
void LLLeapListener::getFeature(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
LLSD::String feature_name(request["feature"]);
|
||||
LLSD features(getFeatures());
|
||||
if (features[feature_name].isDefined())
|
||||
{
|
||||
reply["feature"] = features[feature_name];
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::saveListener(const std::string& pump_name,
|
||||
const std::string& listener_name,
|
||||
const LLBoundListener& listener)
|
||||
{
|
||||
mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name),
|
||||
listener));
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @file llleaplistener.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-03-16
|
||||
* @brief LLEventAPI supporting LEAP plugins
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLLEAPLISTENER_H)
|
||||
#define LL_LLLEAPLISTENER_H
|
||||
|
||||
#include "lleventapi.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/ptr_container/ptr_map.hpp>
|
||||
|
||||
/// Listener class implementing LLLeap query/control operations.
|
||||
/// See https://jira.lindenlab.com/jira/browse/DEV-31978.
|
||||
class LLLeapListener: public LLEventAPI
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Decouple LLLeap by dependency injection. Certain LLLeapListener
|
||||
* operations must be able to cause LLLeap to listen on a specified
|
||||
* LLEventPump with the LLLeap listener that wraps incoming events in an
|
||||
* outer (pump=, data=) map and forwards them to the plugin. Very well,
|
||||
* define the signature for a function that will perform that, and make
|
||||
* our constructor accept such a function.
|
||||
*/
|
||||
typedef boost::function<LLBoundListener(LLEventPump&, const std::string& listener)>
|
||||
ConnectFunc;
|
||||
LLLeapListener(const ConnectFunc& connect);
|
||||
~LLLeapListener();
|
||||
|
||||
static LLSD getFeatures();
|
||||
|
||||
private:
|
||||
void newpump(const LLSD&);
|
||||
void killpump(const LLSD&);
|
||||
void listen(const LLSD&);
|
||||
void stoplistening(const LLSD&);
|
||||
void ping(const LLSD&) const;
|
||||
void getAPIs(const LLSD&) const;
|
||||
void getAPI(const LLSD&) const;
|
||||
void getFeatures(const LLSD&) const;
|
||||
void getFeature(const LLSD&) const;
|
||||
|
||||
void saveListener(const std::string& pump_name, const std::string& listener_name,
|
||||
const LLBoundListener& listener);
|
||||
|
||||
ConnectFunc mConnect;
|
||||
|
||||
// In theory, listen() could simply call the relevant LLEventPump's
|
||||
// listen() method, stoplistening() likewise. Lifespan issues make us
|
||||
// capture the LLBoundListener objects: when this object goes away, all
|
||||
// those listeners should be disconnected. But what if the client listens,
|
||||
// stops, listens again on the same LLEventPump with the same listener
|
||||
// name? Merely collecting LLBoundListeners wouldn't adequately track
|
||||
// that. So capture the latest LLBoundListener for this LLEventPump name
|
||||
// and listener name.
|
||||
typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
|
||||
ListenersMap mListeners;
|
||||
// Similar lifespan reasoning applies to LLEventPumps instantiated by
|
||||
// newpump() operations.
|
||||
typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
|
||||
EventPumpsMap mEventPumps;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLLEAPLISTENER_H) */
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,553 @@
|
|||
/**
|
||||
* @file llprocess.h
|
||||
* @brief Utility class for launching, terminating, and tracking child processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPROCESS_H
|
||||
#define LL_LLPROCESS_H
|
||||
|
||||
#include "llinitparam.h"
|
||||
#include "llsdparam.h"
|
||||
#include "apr_thread_proc.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/ptr_container/ptr_vector.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <iosfwd> // std::ostream
|
||||
#include <stdexcept>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h> // HANDLE (eye roll)
|
||||
#elif LL_LINUX
|
||||
#if defined(Status)
|
||||
#undef Status
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class LLEventPump;
|
||||
|
||||
class LLProcess;
|
||||
/// LLProcess instances are created on the heap by static factory methods and
|
||||
/// managed by ref-counted pointers.
|
||||
typedef boost::shared_ptr<LLProcess> LLProcessPtr;
|
||||
|
||||
/**
|
||||
* LLProcess handles launching an external process with specified command line
|
||||
* arguments. It also keeps track of whether the process is still running, and
|
||||
* can kill it if required.
|
||||
*
|
||||
* In discussing LLProcess, we use the term "parent" to refer to this process
|
||||
* (the process invoking LLProcess), versus "child" to refer to the process
|
||||
* spawned by LLProcess.
|
||||
*
|
||||
* LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an
|
||||
* LLProcess object's Status won't update until the next "mainloop" tick. For
|
||||
* instance, the Second Life viewer's main loop already posts to an
|
||||
* LLEventPump by that name once per iteration. See
|
||||
* indra/llcommon/tests/llprocess_test.cpp for an example of waiting for
|
||||
* child-process termination in a standalone test context.
|
||||
*/
|
||||
class LL_COMMON_API LLProcess: public boost::noncopyable
|
||||
{
|
||||
LOG_CLASS(LLProcess);
|
||||
public:
|
||||
/**
|
||||
* Specify what to pass for each of child stdin, stdout, stderr.
|
||||
* @see LLProcess::Params::files.
|
||||
*/
|
||||
struct FileParam: public LLInitParam::Block<FileParam>
|
||||
{
|
||||
/**
|
||||
* type of file handle to pass to child process
|
||||
*
|
||||
* - "" (default): let the child inherit the same file handle used by
|
||||
* this process. For instance, if passed as stdout, child stdout
|
||||
* will be interleaved with stdout from this process. In this case,
|
||||
* @a name is moot and should be left "".
|
||||
*
|
||||
* - "file": open an OS filesystem file with the specified @a name.
|
||||
* <i>Not yet implemented.</i>
|
||||
*
|
||||
* - "pipe" or "tpipe" or "npipe": depends on @a name
|
||||
*
|
||||
* - @a name.empty(): construct an OS pipe used only for this slot
|
||||
* of the forthcoming child process.
|
||||
*
|
||||
* - ! @a name.empty(): in a global registry, find or create (using
|
||||
* the specified @a name) an OS pipe. The point of the (purely
|
||||
* internal) @a name is that passing the same @a name in more than
|
||||
* one slot for a given LLProcess -- or for slots in different
|
||||
* LLProcess instances -- means the same pipe. For example, you
|
||||
* might pass the same @a name value as both stdout and stderr to
|
||||
* make the child process produce both on the same actual pipe. Or
|
||||
* you might pass the same @a name as the stdout for one LLProcess
|
||||
* and the stdin for another to connect the two child processes.
|
||||
* Use LLProcess::getPipeName() to generate a unique name
|
||||
* guaranteed not to already exist in the registry. <i>Not yet
|
||||
* implemented.</i>
|
||||
*
|
||||
* The difference between "pipe", "tpipe" and "npipe" is as follows.
|
||||
*
|
||||
* - "pipe": direct LLProcess to monitor the parent end of the pipe,
|
||||
* pumping nonblocking I/O every frame. The expectation (at least
|
||||
* for stdout or stderr) is that the caller will listen for
|
||||
* incoming data and consume it as it arrives. It's important not
|
||||
* to neglect such a pipe, because it's buffered in memory. If you
|
||||
* suspect the child may produce a great volume of output between
|
||||
* frames, consider directing the child to write to a filesystem
|
||||
* file instead, then read the file later.
|
||||
*
|
||||
* - "tpipe": do not engage LLProcess machinery to monitor the
|
||||
* parent end of the pipe. A "tpipe" is used only to connect
|
||||
* different child processes. As such, it makes little sense to
|
||||
* pass an empty @a name. <i>Not yet implemented.</i>
|
||||
*
|
||||
* - "npipe": like "tpipe", but use an OS named pipe with a
|
||||
* generated name. Note that @a name is the @em internal name of
|
||||
* the pipe in our global registry -- it doesn't necessarily have
|
||||
* anything to do with the pipe's name in the OS filesystem. Use
|
||||
* LLProcess::getPipeName() to obtain the named pipe's OS
|
||||
* filesystem name, e.g. to pass it as the @a name to another
|
||||
* LLProcess instance using @a type "file". This supports usage
|
||||
* like bash's <(subcommand...) or >(subcommand...)
|
||||
* constructs. <i>Not yet implemented.</i>
|
||||
*
|
||||
* In all cases the open mode (read, write) is determined by the child
|
||||
* slot you're filling. Child stdin means select the "read" end of a
|
||||
* pipe, or open a filesystem file for reading; child stdout or stderr
|
||||
* means select the "write" end of a pipe, or open a filesystem file
|
||||
* for writing.
|
||||
*
|
||||
* Confusion such as passing the same pipe as the stdin of two
|
||||
* processes (rather than stdout for one and stdin for the other) is
|
||||
* explicitly permitted: it's up to the caller to construct meaningful
|
||||
* LLProcess pipe graphs.
|
||||
*/
|
||||
Optional<std::string> type;
|
||||
Optional<std::string> name;
|
||||
|
||||
FileParam(const std::string& tp="", const std::string& nm=""):
|
||||
type("type"),
|
||||
name("name")
|
||||
{
|
||||
// If caller wants to specify values, use explicit assignment to
|
||||
// set them rather than initialization.
|
||||
if (! tp.empty()) type = tp;
|
||||
if (! nm.empty()) name = nm;
|
||||
}
|
||||
};
|
||||
|
||||
/// Param block definition
|
||||
struct Params: public LLInitParam::Block<Params>
|
||||
{
|
||||
Params():
|
||||
executable("executable"),
|
||||
args("args"),
|
||||
cwd("cwd"),
|
||||
autokill("autokill", true),
|
||||
files("files"),
|
||||
postend("postend"),
|
||||
desc("desc")
|
||||
{}
|
||||
|
||||
/// pathname of executable
|
||||
Mandatory<std::string> executable;
|
||||
/**
|
||||
* zero or more additional command-line arguments. Arguments are
|
||||
* passed through as exactly as we can manage, whitespace and all.
|
||||
* @note On Windows we manage this by implicitly double-quoting each
|
||||
* argument while assembling the command line.
|
||||
*/
|
||||
Multiple<std::string> args;
|
||||
/// current working directory, if need it changed
|
||||
Optional<std::string> cwd;
|
||||
/// implicitly kill process on destruction of LLProcess object
|
||||
/// (default true)
|
||||
Optional<bool> autokill;
|
||||
/**
|
||||
* Up to three FileParam items: for child stdin, stdout, stderr.
|
||||
* Passing two FileParam entries means default treatment for stderr,
|
||||
* and so forth.
|
||||
*
|
||||
* @note LLInitParam::Block permits usage like this:
|
||||
* @code
|
||||
* LLProcess::Params params;
|
||||
* ...
|
||||
* params.files
|
||||
* .add(LLProcess::FileParam()) // stdin
|
||||
* .add(LLProcess::FileParam().type("pipe") // stdout
|
||||
* .add(LLProcess::FileParam().type("file").name("error.log"));
|
||||
* @endcode
|
||||
*
|
||||
* @note While it's theoretically plausible to pass additional open
|
||||
* file handles to a child specifically written to expect them, our
|
||||
* underlying implementation doesn't yet support that.
|
||||
*/
|
||||
Multiple<FileParam, AtMost<3> > files;
|
||||
/**
|
||||
* On child-process termination, if this LLProcess object still
|
||||
* exists, post LLSD event to LLEventPump with specified name (default
|
||||
* no event). Event contains at least:
|
||||
*
|
||||
* - "id" as obtained from getProcessID()
|
||||
* - "desc" short string description of child (executable + pid)
|
||||
* - "state" @c state enum value, from Status.mState
|
||||
* - "data" if "state" is EXITED, exit code; if KILLED, on Posix,
|
||||
* signal number
|
||||
* - "string" English text describing "state" and "data" (e.g. "exited
|
||||
* with code 0")
|
||||
*/
|
||||
Optional<std::string> postend;
|
||||
/**
|
||||
* Description of child process for logging purposes. It need not be
|
||||
* unique; the logged description string will contain the PID as well.
|
||||
* If this is omitted, a description will be derived from the
|
||||
* executable name.
|
||||
*/
|
||||
Optional<std::string> desc;
|
||||
};
|
||||
typedef LLSDParamAdapter<Params> LLSDOrParams;
|
||||
|
||||
/**
|
||||
* Factory accepting either plain LLSD::Map or Params block.
|
||||
* MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
|
||||
*/
|
||||
static LLProcessPtr create(const LLSDOrParams& params);
|
||||
virtual ~LLProcess();
|
||||
|
||||
/// Is child process still running?
|
||||
bool isRunning() const;
|
||||
// static isRunning(LLProcessPtr), getStatus(LLProcessPtr),
|
||||
// getStatusString(LLProcessPtr), kill(LLProcessPtr) handle the case in
|
||||
// which the passed LLProcessPtr might be NULL (default-constructed).
|
||||
static bool isRunning(const LLProcessPtr&);
|
||||
|
||||
/**
|
||||
* State of child process
|
||||
*/
|
||||
enum state
|
||||
{
|
||||
UNSTARTED, ///< initial value, invisible to consumer
|
||||
RUNNING, ///< child process launched
|
||||
EXITED, ///< child process terminated voluntarily
|
||||
KILLED ///< child process terminated involuntarily
|
||||
};
|
||||
|
||||
/**
|
||||
* Status info
|
||||
*/
|
||||
struct Status
|
||||
{
|
||||
Status():
|
||||
mState(UNSTARTED),
|
||||
mData(0)
|
||||
{}
|
||||
|
||||
state mState; ///< @see state
|
||||
/**
|
||||
* - for mState == EXITED: mData is exit() code
|
||||
* - for mState == KILLED: mData is signal number (Posix)
|
||||
* - otherwise: mData is undefined
|
||||
*/
|
||||
int mData;
|
||||
};
|
||||
|
||||
/// Status query
|
||||
Status getStatus() const;
|
||||
static Status getStatus(const LLProcessPtr&);
|
||||
/// English Status string query, for logging etc.
|
||||
std::string getStatusString() const;
|
||||
static std::string getStatusString(const std::string& desc, const LLProcessPtr&);
|
||||
/// English Status string query for previously-captured Status
|
||||
std::string getStatusString(const Status& status) const;
|
||||
/// static English Status string query
|
||||
static std::string getStatusString(const std::string& desc, const Status& status);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(const std::string& who="");
|
||||
static bool kill(const LLProcessPtr& p, const std::string& who="");
|
||||
|
||||
#if LL_WINDOWS
|
||||
typedef int id; ///< as returned by getProcessID()
|
||||
typedef HANDLE handle; ///< as returned by getProcessHandle()
|
||||
#else
|
||||
typedef pid_t id;
|
||||
typedef pid_t handle;
|
||||
#endif
|
||||
/**
|
||||
* Get an int-like id value. This is primarily intended for a human reader
|
||||
* to differentiate processes.
|
||||
*/
|
||||
id getProcessID() const;
|
||||
/**
|
||||
* Get a "handle" of a kind that you might pass to platform-specific API
|
||||
* functions to engage features not directly supported by LLProcess.
|
||||
*/
|
||||
handle getProcessHandle() const;
|
||||
|
||||
/**
|
||||
* Test if a process (@c handle obtained from getProcessHandle()) is still
|
||||
* running. Return same nonzero @c handle value if still running, else
|
||||
* zero, so you can test it like a bool. But if you want to update a
|
||||
* stored variable as a side effect, you can write code like this:
|
||||
* @code
|
||||
* hchild = LLProcess::isRunning(hchild);
|
||||
* @endcode
|
||||
* @note This method is intended as a unit-test hook, not as the first of
|
||||
* a whole set of operations supported on freestanding @c handle values.
|
||||
* New functionality should be added as nonstatic members operating on
|
||||
* the same data as getProcessHandle().
|
||||
*
|
||||
* In particular, if child termination is detected by this static isRunning()
|
||||
* rather than by nonstatic isRunning(), the LLProcess object won't be
|
||||
* aware of the child's changed status and may encounter OS errors trying
|
||||
* to obtain it. This static isRunning() is only intended for after the
|
||||
* launching LLProcess object has been destroyed.
|
||||
*/
|
||||
static handle isRunning(handle, const std::string& desc="");
|
||||
|
||||
/// Provide symbolic access to child's file slots
|
||||
enum FILESLOT { STDIN=0, STDOUT=1, STDERR=2, NSLOTS=3 };
|
||||
|
||||
/**
|
||||
* For a pipe constructed with @a type "npipe", obtain the generated OS
|
||||
* filesystem name for the specified pipe. Otherwise returns the empty
|
||||
* string. @see LLProcess::FileParam::type
|
||||
*/
|
||||
std::string getPipeName(FILESLOT) const;
|
||||
|
||||
/// base of ReadPipe, WritePipe
|
||||
class LL_COMMON_API BasePipe
|
||||
{
|
||||
public:
|
||||
virtual ~BasePipe() = 0;
|
||||
|
||||
typedef std::size_t size_type;
|
||||
static const size_type npos;
|
||||
|
||||
/**
|
||||
* Get accumulated buffer length.
|
||||
*
|
||||
* For WritePipe, is there still pending data to send to child?
|
||||
*
|
||||
* For ReadPipe, we often need to refrain from actually reading the
|
||||
* std::istream returned by get_istream() until we've accumulated
|
||||
* enough data to make it worthwhile. For instance, if we're expecting
|
||||
* a number from the child, but the child happens to flush "12" before
|
||||
* emitting "3\n", get_istream() >> myint could return 12 rather than
|
||||
* 123!
|
||||
*/
|
||||
virtual size_type size() const = 0;
|
||||
};
|
||||
|
||||
/// As returned by getWritePipe() or getOptWritePipe()
|
||||
class WritePipe: public BasePipe
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get ostream& on which to write to child's stdin.
|
||||
*
|
||||
* @usage
|
||||
* @code
|
||||
* myProcess->getWritePipe().get_ostream() << "Hello, child!" << std::endl;
|
||||
* @endcode
|
||||
*/
|
||||
virtual std::ostream& get_ostream() = 0;
|
||||
};
|
||||
|
||||
/// As returned by getReadPipe() or getOptReadPipe()
|
||||
class ReadPipe: public BasePipe
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get istream& on which to read from child's stdout or stderr.
|
||||
*
|
||||
* @usage
|
||||
* @code
|
||||
* std::string stuff;
|
||||
* myProcess->getReadPipe().get_istream() >> stuff;
|
||||
* @endcode
|
||||
*
|
||||
* You should be sure in advance that the ReadPipe in question can
|
||||
* fill the request. @see getPump()
|
||||
*/
|
||||
virtual std::istream& get_istream() = 0;
|
||||
|
||||
/**
|
||||
* Like std::getline(get_istream(), line), but trims off trailing '\r'
|
||||
* to make calling code less platform-sensitive.
|
||||
*/
|
||||
virtual std::string getline() = 0;
|
||||
|
||||
/**
|
||||
* Like get_istream().read(buffer, n), but returns std::string rather
|
||||
* than requiring caller to construct a buffer, etc.
|
||||
*/
|
||||
virtual std::string read(size_type len) = 0;
|
||||
|
||||
/**
|
||||
* Peek at accumulated buffer data without consuming it. Optional
|
||||
* parameters give you substr() functionality.
|
||||
*
|
||||
* @note You can discard buffer data using get_istream().ignore(n).
|
||||
*/
|
||||
virtual std::string peek(size_type offset=0, size_type len=npos) const = 0;
|
||||
|
||||
/**
|
||||
* Detect presence of a substring (or char) in accumulated buffer data
|
||||
* without retrieving it. Optional offset allows you to search from
|
||||
* specified position.
|
||||
*/
|
||||
template <typename SEEK>
|
||||
bool contains(SEEK seek, size_type offset=0) const
|
||||
{ return find(seek, offset) != npos; }
|
||||
|
||||
/**
|
||||
* Search for a substring in accumulated buffer data without
|
||||
* retrieving it. Returns size_type position at which found, or npos
|
||||
* meaning not found. Optional offset allows you to search from
|
||||
* specified position.
|
||||
*/
|
||||
virtual size_type find(const std::string& seek, size_type offset=0) const = 0;
|
||||
|
||||
/**
|
||||
* Search for a char in accumulated buffer data without retrieving it.
|
||||
* Returns size_type position at which found, or npos meaning not
|
||||
* found. Optional offset allows you to search from specified
|
||||
* position.
|
||||
*/
|
||||
virtual size_type find(char seek, size_type offset=0) const = 0;
|
||||
|
||||
/**
|
||||
* Get LLEventPump& on which to listen for incoming data. The posted
|
||||
* LLSD::Map event will contain:
|
||||
*
|
||||
* - "data" part of pending data; see setLimit()
|
||||
* - "len" entire length of pending data, regardless of setLimit()
|
||||
* - "slot" this ReadPipe's FILESLOT, e.g. LLProcess::STDOUT
|
||||
* - "name" e.g. "stdout"
|
||||
* - "desc" e.g. "SLPlugin (pid) stdout"
|
||||
* - "eof" @c true means there no more data will arrive on this pipe,
|
||||
* therefore no more events on this pump
|
||||
*
|
||||
* If the child sends "abc", and this ReadPipe posts "data"="abc", but
|
||||
* you don't consume it by reading the std::istream returned by
|
||||
* get_istream(), and the child next sends "def", ReadPipe will post
|
||||
* "data"="abcdef".
|
||||
*/
|
||||
virtual LLEventPump& getPump() = 0;
|
||||
|
||||
/**
|
||||
* Set maximum length of buffer data that will be posted in the LLSD
|
||||
* announcing arrival of new data from the child. If you call
|
||||
* setLimit(5), and the child sends "abcdef", the LLSD event will
|
||||
* contain "data"="abcde". However, you may still read the entire
|
||||
* "abcdef" from get_istream(): this limit affects only the size of
|
||||
* the data posted with the LLSD event. If you don't call this method,
|
||||
* @em no data will be posted: the default is 0 bytes.
|
||||
*/
|
||||
virtual void setLimit(size_type limit) = 0;
|
||||
|
||||
/**
|
||||
* Query the current setLimit() limit.
|
||||
*/
|
||||
virtual size_type getLimit() const = 0;
|
||||
};
|
||||
|
||||
/// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
|
||||
/// create a pipe at the corresponding FILESLOT.
|
||||
struct NoPipe: public std::runtime_error
|
||||
{
|
||||
NoPipe(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a reference to the (only) WritePipe for this LLProcess. @a slot, if
|
||||
* specified, must be STDIN. Throws NoPipe if you did not request a "pipe"
|
||||
* for child stdin. Use this method when you know how you created the
|
||||
* LLProcess in hand.
|
||||
*/
|
||||
WritePipe& getWritePipe(FILESLOT slot=STDIN);
|
||||
|
||||
/**
|
||||
* Get a boost::optional<WritePipe&> to the (only) WritePipe for this
|
||||
* LLProcess. @a slot, if specified, must be STDIN. The return value is
|
||||
* empty if you did not request a "pipe" for child stdin. Use this method
|
||||
* for inspecting an LLProcess you did not create.
|
||||
*/
|
||||
boost::optional<WritePipe&> getOptWritePipe(FILESLOT slot=STDIN);
|
||||
|
||||
/**
|
||||
* Get a reference to one of the ReadPipes for this LLProcess. @a slot, if
|
||||
* specified, must be STDOUT or STDERR. Throws NoPipe if you did not
|
||||
* request a "pipe" for child stdout or stderr. Use this method when you
|
||||
* know how you created the LLProcess in hand.
|
||||
*/
|
||||
ReadPipe& getReadPipe(FILESLOT slot);
|
||||
|
||||
/**
|
||||
* Get a boost::optional<ReadPipe&> to one of the ReadPipes for this
|
||||
* LLProcess. @a slot, if specified, must be STDOUT or STDERR. The return
|
||||
* value is empty if you did not request a "pipe" for child stdout or
|
||||
* stderr. Use this method for inspecting an LLProcess you did not create.
|
||||
*/
|
||||
boost::optional<ReadPipe&> getOptReadPipe(FILESLOT slot);
|
||||
|
||||
/// little utilities that really should already be somewhere else in the
|
||||
/// code base
|
||||
static std::string basename(const std::string& path);
|
||||
static std::string getline(std::istream&);
|
||||
|
||||
private:
|
||||
/// constructor is private: use create() instead
|
||||
LLProcess(const LLSDOrParams& params);
|
||||
void autokill();
|
||||
// Classic-C-style APR callback
|
||||
static void status_callback(int reason, void* data, int status);
|
||||
// Object-oriented callback
|
||||
void handle_status(int reason, int status);
|
||||
// implementation for get[Opt][Read|Write]Pipe()
|
||||
template <class PIPETYPE>
|
||||
PIPETYPE& getPipe(FILESLOT slot);
|
||||
template <class PIPETYPE>
|
||||
boost::optional<PIPETYPE&> getOptPipe(FILESLOT slot);
|
||||
template <class PIPETYPE>
|
||||
PIPETYPE* getPipePtr(std::string& error, FILESLOT slot);
|
||||
|
||||
std::string mDesc;
|
||||
std::string mPostend;
|
||||
apr_proc_t mProcess;
|
||||
bool mAutokill;
|
||||
Status mStatus;
|
||||
// explicitly want this ptr_vector to be able to store NULLs
|
||||
typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
|
||||
PipeVector mPipes;
|
||||
};
|
||||
|
||||
/// for logging
|
||||
LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&);
|
||||
|
||||
#endif // LL_LLPROCESS_H
|
||||
|
|
@ -1,357 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.cpp
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llprocesslauncher.h"
|
||||
|
||||
#include <iostream>
|
||||
#if LL_DARWIN || LL_LINUX
|
||||
// not required or present on Win32
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
LLProcessLauncher::LLProcessLauncher()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
mProcessHandle = 0;
|
||||
#else
|
||||
mProcessID = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
LLProcessLauncher::~LLProcessLauncher()
|
||||
{
|
||||
kill();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setExecutable(const std::string &executable)
|
||||
{
|
||||
mExecutable = executable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setWorkingDirectory(const std::string &dir)
|
||||
{
|
||||
mWorkingDir = dir;
|
||||
}
|
||||
|
||||
const std::string& LLProcessLauncher::getExecutable() const
|
||||
{
|
||||
return mExecutable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::clearArguments()
|
||||
{
|
||||
mLaunchArguments.clear();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::addArgument(const std::string &arg)
|
||||
{
|
||||
mLaunchArguments.push_back(arg);
|
||||
}
|
||||
|
||||
void LLProcessLauncher::addArgument(const char *arg)
|
||||
{
|
||||
mLaunchArguments.push_back(std::string(arg));
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
|
||||
PROCESS_INFORMATION pinfo;
|
||||
STARTUPINFOA sinfo;
|
||||
memset(&sinfo, 0, sizeof(sinfo));
|
||||
|
||||
std::string args = mExecutable;
|
||||
for(int i = 0; i < (int)mLaunchArguments.size(); i++)
|
||||
{
|
||||
args += " ";
|
||||
args += mLaunchArguments[i];
|
||||
}
|
||||
|
||||
// So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
|
||||
char *args2 = new char[args.size() + 1];
|
||||
strcpy(args2, args.c_str());
|
||||
|
||||
const char * working_directory = 0;
|
||||
if(!mWorkingDir.empty()) working_directory = mWorkingDir.c_str();
|
||||
if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
|
||||
{
|
||||
result = GetLastError();
|
||||
|
||||
LPTSTR error_str = 0;
|
||||
if(
|
||||
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL,
|
||||
result,
|
||||
0,
|
||||
(LPTSTR)&error_str,
|
||||
0,
|
||||
NULL)
|
||||
!= 0)
|
||||
{
|
||||
char message[256];
|
||||
wcstombs(message, error_str, 256);
|
||||
message[255] = 0;
|
||||
llwarns << "CreateProcessA failed: " << message << llendl;
|
||||
LocalFree(error_str);
|
||||
}
|
||||
|
||||
if(result == 0)
|
||||
{
|
||||
// Make absolutely certain we return a non-zero value on failure.
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
|
||||
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
|
||||
mProcessHandle = pinfo.hProcess;
|
||||
CloseHandle(pinfo.hThread); // stops leaks - nothing else
|
||||
}
|
||||
|
||||
delete[] args2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
if(mProcessHandle != 0)
|
||||
{
|
||||
DWORD waitresult = WaitForSingleObject(mProcessHandle, 0);
|
||||
if(waitresult == WAIT_OBJECT_0)
|
||||
{
|
||||
// the process has completed.
|
||||
mProcessHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (mProcessHandle != 0);
|
||||
}
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessHandle != 0)
|
||||
{
|
||||
TerminateProcess(mProcessHandle,0);
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Forget about the process
|
||||
mProcessHandle = 0;
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// No actions necessary on Windows.
|
||||
}
|
||||
|
||||
#else // Mac and linux
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
static std::list<pid_t> sZombies;
|
||||
|
||||
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
|
||||
static bool reap_pid(pid_t pid)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
|
||||
if(wait_result == pid)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if(wait_result == -1)
|
||||
{
|
||||
if(errno == ECHILD)
|
||||
{
|
||||
// No such process -- this may mean we're ignoring SIGCHILD.
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
int current_wd = -1;
|
||||
|
||||
// create an argv vector for the child process
|
||||
const char ** fake_argv = new const char *[mLaunchArguments.size() + 2]; // 1 for the executable path, 1 for the NULL terminator
|
||||
|
||||
int i = 0;
|
||||
|
||||
// add the executable path
|
||||
fake_argv[i++] = mExecutable.c_str();
|
||||
|
||||
// and any arguments
|
||||
for(int j=0; j < mLaunchArguments.size(); j++)
|
||||
fake_argv[i++] = mLaunchArguments[j].c_str();
|
||||
|
||||
// terminate with a null pointer
|
||||
fake_argv[i] = NULL;
|
||||
|
||||
if(!mWorkingDir.empty())
|
||||
{
|
||||
// save the current working directory
|
||||
current_wd = ::open(".", O_RDONLY);
|
||||
|
||||
// and change to the one the child will be executed in
|
||||
if (::chdir(mWorkingDir.c_str()))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
}
|
||||
|
||||
// flush all buffers before the child inherits them
|
||||
::fflush(NULL);
|
||||
|
||||
pid_t id = vfork();
|
||||
if(id == 0)
|
||||
{
|
||||
// child process
|
||||
|
||||
::execv(mExecutable.c_str(), (char * const *)fake_argv);
|
||||
|
||||
// If we reach this point, the exec failed.
|
||||
// Use _exit() instead of exit() per the vfork man page.
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
// parent process
|
||||
|
||||
if(current_wd >= 0)
|
||||
{
|
||||
// restore the previous working directory
|
||||
if (::fchdir(current_wd))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
::close(current_wd);
|
||||
}
|
||||
|
||||
delete[] fake_argv;
|
||||
|
||||
mProcessID = id;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// Check whether the process has exited, and reap it if it has.
|
||||
if(reap_pid(mProcessID))
|
||||
{
|
||||
// the process has exited.
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (mProcessID != 0);
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// Try to kill the process. We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result.
|
||||
(void)::kill(mProcessID, SIGTERM);
|
||||
|
||||
// This will have the side-effect of reaping the zombie if the process has exited.
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Disassociate the process from this object
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// We may still need to reap the process's zombie eventually
|
||||
sZombies.push_back(mProcessID);
|
||||
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// Attempt to real all saved process ID's.
|
||||
|
||||
std::list<pid_t>::iterator iter = sZombies.begin();
|
||||
while(iter != sZombies.end())
|
||||
{
|
||||
if(reap_pid(*iter))
|
||||
{
|
||||
iter = sZombies.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.h
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPROCESSLAUNCHER_H
|
||||
#define LL_LLPROCESSLAUNCHER_H
|
||||
|
||||
#if LL_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
LLProcessLauncher handles launching external processes with specified command line arguments.
|
||||
It also keeps track of whether the process is still running, and can kill it if required.
|
||||
*/
|
||||
|
||||
class LL_COMMON_API LLProcessLauncher
|
||||
{
|
||||
LOG_CLASS(LLProcessLauncher);
|
||||
public:
|
||||
LLProcessLauncher();
|
||||
virtual ~LLProcessLauncher();
|
||||
|
||||
void setExecutable(const std::string &executable);
|
||||
void setWorkingDirectory(const std::string &dir);
|
||||
|
||||
const std::string& getExecutable() const;
|
||||
|
||||
void clearArguments();
|
||||
void addArgument(const std::string &arg);
|
||||
void addArgument(const char *arg);
|
||||
|
||||
int launch(void);
|
||||
bool isRunning(void);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(void);
|
||||
|
||||
// Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted.
|
||||
// Normally, the destructor will attempt to kill the process and wait for termination.
|
||||
// This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits.
|
||||
void orphan(void);
|
||||
|
||||
// This needs to be called periodically on Mac/Linux to clean up zombie processes.
|
||||
static void reap(void);
|
||||
|
||||
// Accessors for platform-specific process ID
|
||||
#if LL_WINDOWS
|
||||
HANDLE getProcessHandle() { return mProcessHandle; };
|
||||
#else
|
||||
pid_t getProcessID() { return mProcessID; };
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::string mExecutable;
|
||||
std::string mWorkingDir;
|
||||
std::vector<std::string> mLaunchArguments;
|
||||
|
||||
#if LL_WINDOWS
|
||||
HANDLE mProcessHandle;
|
||||
#else
|
||||
pid_t mProcessID;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // LL_LLPROCESSLAUNCHER_H
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include <boost/type_traits.hpp>
|
||||
#include "llsingleton.h"
|
||||
#include "lltypeinfolookup.h"
|
||||
|
||||
template <typename T>
|
||||
class LLRegistryDefaultComparator
|
||||
|
|
@ -38,6 +39,24 @@ class LLRegistryDefaultComparator
|
|||
bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; }
|
||||
};
|
||||
|
||||
template <typename KEY, typename VALUE>
|
||||
struct LLRegistryMapSelector
|
||||
{
|
||||
typedef std::map<KEY, VALUE> type;
|
||||
};
|
||||
|
||||
template <typename VALUE>
|
||||
struct LLRegistryMapSelector<std::type_info*, VALUE>
|
||||
{
|
||||
typedef LLTypeInfoLookup<VALUE> type;
|
||||
};
|
||||
|
||||
template <typename VALUE>
|
||||
struct LLRegistryMapSelector<const std::type_info*, VALUE>
|
||||
{
|
||||
typedef LLTypeInfoLookup<VALUE> type;
|
||||
};
|
||||
|
||||
template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
|
||||
class LLRegistry
|
||||
{
|
||||
|
|
@ -53,7 +72,7 @@ public:
|
|||
{
|
||||
friend class LLRegistry<KEY, VALUE, COMPARATOR>;
|
||||
public:
|
||||
typedef typename std::map<KEY, VALUE> registry_map_t;
|
||||
typedef typename LLRegistryMapSelector<KEY, VALUE>::type registry_map_t;
|
||||
|
||||
bool add(ref_const_key_t key, ref_const_value_t value)
|
||||
{
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
#include "llinitparam.h"
|
||||
#include "boost/function.hpp"
|
||||
|
||||
struct LLParamSDParserUtilities
|
||||
struct LL_COMMON_API LLParamSDParserUtilities
|
||||
{
|
||||
static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range);
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ struct LLParamSDParserUtilities
|
|||
static void readSDValues(read_sd_cb_t cb, const LLSD& sd);
|
||||
};
|
||||
|
||||
class LLParamSDParser
|
||||
class LL_COMMON_API LLParamSDParser
|
||||
: public LLInitParam::Parser
|
||||
{
|
||||
LOG_CLASS(LLParamSDParser);
|
||||
|
|
@ -92,7 +92,7 @@ private:
|
|||
};
|
||||
|
||||
|
||||
extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR;
|
||||
extern LL_COMMON_API LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR;
|
||||
template<typename T>
|
||||
class LLSDParamAdapter : public T
|
||||
{
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* @file llsortedvector.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-04-08
|
||||
* @brief LLSortedVector class wraps a vector that we maintain in sorted
|
||||
* order so we can perform binary-search lookups.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLSORTEDVECTOR_H)
|
||||
#define LL_LLSORTEDVECTOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* LLSortedVector contains a std::vector<std::pair> that we keep sorted on the
|
||||
* first of the pair. This makes insertion somewhat more expensive than simple
|
||||
* std::vector::push_back(), but allows us to use binary search for lookups.
|
||||
* It's intended for small aggregates where lookup is far more performance-
|
||||
* critical than insertion; in such cases a binary search on a small, sorted
|
||||
* std::vector can be more performant than a std::map lookup.
|
||||
*/
|
||||
template <typename KEY, typename VALUE>
|
||||
class LLSortedVector
|
||||
{
|
||||
public:
|
||||
typedef LLSortedVector<KEY, VALUE> self;
|
||||
typedef KEY key_type;
|
||||
typedef VALUE mapped_type;
|
||||
typedef std::pair<key_type, mapped_type> value_type;
|
||||
typedef std::vector<value_type> PairVector;
|
||||
typedef typename PairVector::iterator iterator;
|
||||
typedef typename PairVector::const_iterator const_iterator;
|
||||
|
||||
/// Empty
|
||||
LLSortedVector() {}
|
||||
|
||||
/// Fixed initial size
|
||||
LLSortedVector(std::size_t size):
|
||||
mVector(size)
|
||||
{}
|
||||
|
||||
/// Bulk load
|
||||
template <typename ITER>
|
||||
LLSortedVector(ITER begin, ITER end):
|
||||
mVector(begin, end)
|
||||
{
|
||||
// Allow caller to dump in a bunch of (pairs convertible to)
|
||||
// value_type if desired, but make sure we sort afterwards.
|
||||
std::sort(mVector.begin(), mVector.end());
|
||||
}
|
||||
|
||||
/// insert(key, value)
|
||||
std::pair<iterator, bool> insert(const key_type& key, const mapped_type& value)
|
||||
{
|
||||
return insert(value_type(key, value));
|
||||
}
|
||||
|
||||
/// insert(value_type)
|
||||
std::pair<iterator, bool> insert(const value_type& pair)
|
||||
{
|
||||
typedef std::pair<iterator, bool> iterbool;
|
||||
iterator found = std::lower_bound(mVector.begin(), mVector.end(), pair,
|
||||
less<value_type>());
|
||||
// have to check for end() before it's even valid to dereference
|
||||
if (found == mVector.end())
|
||||
{
|
||||
std::size_t index(mVector.size());
|
||||
mVector.push_back(pair);
|
||||
// don't forget that push_back() invalidates 'found'
|
||||
return iterbool(mVector.begin() + index, true);
|
||||
}
|
||||
if (found->first == pair.first)
|
||||
{
|
||||
return iterbool(found, false);
|
||||
}
|
||||
// remember that insert() invalidates 'found' -- save index
|
||||
std::size_t index(found - mVector.begin());
|
||||
mVector.insert(found, pair);
|
||||
// okay, convert from index back to iterator
|
||||
return iterbool(mVector.begin() + index, true);
|
||||
}
|
||||
|
||||
iterator begin() { return mVector.begin(); }
|
||||
iterator end() { return mVector.end(); }
|
||||
const_iterator begin() const { return mVector.begin(); }
|
||||
const_iterator end() const { return mVector.end(); }
|
||||
|
||||
bool empty() const { return mVector.empty(); }
|
||||
std::size_t size() const { return mVector.size(); }
|
||||
|
||||
/// find
|
||||
iterator find(const key_type& key)
|
||||
{
|
||||
iterator found = std::lower_bound(mVector.begin(), mVector.end(),
|
||||
value_type(key, mapped_type()),
|
||||
less<value_type>());
|
||||
if (found == mVector.end() || found->first != key)
|
||||
return mVector.end();
|
||||
return found;
|
||||
}
|
||||
|
||||
const_iterator find(const key_type& key) const
|
||||
{
|
||||
return const_cast<self*>(this)->find(key);
|
||||
}
|
||||
|
||||
private:
|
||||
// Define our own 'less' comparator so we can specialize without messing
|
||||
// with std::less.
|
||||
template <typename T>
|
||||
struct less: public std::less<T> {};
|
||||
|
||||
// Specialize 'less' for an LLSortedVector::value_type involving
|
||||
// std::type_info*. This is one of LLSortedVector's foremost use cases. We
|
||||
// specialize 'less' rather than just defining a specific comparator
|
||||
// because LLSortedVector should be usable for other key_types as well.
|
||||
template <typename T>
|
||||
struct less< std::pair<std::type_info*, T> >:
|
||||
public std::binary_function<std::pair<std::type_info*, T>,
|
||||
std::pair<std::type_info*, T>,
|
||||
bool>
|
||||
{
|
||||
bool operator()(const std::pair<std::type_info*, T>& lhs,
|
||||
const std::pair<std::type_info*, T>& rhs) const
|
||||
{
|
||||
return lhs.first->before(*rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
// Same as above, but with const std::type_info*.
|
||||
template <typename T>
|
||||
struct less< std::pair<const std::type_info*, T> >:
|
||||
public std::binary_function<std::pair<const std::type_info*, T>,
|
||||
std::pair<const std::type_info*, T>,
|
||||
bool>
|
||||
{
|
||||
bool operator()(const std::pair<const std::type_info*, T>& lhs,
|
||||
const std::pair<const std::type_info*, T>& rhs) const
|
||||
{
|
||||
return lhs.first->before(*rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
PairVector mVector;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLSORTEDVECTOR_H) */
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @file llstreamqueue.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-05
|
||||
* @brief Implementation for llstreamqueue.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llstreamqueue.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
|
||||
// As of this writing, llstreamqueue.h is entirely template-based, therefore
|
||||
// we don't strictly need a corresponding .cpp file. However, our CMake test
|
||||
// macro assumes one. Here it is.
|
||||
bool llstreamqueue_cpp_ignored = true;
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* @file llstreamqueue.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-04
|
||||
* @brief Definition of LLStreamQueue
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLSTREAMQUEUE_H)
|
||||
#define LL_LLSTREAMQUEUE_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <iosfwd> // std::streamsize
|
||||
#include <boost/iostreams/categories.hpp>
|
||||
|
||||
/**
|
||||
* This class is a growable buffer between a producer and consumer. It serves
|
||||
* as a queue usable with Boost.Iostreams -- hence, a "stream queue."
|
||||
*
|
||||
* This is especially useful for buffering nonblocking I/O. For instance, we
|
||||
* want application logic to be able to serialize LLSD to a std::ostream. We
|
||||
* may write more data than the destination pipe can handle all at once, but
|
||||
* it's imperative NOT to block the application-level serialization call. So
|
||||
* we buffer it instead. Successive frames can try nonblocking writes to the
|
||||
* destination pipe until all buffered data has been sent.
|
||||
*
|
||||
* Similarly, we want application logic be able to deserialize LLSD from a
|
||||
* std::istream. Again, we must not block that deserialize call waiting for
|
||||
* more data to arrive from the input pipe! Instead we build up a buffer over
|
||||
* a number of frames, using successive nonblocking reads, until we have
|
||||
* "enough" data to be able to present it through a std::istream.
|
||||
*
|
||||
* @note The use cases for this class overlap somewhat with those for the
|
||||
* LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This
|
||||
* class has two virtues over the older machinery:
|
||||
*
|
||||
* # It's vastly simpler -- way fewer concepts. It's not clear to me whether
|
||||
* there were ever LLIOPipe/etc. use cases that demanded all the fanciness
|
||||
* rolled in, or whether they were simply overdesigned. In any case, no
|
||||
* remaining Lindens will admit to familiarity with those classes -- and
|
||||
* they're sufficiently obtuse that it would take considerable learning
|
||||
* curve to figure out how to use them properly. The bottom line is that
|
||||
* current management is not keen on any more engineers climbing that curve.
|
||||
* # This class is designed around available components such as std::string,
|
||||
* std::list, Boost.Iostreams. There's less proprietary code.
|
||||
*/
|
||||
template <typename Ch>
|
||||
class LLGenericStreamQueue
|
||||
{
|
||||
public:
|
||||
LLGenericStreamQueue():
|
||||
mSize(0),
|
||||
mClosed(false)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Boost.Iostreams Source Device facade for use with other Boost.Iostreams
|
||||
* functionality. LLGenericStreamQueue doesn't quite fit any of the Boost
|
||||
* 1.48 Iostreams concepts; instead it behaves as both a Sink and a
|
||||
* Source. This is its Source facade.
|
||||
*/
|
||||
struct Source
|
||||
{
|
||||
typedef Ch char_type;
|
||||
typedef boost::iostreams::source_tag category;
|
||||
|
||||
/// Bind the underlying LLGenericStreamQueue
|
||||
Source(LLGenericStreamQueue& sq):
|
||||
mStreamQueue(sq)
|
||||
{}
|
||||
|
||||
// Read up to n characters from the underlying data source into the
|
||||
// buffer s, returning the number of characters read; return -1 to
|
||||
// indicate EOF
|
||||
std::streamsize read(Ch* s, std::streamsize n)
|
||||
{
|
||||
return mStreamQueue.read(s, n);
|
||||
}
|
||||
|
||||
LLGenericStreamQueue& mStreamQueue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Boost.Iostreams Sink Device facade for use with other Boost.Iostreams
|
||||
* functionality. LLGenericStreamQueue doesn't quite fit any of the Boost
|
||||
* 1.48 Iostreams concepts; instead it behaves as both a Sink and a
|
||||
* Source. This is its Sink facade.
|
||||
*/
|
||||
struct Sink
|
||||
{
|
||||
typedef Ch char_type;
|
||||
typedef boost::iostreams::sink_tag category;
|
||||
|
||||
/// Bind the underlying LLGenericStreamQueue
|
||||
Sink(LLGenericStreamQueue& sq):
|
||||
mStreamQueue(sq)
|
||||
{}
|
||||
|
||||
/// Write up to n characters from the buffer s to the output sequence,
|
||||
/// returning the number of characters written
|
||||
std::streamsize write(const Ch* s, std::streamsize n)
|
||||
{
|
||||
return mStreamQueue.write(s, n);
|
||||
}
|
||||
|
||||
/// Send EOF to consumer
|
||||
void close()
|
||||
{
|
||||
mStreamQueue.close();
|
||||
}
|
||||
|
||||
LLGenericStreamQueue& mStreamQueue;
|
||||
};
|
||||
|
||||
/// Present Boost.Iostreams Source facade
|
||||
Source asSource() { return Source(*this); }
|
||||
/// Present Boost.Iostreams Sink facade
|
||||
Sink asSink() { return Sink(*this); }
|
||||
|
||||
/// append data to buffer
|
||||
std::streamsize write(const Ch* s, std::streamsize n)
|
||||
{
|
||||
// Unclear how often we might be asked to write 0 bytes -- perhaps a
|
||||
// naive caller responding to an unready nonblocking read. But if we
|
||||
// do get such a call, don't add a completely empty BufferList entry.
|
||||
if (n == 0)
|
||||
return n;
|
||||
// We could implement this using a single std::string object, a la
|
||||
// ostringstream. But the trouble with appending to a string is that
|
||||
// you might have to recopy all previous contents to grow its size. If
|
||||
// we want this to scale to large data volumes, better to allocate
|
||||
// individual pieces.
|
||||
mBuffer.push_back(string(s, n));
|
||||
mSize += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform this LLGenericStreamQueue that no further data are forthcoming.
|
||||
* For our purposes, close() is strictly a producer-side operation;
|
||||
* there's little point in closing the consumer side.
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
mClosed = true;
|
||||
}
|
||||
|
||||
/// consume data from buffer
|
||||
std::streamsize read(Ch* s, std::streamsize n)
|
||||
{
|
||||
// read() is actually a convenience method for peek() followed by
|
||||
// skip().
|
||||
std::streamsize got(peek(s, n));
|
||||
// We can only skip() as many characters as we can peek(); ignore
|
||||
// skip() return here.
|
||||
skip(n);
|
||||
return got;
|
||||
}
|
||||
|
||||
/// Retrieve data from buffer without consuming. Like read(), return -1 on
|
||||
/// EOF.
|
||||
std::streamsize peek(Ch* s, std::streamsize n) const;
|
||||
|
||||
/// Consume data from buffer without retrieving. Unlike read() and peek(),
|
||||
/// at EOF we simply skip 0 characters.
|
||||
std::streamsize skip(std::streamsize n);
|
||||
|
||||
/// How many characters do we currently have buffered?
|
||||
std::streamsize size() const
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::basic_string<Ch> string;
|
||||
typedef std::list<string> BufferList;
|
||||
BufferList mBuffer;
|
||||
std::streamsize mSize;
|
||||
bool mClosed;
|
||||
};
|
||||
|
||||
template <typename Ch>
|
||||
std::streamsize LLGenericStreamQueue<Ch>::peek(Ch* s, std::streamsize n) const
|
||||
{
|
||||
// Here we may have to build up 'n' characters from an arbitrary
|
||||
// number of individual BufferList entries.
|
||||
typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end());
|
||||
// Indicate EOF if producer has closed the pipe AND we've exhausted
|
||||
// all previously-buffered data.
|
||||
if (mClosed && bli == blend)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
// Here either producer hasn't yet closed, or we haven't yet exhausted
|
||||
// remaining data.
|
||||
std::streamsize needed(n), got(0);
|
||||
// Loop until either we run out of BufferList entries or we've
|
||||
// completely satisfied the request.
|
||||
for ( ; bli != blend && needed; ++bli)
|
||||
{
|
||||
std::streamsize chunk(std::min(needed, std::streamsize(bli->length())));
|
||||
std::copy(bli->begin(), bli->begin() + chunk, s);
|
||||
needed -= chunk;
|
||||
s += chunk;
|
||||
got += chunk;
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
template <typename Ch>
|
||||
std::streamsize LLGenericStreamQueue<Ch>::skip(std::streamsize n)
|
||||
{
|
||||
typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end());
|
||||
std::streamsize toskip(n), skipped(0);
|
||||
while (bli != blend && toskip >= bli->length())
|
||||
{
|
||||
std::streamsize chunk(bli->length());
|
||||
typename BufferList::iterator zap(bli++);
|
||||
mBuffer.erase(zap);
|
||||
mSize -= chunk;
|
||||
toskip -= chunk;
|
||||
skipped += chunk;
|
||||
}
|
||||
if (bli != blend && toskip)
|
||||
{
|
||||
bli->erase(bli->begin(), bli->begin() + toskip);
|
||||
mSize -= toskip;
|
||||
skipped += toskip;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
typedef LLGenericStreamQueue<char> LLStreamQueue;
|
||||
typedef LLGenericStreamQueue<wchar_t> LLWStreamQueue;
|
||||
|
||||
#endif /* ! defined(LL_LLSTREAMQUEUE_H) */
|
||||
|
|
@ -912,22 +912,24 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions);
|
|||
template<>
|
||||
void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims)
|
||||
{
|
||||
std::string currToken;
|
||||
std::string::size_type begIdx, endIdx;
|
||||
|
||||
begIdx = instr.find_first_not_of (delims);
|
||||
while (begIdx != std::string::npos)
|
||||
// Starting at offset 0, scan forward for the next non-delimiter. We're
|
||||
// done when the only characters left in 'instr' are delimiters.
|
||||
for (std::string::size_type begIdx, endIdx = 0;
|
||||
(begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; )
|
||||
{
|
||||
// Found a non-delimiter. After that, find the next delimiter.
|
||||
endIdx = instr.find_first_of (delims, begIdx);
|
||||
if (endIdx == std::string::npos)
|
||||
{
|
||||
// No more delimiters: this token extends to the end of the string.
|
||||
endIdx = instr.length();
|
||||
}
|
||||
|
||||
currToken = instr.substr(begIdx, endIdx - begIdx);
|
||||
// extract the token between begIdx and endIdx; substr() needs length
|
||||
std::string currToken(instr.substr(begIdx, endIdx - begIdx));
|
||||
LLStringUtil::trim (currToken);
|
||||
tokens.push_back(currToken);
|
||||
begIdx = instr.find_first_not_of (delims, endIdx);
|
||||
// next scan past delimiters starts at endIdx
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#if LL_SOLARIS
|
||||
// stricmp and strnicmp do not exist on Solaris:
|
||||
|
|
@ -237,40 +238,77 @@ private:
|
|||
static std::string sLocale;
|
||||
|
||||
public:
|
||||
typedef typename std::basic_string<T>::size_type size_type;
|
||||
typedef std::basic_string<T> string_type;
|
||||
typedef typename string_type::size_type size_type;
|
||||
|
||||
public:
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Utility functions that operate on std::strings
|
||||
|
||||
static const std::basic_string<T> null;
|
||||
static const string_type null;
|
||||
|
||||
typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
|
||||
LL_COMMON_API static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims);
|
||||
LL_COMMON_API static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals);
|
||||
LL_COMMON_API static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch);
|
||||
LL_COMMON_API static S32 format(std::basic_string<T>& s, const format_map_t& substitutions);
|
||||
LL_COMMON_API static S32 format(std::basic_string<T>& s, const LLSD& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions);
|
||||
/// considers any sequence of delims as a single field separator
|
||||
LL_COMMON_API static void getTokens(const string_type& instr,
|
||||
std::vector<string_type >& tokens,
|
||||
const string_type& delims);
|
||||
/// like simple scan overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& delims);
|
||||
/// add support for keep_delims and quotes (either could be empty string)
|
||||
static void getTokens(const string_type& instr,
|
||||
std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes=string_type());
|
||||
/// like keep_delims-and-quotes overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes=string_type());
|
||||
/// add support for escapes (could be empty string)
|
||||
static void getTokens(const string_type& instr,
|
||||
std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes);
|
||||
/// like escapes overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes);
|
||||
|
||||
LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
|
||||
LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
|
||||
LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
|
||||
LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
|
||||
LL_COMMON_API static void setLocale (std::string inLocale);
|
||||
LL_COMMON_API static std::string getLocale (void);
|
||||
|
||||
static bool isValidIndex(const std::basic_string<T>& string, size_type i)
|
||||
static bool isValidIndex(const string_type& string, size_type i)
|
||||
{
|
||||
return !string.empty() && (0 <= i) && (i <= string.size());
|
||||
}
|
||||
|
||||
static void trimHead(std::basic_string<T>& string);
|
||||
static void trimTail(std::basic_string<T>& string);
|
||||
static void trim(std::basic_string<T>& string) { trimHead(string); trimTail(string); }
|
||||
static void truncate(std::basic_string<T>& string, size_type count);
|
||||
static bool contains(const string_type& string, T c, size_type i=0)
|
||||
{
|
||||
return string.find(c, i) != string_type::npos;
|
||||
}
|
||||
|
||||
static void toUpper(std::basic_string<T>& string);
|
||||
static void toLower(std::basic_string<T>& string);
|
||||
static void trimHead(string_type& string);
|
||||
static void trimTail(string_type& string);
|
||||
static void trim(string_type& string) { trimHead(string); trimTail(string); }
|
||||
static void truncate(string_type& string, size_type count);
|
||||
|
||||
static void toUpper(string_type& string);
|
||||
static void toLower(string_type& string);
|
||||
|
||||
// True if this is the head of s.
|
||||
static BOOL isHead( const std::basic_string<T>& string, const T* s );
|
||||
static BOOL isHead( const string_type& string, const T* s );
|
||||
|
||||
/**
|
||||
* @brief Returns true if string starts with substr
|
||||
|
|
@ -278,8 +316,8 @@ public:
|
|||
* If etither string or substr are empty, this method returns false.
|
||||
*/
|
||||
static bool startsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr);
|
||||
const string_type& string,
|
||||
const string_type& substr);
|
||||
|
||||
/**
|
||||
* @brief Returns true if string ends in substr
|
||||
|
|
@ -287,19 +325,32 @@ public:
|
|||
* If etither string or substr are empty, this method returns false.
|
||||
*/
|
||||
static bool endsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr);
|
||||
const string_type& string,
|
||||
const string_type& substr);
|
||||
|
||||
static void addCRLF(std::basic_string<T>& string);
|
||||
static void removeCRLF(std::basic_string<T>& string);
|
||||
static void addCRLF(string_type& string);
|
||||
static void removeCRLF(string_type& string);
|
||||
|
||||
static void replaceTabsWithSpaces( std::basic_string<T>& string, size_type spaces_per_tab );
|
||||
static void replaceNonstandardASCII( std::basic_string<T>& string, T replacement );
|
||||
static void replaceChar( std::basic_string<T>& string, T target, T replacement );
|
||||
static void replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement );
|
||||
static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
|
||||
static void replaceNonstandardASCII( string_type& string, T replacement );
|
||||
static void replaceChar( string_type& string, T target, T replacement );
|
||||
static void replaceString( string_type& string, string_type target, string_type replacement );
|
||||
|
||||
static BOOL containsNonprintable(const std::basic_string<T>& string);
|
||||
static void stripNonprintable(std::basic_string<T>& string);
|
||||
static BOOL containsNonprintable(const string_type& string);
|
||||
static void stripNonprintable(string_type& string);
|
||||
|
||||
/**
|
||||
* Double-quote an argument string if needed, unless it's already
|
||||
* double-quoted. Decide whether it's needed based on the presence of any
|
||||
* character in @a triggers (default space or double-quote). If we quote
|
||||
* it, escape any embedded double-quote with the @a escape string (default
|
||||
* backslash).
|
||||
*
|
||||
* Passing triggers="" means always quote, unless it's already double-quoted.
|
||||
*/
|
||||
static string_type quote(const string_type& str,
|
||||
const string_type& triggers=" \"",
|
||||
const string_type& escape="\\");
|
||||
|
||||
/**
|
||||
* @brief Unsafe way to make ascii characters. You should probably
|
||||
|
|
@ -308,18 +359,18 @@ public:
|
|||
* The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
|
||||
* should work.
|
||||
*/
|
||||
static void _makeASCII(std::basic_string<T>& string);
|
||||
static void _makeASCII(string_type& string);
|
||||
|
||||
// Conversion to other data types
|
||||
static BOOL convertToBOOL(const std::basic_string<T>& string, BOOL& value);
|
||||
static BOOL convertToU8(const std::basic_string<T>& string, U8& value);
|
||||
static BOOL convertToS8(const std::basic_string<T>& string, S8& value);
|
||||
static BOOL convertToS16(const std::basic_string<T>& string, S16& value);
|
||||
static BOOL convertToU16(const std::basic_string<T>& string, U16& value);
|
||||
static BOOL convertToU32(const std::basic_string<T>& string, U32& value);
|
||||
static BOOL convertToS32(const std::basic_string<T>& string, S32& value);
|
||||
static BOOL convertToF32(const std::basic_string<T>& string, F32& value);
|
||||
static BOOL convertToF64(const std::basic_string<T>& string, F64& value);
|
||||
static BOOL convertToBOOL(const string_type& string, BOOL& value);
|
||||
static BOOL convertToU8(const string_type& string, U8& value);
|
||||
static BOOL convertToS8(const string_type& string, S8& value);
|
||||
static BOOL convertToS16(const string_type& string, S16& value);
|
||||
static BOOL convertToU16(const string_type& string, U16& value);
|
||||
static BOOL convertToU32(const string_type& string, U32& value);
|
||||
static BOOL convertToS32(const string_type& string, S32& value);
|
||||
static BOOL convertToF32(const string_type& string, F32& value);
|
||||
static BOOL convertToF64(const string_type& string, F64& value);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utility functions for working with char*'s and strings
|
||||
|
|
@ -327,24 +378,24 @@ public:
|
|||
// Like strcmp but also handles empty strings. Uses
|
||||
// current locale.
|
||||
static S32 compareStrings(const T* lhs, const T* rhs);
|
||||
static S32 compareStrings(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs);
|
||||
static S32 compareStrings(const string_type& lhs, const string_type& rhs);
|
||||
|
||||
// case insensitive version of above. Uses current locale on
|
||||
// Win32, and falls back to a non-locale aware comparison on
|
||||
// Linux.
|
||||
static S32 compareInsensitive(const T* lhs, const T* rhs);
|
||||
static S32 compareInsensitive(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs);
|
||||
static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
|
||||
|
||||
// Case sensitive comparison with good handling of numbers. Does not use current locale.
|
||||
// a.k.a. strdictcmp()
|
||||
static S32 compareDict(const std::basic_string<T>& a, const std::basic_string<T>& b);
|
||||
static S32 compareDict(const string_type& a, const string_type& b);
|
||||
|
||||
// Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
|
||||
// a.k.a. strdictcmp()
|
||||
static S32 compareDictInsensitive(const std::basic_string<T>& a, const std::basic_string<T>& b);
|
||||
static S32 compareDictInsensitive(const string_type& a, const string_type& b);
|
||||
|
||||
// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
|
||||
static BOOL precedesDict( const std::basic_string<T>& a, const std::basic_string<T>& b );
|
||||
static BOOL precedesDict( const string_type& a, const string_type& b );
|
||||
|
||||
// A replacement for strncpy.
|
||||
// If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
|
||||
|
|
@ -352,7 +403,7 @@ public:
|
|||
static void copy(T* dst, const T* src, size_type dst_size);
|
||||
|
||||
// Copies src into dst at a given offset.
|
||||
static void copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type offset);
|
||||
static void copyInto(string_type& dst, const string_type& src, size_type offset);
|
||||
|
||||
static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
|
||||
|
||||
|
|
@ -362,7 +413,7 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
LL_COMMON_API static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens);
|
||||
LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
|
||||
};
|
||||
|
||||
template<class T> const std::basic_string<T> LLStringUtilBase<T>::null;
|
||||
|
|
@ -636,10 +687,325 @@ namespace LLStringFn
|
|||
////////////////////////////////////////////////////////////
|
||||
// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp.
|
||||
// There is no LLWStringUtil::format implementation currently.
|
||||
// Calling thse for anything other than LLStringUtil will produce link errors.
|
||||
// Calling these for anything other than LLStringUtil will produce link errors.
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, delims);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, drop_delims, keep_delims, quotes);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
namespace LLStringUtilBaseImpl
|
||||
{
|
||||
|
||||
/**
|
||||
* Input string scanner helper for getTokens(), or really any other
|
||||
* character-parsing routine that may have to deal with escape characters.
|
||||
* This implementation defines the concept (also an interface, should you
|
||||
* choose to implement the concept by subclassing) and provides trivial
|
||||
* implementations for a string @em without escape processing.
|
||||
*/
|
||||
template <class T>
|
||||
struct InString
|
||||
{
|
||||
typedef std::basic_string<T> string_type;
|
||||
typedef typename string_type::const_iterator const_iterator;
|
||||
|
||||
InString(const_iterator b, const_iterator e):
|
||||
mIter(b),
|
||||
mEnd(e)
|
||||
{}
|
||||
virtual ~InString() {}
|
||||
|
||||
bool done() const { return mIter == mEnd; }
|
||||
/// Is the current character (*mIter) escaped? This implementation can
|
||||
/// answer trivially because it doesn't support escapes.
|
||||
virtual bool escaped() const { return false; }
|
||||
/// Obtain the current character and advance @c mIter.
|
||||
virtual T next() { return *mIter++; }
|
||||
/// Does the current character match specified character?
|
||||
virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
|
||||
/// Is the current character any one of the specified characters?
|
||||
virtual bool oneof(const string_type& delims) const
|
||||
{
|
||||
return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan forward from @from until either @a delim or end. This is primarily
|
||||
* useful for processing quoted substrings.
|
||||
*
|
||||
* If we do see @a delim, append everything from @from until (excluding)
|
||||
* @a delim to @a into, advance @c mIter to skip @a delim, and return @c
|
||||
* true.
|
||||
*
|
||||
* If we do not see @a delim, do not alter @a into or @c mIter and return
|
||||
* @c false. Do not pass GO, do not collect $200.
|
||||
*
|
||||
* @note The @c false case described above implements normal getTokens()
|
||||
* treatment of an unmatched open quote: treat the quote character as if
|
||||
* escaped, that is, simply collect it as part of the current token. Other
|
||||
* plausible behaviors directly affect the way getTokens() deals with an
|
||||
* unmatched quote: e.g. throwing an exception to treat it as an error, or
|
||||
* assuming a close quote beyond end of string (in which case return @c
|
||||
* true).
|
||||
*/
|
||||
virtual bool collect_until(string_type& into, const_iterator from, T delim)
|
||||
{
|
||||
const_iterator found = std::find(from, mEnd, delim);
|
||||
// If we didn't find delim, change nothing, just tell caller.
|
||||
if (found == mEnd)
|
||||
return false;
|
||||
// Found delim! Append everything between from and found.
|
||||
into.append(from, found);
|
||||
// advance past delim in input
|
||||
mIter = found + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
const_iterator mIter, mEnd;
|
||||
};
|
||||
|
||||
/// InString subclass that handles escape characters
|
||||
template <class T>
|
||||
class InEscString: public InString<T>
|
||||
{
|
||||
public:
|
||||
typedef InString<T> super;
|
||||
typedef typename super::string_type string_type;
|
||||
typedef typename super::const_iterator const_iterator;
|
||||
using super::done;
|
||||
using super::mIter;
|
||||
using super::mEnd;
|
||||
|
||||
InEscString(const_iterator b, const_iterator e, const string_type& escapes):
|
||||
super(b, e),
|
||||
mEscapes(escapes)
|
||||
{
|
||||
// Even though we've already initialized 'mIter' via our base-class
|
||||
// constructor, set it again to check for initial escape char.
|
||||
setiter(b);
|
||||
}
|
||||
|
||||
/// This implementation uses the answer cached by setiter().
|
||||
virtual bool escaped() const { return mIsEsc; }
|
||||
virtual T next()
|
||||
{
|
||||
// If we're looking at the escape character of an escape sequence,
|
||||
// skip that character. This is the one time we can modify 'mIter'
|
||||
// without using setiter: for this one case we DO NOT CARE if the
|
||||
// escaped character is itself an escape.
|
||||
if (mIsEsc)
|
||||
++mIter;
|
||||
// If we were looking at an escape character, this is the escaped
|
||||
// character; otherwise it's just the next character.
|
||||
T result(*mIter);
|
||||
// Advance mIter, checking for escape sequence.
|
||||
setiter(mIter + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual bool is(T ch) const
|
||||
{
|
||||
// Like base-class is(), except that an escaped character matches
|
||||
// nothing.
|
||||
return (! done()) && (! mIsEsc) && *mIter == ch;
|
||||
}
|
||||
|
||||
virtual bool oneof(const string_type& delims) const
|
||||
{
|
||||
// Like base-class oneof(), except that an escaped character matches
|
||||
// nothing.
|
||||
return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
|
||||
}
|
||||
|
||||
virtual bool collect_until(string_type& into, const_iterator from, T delim)
|
||||
{
|
||||
// Deal with escapes in the characters we collect; that is, an escaped
|
||||
// character must become just that character without the preceding
|
||||
// escape. Collect characters in a separate string rather than
|
||||
// directly appending to 'into' in case we do not find delim, in which
|
||||
// case we're supposed to leave 'into' unmodified.
|
||||
string_type collected;
|
||||
// For scanning purposes, we're going to work directly with 'mIter'.
|
||||
// Save its current value in case we fail to see delim.
|
||||
const_iterator save_iter(mIter);
|
||||
// Okay, set 'mIter', checking for escape.
|
||||
setiter(from);
|
||||
while (! done())
|
||||
{
|
||||
// If we see an unescaped delim, stop and report success.
|
||||
if ((! mIsEsc) && *mIter == delim)
|
||||
{
|
||||
// Append collected chars to 'into'.
|
||||
into.append(collected);
|
||||
// Don't forget to advance 'mIter' past delim.
|
||||
setiter(mIter + 1);
|
||||
return true;
|
||||
}
|
||||
// We're not at end, and either we're not looking at delim or it's
|
||||
// escaped. Collect this character and keep going.
|
||||
collected.push_back(next());
|
||||
}
|
||||
// Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
|
||||
// caller.
|
||||
setiter(save_iter);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void setiter(const_iterator i)
|
||||
{
|
||||
mIter = i;
|
||||
|
||||
// Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
|
||||
// answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
|
||||
// contains(mEscapes, *mIter).
|
||||
|
||||
// We're looking at an escaped char if we're not already at end (that
|
||||
// is, *mIter is even meaningful); if *mIter is in fact one of the
|
||||
// specified escape characters; and if there's one more character
|
||||
// following it. That is, if an escape character is the very last
|
||||
// character of the input string, it loses its special meaning.
|
||||
mIsEsc = (! done()) &&
|
||||
LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
|
||||
(mIter+1) != mEnd;
|
||||
}
|
||||
|
||||
const string_type mEscapes;
|
||||
bool mIsEsc;
|
||||
};
|
||||
|
||||
/// getTokens() implementation based on InString concept
|
||||
template <typename INSTRING, typename string_type>
|
||||
void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
// There are times when we want to match either drop_delims or
|
||||
// keep_delims. Concatenate them up front to speed things up.
|
||||
string_type all_delims(drop_delims + keep_delims);
|
||||
// no tokens yet
|
||||
tokens.clear();
|
||||
|
||||
// try for another token
|
||||
while (! instr.done())
|
||||
{
|
||||
// scan past any drop_delims
|
||||
while (instr.oneof(drop_delims))
|
||||
{
|
||||
// skip this drop_delim
|
||||
instr.next();
|
||||
// but if that was the end of the string, done
|
||||
if (instr.done())
|
||||
return;
|
||||
}
|
||||
// found the start of another token: make a slot for it
|
||||
tokens.push_back(string_type());
|
||||
if (instr.oneof(keep_delims))
|
||||
{
|
||||
// *iter is a keep_delim, a token of exactly 1 character. Append
|
||||
// that character to the new token and proceed.
|
||||
tokens.back().push_back(instr.next());
|
||||
continue;
|
||||
}
|
||||
// Here we have a non-delimiter token, which might consist of a mix of
|
||||
// quoted and unquoted parts. Use bash rules for quoting: you can
|
||||
// embed a quoted substring in the midst of an unquoted token (e.g.
|
||||
// ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
|
||||
// to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
|
||||
// from bash in that bash considers an unmatched quote an error. Our
|
||||
// param signature doesn't allow for errors, so just pretend it's not
|
||||
// a quote and embed it.
|
||||
// At this level, keep scanning until we hit the next delimiter of
|
||||
// either type (drop_delims or keep_delims).
|
||||
while (! instr.oneof(all_delims))
|
||||
{
|
||||
// If we're looking at an open quote, search forward for
|
||||
// a close quote, collecting characters along the way.
|
||||
if (instr.oneof(quotes) &&
|
||||
instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
|
||||
{
|
||||
// collect_until is cleverly designed to do exactly what we
|
||||
// need here. No further action needed if it returns true.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Either *iter isn't a quote, or there's no matching close
|
||||
// quote: in other words, just an ordinary char. Append it to
|
||||
// current token.
|
||||
tokens.back().push_back(instr.next());
|
||||
}
|
||||
// having scanned that segment of this token, if we've reached the
|
||||
// end of the string, we're done
|
||||
if (instr.done())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LLStringUtilBaseImpl
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
// Because this overload doesn't support escapes, use simple InString to
|
||||
// manage input range.
|
||||
LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
|
||||
LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes, const string_type& escapes)
|
||||
{
|
||||
// This overload must deal with escapes. Delegate that to InEscString
|
||||
// (unless there ARE no escapes).
|
||||
boost::scoped_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
|
||||
if (escapes.empty())
|
||||
instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
|
||||
else
|
||||
instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
|
||||
LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
|
||||
}
|
||||
|
||||
// static
|
||||
template<class T>
|
||||
|
|
@ -669,7 +1035,7 @@ S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareStrings(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs)
|
||||
S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)
|
||||
{
|
||||
return LLStringOps::collate(lhs.c_str(), rhs.c_str());
|
||||
}
|
||||
|
|
@ -695,8 +1061,8 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
|
|||
}
|
||||
else
|
||||
{
|
||||
std::basic_string<T> lhs_string(lhs);
|
||||
std::basic_string<T> rhs_string(rhs);
|
||||
string_type lhs_string(lhs);
|
||||
string_type rhs_string(rhs);
|
||||
LLStringUtilBase<T>::toUpper(lhs_string);
|
||||
LLStringUtilBase<T>::toUpper(rhs_string);
|
||||
result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
|
||||
|
|
@ -706,10 +1072,10 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs)
|
||||
S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)
|
||||
{
|
||||
std::basic_string<T> lhs_string(lhs);
|
||||
std::basic_string<T> rhs_string(rhs);
|
||||
string_type lhs_string(lhs);
|
||||
string_type rhs_string(rhs);
|
||||
LLStringUtilBase<T>::toUpper(lhs_string);
|
||||
LLStringUtilBase<T>::toUpper(rhs_string);
|
||||
return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
|
||||
|
|
@ -720,7 +1086,7 @@ S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, con
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std::basic_string<T>& bstr)
|
||||
S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)
|
||||
{
|
||||
const T* a = astr.c_str();
|
||||
const T* b = bstr.c_str();
|
||||
|
|
@ -761,7 +1127,7 @@ S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std
|
|||
|
||||
// static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr, const std::basic_string<T>& bstr)
|
||||
S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)
|
||||
{
|
||||
const T* a = astr.c_str();
|
||||
const T* b = bstr.c_str();
|
||||
|
|
@ -796,7 +1162,7 @@ S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr
|
|||
// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
|
||||
// static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std::basic_string<T>& b )
|
||||
BOOL LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )
|
||||
{
|
||||
if( a.size() && b.size() )
|
||||
{
|
||||
|
|
@ -810,7 +1176,7 @@ BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::toUpper(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -824,7 +1190,7 @@ void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::toLower(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::toLower(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -838,7 +1204,7 @@ void LLStringUtilBase<T>::toLower(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::trimHead(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -853,7 +1219,7 @@ void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::trimTail(string_type& string)
|
||||
{
|
||||
if( string.size() )
|
||||
{
|
||||
|
|
@ -872,7 +1238,7 @@ void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string)
|
|||
// Replace line feeds with carriage return-line feed pairs.
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::addCRLF(string_type& string)
|
||||
{
|
||||
const T LF = 10;
|
||||
const T CR = 13;
|
||||
|
|
@ -914,7 +1280,7 @@ void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string)
|
|||
// Remove all carriage returns
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::removeCRLF(string_type& string)
|
||||
{
|
||||
const T CR = 13;
|
||||
|
||||
|
|
@ -935,10 +1301,10 @@ void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T replacement )
|
||||
void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
|
||||
{
|
||||
size_type found_pos = 0;
|
||||
while( (found_pos = string.find(target, found_pos)) != std::basic_string<T>::npos )
|
||||
while( (found_pos = string.find(target, found_pos)) != string_type::npos )
|
||||
{
|
||||
string[found_pos] = replacement;
|
||||
found_pos++; // avoid infinite defeat if target == replacement
|
||||
|
|
@ -947,10 +1313,10 @@ void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement )
|
||||
void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement )
|
||||
{
|
||||
size_type found_pos = 0;
|
||||
while( (found_pos = string.find(target, found_pos)) != std::basic_string<T>::npos )
|
||||
while( (found_pos = string.find(target, found_pos)) != string_type::npos )
|
||||
{
|
||||
string.replace( found_pos, target.length(), replacement );
|
||||
found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
|
||||
|
|
@ -959,7 +1325,7 @@ void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basi
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string, T replacement )
|
||||
void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )
|
||||
{
|
||||
const char LF = 10;
|
||||
const S8 MIN = 32;
|
||||
|
|
@ -979,12 +1345,12 @@ void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string,
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size_type spaces_per_tab )
|
||||
void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )
|
||||
{
|
||||
const T TAB = '\t';
|
||||
const T SPACE = ' ';
|
||||
|
||||
std::basic_string<T> out_str;
|
||||
string_type out_str;
|
||||
// Replace tabs with spaces
|
||||
for (size_type i = 0; i < str.length(); i++)
|
||||
{
|
||||
|
|
@ -1003,7 +1369,7 @@ void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& string)
|
||||
BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string)
|
||||
{
|
||||
const char MIN = 32;
|
||||
BOOL rv = FALSE;
|
||||
|
|
@ -1020,7 +1386,7 @@ BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& strin
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::stripNonprintable(string_type& string)
|
||||
{
|
||||
const char MIN = 32;
|
||||
size_type j = 0;
|
||||
|
|
@ -1051,8 +1417,43 @@ void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string)
|
|||
delete []c_string;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str,
|
||||
const string_type& triggers,
|
||||
const string_type& escape)
|
||||
{
|
||||
size_type len(str.length());
|
||||
// If the string is already quoted, assume user knows what s/he's doing.
|
||||
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
// Not already quoted: do we need to? triggers.empty() is a special case
|
||||
// meaning "always quote."
|
||||
if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
|
||||
{
|
||||
// no trigger characters, don't bother quoting
|
||||
return str;
|
||||
}
|
||||
|
||||
// For whatever reason, we must quote this string.
|
||||
string_type result;
|
||||
result.push_back('"');
|
||||
for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
|
||||
{
|
||||
if (*ci == '"')
|
||||
{
|
||||
result.append(escape);
|
||||
}
|
||||
result.push_back(*ci);
|
||||
}
|
||||
result.push_back('"');
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::_makeASCII(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::_makeASCII(string_type& string)
|
||||
{
|
||||
// Replace non-ASCII chars with LL_UNKNOWN_CHAR
|
||||
for (size_type i = 0; i < string.length(); i++)
|
||||
|
|
@ -1082,7 +1483,7 @@ void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )
|
|||
|
||||
// static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type offset)
|
||||
void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)
|
||||
{
|
||||
if ( offset == dst.length() )
|
||||
{
|
||||
|
|
@ -1092,7 +1493,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s
|
|||
}
|
||||
else
|
||||
{
|
||||
std::basic_string<T> tail = dst.substr(offset);
|
||||
string_type tail = dst.substr(offset);
|
||||
|
||||
dst = dst.substr(0, offset);
|
||||
dst += src;
|
||||
|
|
@ -1103,7 +1504,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s
|
|||
// True if this is the head of s.
|
||||
//static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s )
|
||||
BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
|
|
@ -1119,8 +1520,8 @@ BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s
|
|||
// static
|
||||
template<class T>
|
||||
bool LLStringUtilBase<T>::startsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr)
|
||||
const string_type& string,
|
||||
const string_type& substr)
|
||||
{
|
||||
if(string.empty() || (substr.empty())) return false;
|
||||
if(0 == string.find(substr)) return true;
|
||||
|
|
@ -1130,8 +1531,8 @@ bool LLStringUtilBase<T>::startsWith(
|
|||
// static
|
||||
template<class T>
|
||||
bool LLStringUtilBase<T>::endsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr)
|
||||
const string_type& string,
|
||||
const string_type& substr)
|
||||
{
|
||||
if(string.empty() || (substr.empty())) return false;
|
||||
std::string::size_type idx = string.rfind(substr);
|
||||
|
|
@ -1141,14 +1542,14 @@ bool LLStringUtilBase<T>::endsWith(
|
|||
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL& value)
|
||||
BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
if(
|
||||
(temp == "1") ||
|
||||
|
|
@ -1178,7 +1579,7 @@ BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1191,7 +1592,7 @@ BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& va
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1204,7 +1605,7 @@ BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& va
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1217,7 +1618,7 @@ BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1230,17 +1631,17 @@ BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
U32 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
value = v;
|
||||
|
|
@ -1250,17 +1651,17 @@ BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
S32 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
//TODO: figure out overflow and underflow reporting here
|
||||
|
|
@ -1277,7 +1678,7 @@ BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
|
||||
{
|
||||
F64 value64 = 0.0;
|
||||
BOOL success = convertToF64(string, value64);
|
||||
|
|
@ -1290,17 +1691,17 @@ BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64& value)
|
||||
BOOL LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
F64 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
//TODO: figure out overflow and underflow reporting here
|
||||
|
|
@ -1317,7 +1718,7 @@ BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::truncate(std::basic_string<T>& string, size_type count)
|
||||
void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
|
||||
{
|
||||
size_type cur_size = string.size();
|
||||
string.resize(count < cur_size ? count : cur_size);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @file lltypeinfolookup.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-04-08
|
||||
* @brief Template data structure like std::map<std::type_info*, T>
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLTYPEINFOLOOKUP_H)
|
||||
#define LL_LLTYPEINFOLOOKUP_H
|
||||
|
||||
#include "llsortedvector.h"
|
||||
#include <typeinfo>
|
||||
|
||||
/**
|
||||
* LLTypeInfoLookup is specifically designed for use cases for which you might
|
||||
* consider std::map<std::type_info*, VALUE>. We have several such data
|
||||
* structures in the viewer. The trouble with them is that at least on Linux,
|
||||
* you can't rely on always getting the same std::type_info* for a given type:
|
||||
* different load modules will produce different std::type_info*.
|
||||
* LLTypeInfoLookup contains a workaround to address this issue.
|
||||
*
|
||||
* Specifically, when we don't find the passed std::type_info*,
|
||||
* LLTypeInfoLookup performs a linear search over registered entries to
|
||||
* compare name() strings. Presuming that this succeeds, we cache the new
|
||||
* (previously unrecognized) std::type_info* to speed future lookups.
|
||||
*
|
||||
* This worst-case fallback search (linear search with string comparison)
|
||||
* should only happen the first time we look up a given type from a particular
|
||||
* load module other than the one from which we initially registered types.
|
||||
* (However, a lookup which wouldn't succeed anyway will always have
|
||||
* worst-case performance.) This class is probably best used with less than a
|
||||
* few dozen different types.
|
||||
*/
|
||||
template <typename VALUE>
|
||||
class LLTypeInfoLookup
|
||||
{
|
||||
public:
|
||||
typedef LLTypeInfoLookup<VALUE> self;
|
||||
typedef LLSortedVector<const std::type_info*, VALUE> vector_type;
|
||||
typedef typename vector_type::key_type key_type;
|
||||
typedef typename vector_type::mapped_type mapped_type;
|
||||
typedef typename vector_type::value_type value_type;
|
||||
typedef typename vector_type::iterator iterator;
|
||||
typedef typename vector_type::const_iterator const_iterator;
|
||||
|
||||
LLTypeInfoLookup() {}
|
||||
|
||||
iterator begin() { return mVector.begin(); }
|
||||
iterator end() { return mVector.end(); }
|
||||
const_iterator begin() const { return mVector.begin(); }
|
||||
const_iterator end() const { return mVector.end(); }
|
||||
bool empty() const { return mVector.empty(); }
|
||||
std::size_t size() const { return mVector.size(); }
|
||||
|
||||
std::pair<iterator, bool> insert(const std::type_info* key, const VALUE& value)
|
||||
{
|
||||
return insert(value_type(key, value));
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const value_type& pair)
|
||||
{
|
||||
return mVector.insert(pair);
|
||||
}
|
||||
|
||||
// const find() forwards to non-const find(): this can alter mVector!
|
||||
const_iterator find(const std::type_info* key) const
|
||||
{
|
||||
return const_cast<self*>(this)->find(key);
|
||||
}
|
||||
|
||||
// non-const find() caches previously-unknown type_info* to speed future
|
||||
// lookups.
|
||||
iterator find(const std::type_info* key)
|
||||
{
|
||||
iterator found = mVector.find(key);
|
||||
if (found != mVector.end())
|
||||
{
|
||||
// If LLSortedVector::find() found, great, we're done.
|
||||
return found;
|
||||
}
|
||||
// Here we didn't find the passed type_info*. On Linux, though, even
|
||||
// for the same type, typeid(sametype) produces a different type_info*
|
||||
// when used in different load modules. So the fact that we didn't
|
||||
// find the type_info* we seek doesn't mean this type isn't
|
||||
// registered. Scan for matching name() string.
|
||||
for (typename vector_type::iterator ti(mVector.begin()), tend(mVector.end());
|
||||
ti != tend; ++ti)
|
||||
{
|
||||
if (std::string(ti->first->name()) == key->name())
|
||||
{
|
||||
// This unrecognized 'key' is for the same type as ti->first.
|
||||
// To speed future lookups, insert a new entry that lets us
|
||||
// look up ti->second using this same 'key'.
|
||||
return insert(key, ti->second).first;
|
||||
}
|
||||
}
|
||||
// We simply have never seen a type with this type_info* from any load
|
||||
// module.
|
||||
return mVector.end();
|
||||
}
|
||||
|
||||
private:
|
||||
vector_type mVector;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLTYPEINFOLOOKUP_H) */
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
const S32 LL_VERSION_MAJOR = 3;
|
||||
const S32 LL_VERSION_MINOR = 3;
|
||||
const S32 LL_VERSION_PATCH = 2;
|
||||
const S32 LL_VERSION_PATCH = 3;
|
||||
const S32 LL_VERSION_BUILD = 0;
|
||||
|
||||
const char * const LL_CHANNEL = "Second Life Developer";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file StringVec.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-24
|
||||
* @brief Extend TUT ensure_equals() to handle std::vector<std::string>
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_STRINGVEC_H)
|
||||
#define LL_STRINGVEC_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
typedef std::vector<std::string> StringVec;
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const StringVec& strings)
|
||||
{
|
||||
out << '(';
|
||||
StringVec::const_iterator begin(strings.begin()), end(strings.end());
|
||||
if (begin != end)
|
||||
{
|
||||
out << '"' << *begin << '"';
|
||||
while (++begin != end)
|
||||
{
|
||||
out << ", \"" << *begin << '"';
|
||||
}
|
||||
}
|
||||
out << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_STRINGVEC_H) */
|
||||
|
|
@ -30,6 +30,8 @@
|
|||
#define LL_LISTENER_H
|
||||
|
||||
#include "llsd.h"
|
||||
#include "llevents.h"
|
||||
#include "tests/StringVec.h"
|
||||
#include <iostream>
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -133,24 +135,7 @@ struct Collect
|
|||
return false;
|
||||
}
|
||||
void clear() { result.clear(); }
|
||||
typedef std::vector<std::string> StringList;
|
||||
StringList result;
|
||||
StringVec result;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings)
|
||||
{
|
||||
out << '(';
|
||||
Collect::StringList::const_iterator begin(strings.begin()), end(strings.end());
|
||||
if (begin != end)
|
||||
{
|
||||
out << '"' << *begin << '"';
|
||||
while (++begin != end)
|
||||
{
|
||||
out << ", \"" << *begin << '"';
|
||||
}
|
||||
}
|
||||
out << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LISTENER_H) */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llerror_test.cpp
|
||||
* @date December 2006
|
||||
* @brief error unit tests
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $LicenseInfo:firstyear=2006&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$
|
||||
*/
|
||||
|
|
@ -49,7 +49,7 @@ namespace
|
|||
static bool fatalWasCalled;
|
||||
void fatalCall(const std::string&) { fatalWasCalled = true; }
|
||||
}
|
||||
|
||||
|
||||
namespace tut
|
||||
{
|
||||
class TestRecorder : public LLError::Recorder
|
||||
|
|
@ -57,59 +57,65 @@ namespace tut
|
|||
public:
|
||||
TestRecorder() : mWantsTime(false) { }
|
||||
~TestRecorder() { LLError::removeRecorder(this); }
|
||||
|
||||
|
||||
void recordMessage(LLError::ELevel level,
|
||||
const std::string& message)
|
||||
{
|
||||
mMessages.push_back(message);
|
||||
}
|
||||
|
||||
|
||||
int countMessages() { return (int) mMessages.size(); }
|
||||
void clearMessages() { mMessages.clear(); }
|
||||
|
||||
|
||||
void setWantsTime(bool t) { mWantsTime = t; }
|
||||
bool wantsTime() { return mWantsTime; }
|
||||
|
||||
|
||||
std::string message(int n)
|
||||
{
|
||||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n << ", not enough messages";
|
||||
|
||||
|
||||
tut::ensure(test_name.str(), n < countMessages());
|
||||
return mMessages[n];
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
typedef std::vector<std::string> MessageVector;
|
||||
MessageVector mMessages;
|
||||
|
||||
|
||||
bool mWantsTime;
|
||||
};
|
||||
|
||||
struct ErrorTestData
|
||||
{
|
||||
TestRecorder mRecorder;
|
||||
// addRecorder() expects to be able to later delete the passed
|
||||
// Recorder*. Even though removeRecorder() reclaims ownership, passing
|
||||
// a pointer to a data member rather than a heap Recorder subclass
|
||||
// instance would just be Wrong.
|
||||
TestRecorder* mRecorder;
|
||||
LLError::Settings* mPriorErrorSettings;
|
||||
|
||||
ErrorTestData()
|
||||
|
||||
ErrorTestData():
|
||||
mRecorder(new TestRecorder)
|
||||
{
|
||||
fatalWasCalled = false;
|
||||
|
||||
|
||||
mPriorErrorSettings = LLError::saveAndResetSettings();
|
||||
LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
|
||||
LLError::setFatalFunction(fatalCall);
|
||||
LLError::addRecorder(&mRecorder);
|
||||
LLError::addRecorder(mRecorder);
|
||||
}
|
||||
|
||||
|
||||
~ErrorTestData()
|
||||
{
|
||||
LLError::removeRecorder(&mRecorder);
|
||||
LLError::removeRecorder(mRecorder);
|
||||
delete mRecorder;
|
||||
LLError::restoreSettings(mPriorErrorSettings);
|
||||
}
|
||||
|
||||
|
||||
void ensure_message_count(int expectedCount)
|
||||
{
|
||||
ensure_equals("message count", mRecorder.countMessages(), expectedCount);
|
||||
ensure_equals("message count", mRecorder->countMessages(), expectedCount);
|
||||
}
|
||||
|
||||
void ensure_message_contains(int n, const std::string& expectedText)
|
||||
|
|
@ -117,7 +123,7 @@ namespace tut
|
|||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n;
|
||||
|
||||
ensure_contains(test_name.str(), mRecorder.message(n), expectedText);
|
||||
ensure_contains(test_name.str(), mRecorder->message(n), expectedText);
|
||||
}
|
||||
|
||||
void ensure_message_does_not_contain(int n, const std::string& expectedText)
|
||||
|
|
@ -125,22 +131,22 @@ namespace tut
|
|||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n;
|
||||
|
||||
ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText);
|
||||
ensure_does_not_contain(test_name.str(), mRecorder->message(n), expectedText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef test_group<ErrorTestData> ErrorTestGroup;
|
||||
typedef ErrorTestGroup::object ErrorTestObject;
|
||||
|
||||
|
||||
ErrorTestGroup errorTestGroup("error");
|
||||
|
||||
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<1>()
|
||||
// basic test of output
|
||||
{
|
||||
llinfos << "test" << llendl;
|
||||
llinfos << "bob" << llendl;
|
||||
|
||||
|
||||
ensure_message_contains(0, "test");
|
||||
ensure_message_contains(1, "bob");
|
||||
}
|
||||
|
|
@ -159,7 +165,7 @@ namespace
|
|||
};
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<2>()
|
||||
// messages are filtered based on default level
|
||||
|
|
@ -172,7 +178,7 @@ namespace tut
|
|||
ensure_message_contains(3, "error");
|
||||
ensure_message_contains(4, "four");
|
||||
ensure_message_count(5);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_INFO);
|
||||
writeSome();
|
||||
ensure_message_contains(5, "two");
|
||||
|
|
@ -180,20 +186,20 @@ namespace tut
|
|||
ensure_message_contains(7, "error");
|
||||
ensure_message_contains(8, "four");
|
||||
ensure_message_count(9);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_WARN);
|
||||
writeSome();
|
||||
ensure_message_contains(9, "three");
|
||||
ensure_message_contains(10, "error");
|
||||
ensure_message_contains(11, "four");
|
||||
ensure_message_count(12);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_ERROR);
|
||||
writeSome();
|
||||
ensure_message_contains(12, "error");
|
||||
ensure_message_contains(13, "four");
|
||||
ensure_message_count(14);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_NONE);
|
||||
writeSome();
|
||||
ensure_message_count(14);
|
||||
|
|
@ -218,14 +224,14 @@ namespace tut
|
|||
{
|
||||
std::string thisFile = __FILE__;
|
||||
std::string abbreviateFile = LLError::abbreviateFile(thisFile);
|
||||
|
||||
|
||||
ensure_ends_with("file name abbreviation",
|
||||
abbreviateFile,
|
||||
"llcommon/tests/llerror_test.cpp"
|
||||
);
|
||||
ensure_does_not_contain("file name abbreviation",
|
||||
abbreviateFile, "indra");
|
||||
|
||||
|
||||
std::string someFile =
|
||||
#if LL_WINDOWS
|
||||
"C:/amy/bob/cam.cpp"
|
||||
|
|
@ -234,12 +240,12 @@ namespace tut
|
|||
#endif
|
||||
;
|
||||
std::string someAbbreviation = LLError::abbreviateFile(someFile);
|
||||
|
||||
|
||||
ensure_equals("non-indra file abbreviation",
|
||||
someAbbreviation, someFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string locationString(int line)
|
||||
|
|
@ -247,22 +253,22 @@ namespace
|
|||
std::ostringstream location;
|
||||
location << LLError::abbreviateFile(__FILE__)
|
||||
<< "(" << line << ") : ";
|
||||
|
||||
|
||||
return location.str();
|
||||
}
|
||||
|
||||
|
||||
std::string writeReturningLocation()
|
||||
{
|
||||
llinfos << "apple" << llendl; int this_line = __LINE__;
|
||||
return locationString(this_line);
|
||||
}
|
||||
|
||||
|
||||
std::string writeReturningLocationAndFunction()
|
||||
{
|
||||
llinfos << "apple" << llendl; int this_line = __LINE__;
|
||||
return locationString(this_line) + __FUNCTION__;
|
||||
}
|
||||
|
||||
|
||||
std::string errorReturningLocation()
|
||||
{
|
||||
llerrs << "die" << llendl; int this_line = __LINE__;
|
||||
|
|
@ -271,20 +277,20 @@ namespace
|
|||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<5>()
|
||||
// file and line information in log messages
|
||||
{
|
||||
std::string location = writeReturningLocation();
|
||||
// expecting default to not print location information
|
||||
|
||||
|
||||
LLError::setPrintLocation(true);
|
||||
writeReturningLocation();
|
||||
|
||||
|
||||
LLError::setPrintLocation(false);
|
||||
writeReturningLocation();
|
||||
|
||||
|
||||
ensure_message_does_not_contain(0, location);
|
||||
ensure_message_contains(1, location);
|
||||
ensure_message_does_not_contain(2, location);
|
||||
|
|
@ -297,7 +303,7 @@ namespace tut
|
|||
existing log messages often do.) The functions all return their C++
|
||||
name so that test can be substantial mechanized.
|
||||
*/
|
||||
|
||||
|
||||
std::string logFromGlobal(bool id)
|
||||
{
|
||||
llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl;
|
||||
|
|
@ -345,7 +351,7 @@ namespace
|
|||
return "ClassWithNoLogType::logFromStatic";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ClassWithLogType {
|
||||
LOG_CLASS(ClassWithLogType);
|
||||
public:
|
||||
|
|
@ -360,13 +366,13 @@ namespace
|
|||
return "ClassWithLogType::logFromStatic";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); }
|
||||
std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); }
|
||||
std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); }
|
||||
std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); }
|
||||
std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); }
|
||||
|
||||
|
||||
void ensure_has(const std::string& message,
|
||||
const std::string& actual, const std::string& expected)
|
||||
{
|
||||
|
|
@ -379,18 +385,18 @@ namespace
|
|||
throw tut::failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef std::string (*LogFromFunction)(bool);
|
||||
void testLogName(tut::TestRecorder& recorder, LogFromFunction f,
|
||||
void testLogName(tut::TestRecorder* recorder, LogFromFunction f,
|
||||
const std::string& class_name = "")
|
||||
{
|
||||
recorder.clearMessages();
|
||||
recorder->clearMessages();
|
||||
std::string name = f(false);
|
||||
f(true);
|
||||
|
||||
std::string messageWithoutName = recorder.message(0);
|
||||
std::string messageWithName = recorder.message(1);
|
||||
|
||||
|
||||
std::string messageWithoutName = recorder->message(0);
|
||||
std::string messageWithName = recorder->message(1);
|
||||
|
||||
ensure_has(name + " logged without name",
|
||||
messageWithoutName, name);
|
||||
ensure_has(name + " logged with name",
|
||||
|
|
@ -431,18 +437,18 @@ namespace
|
|||
llinfos << "inside" << llendl;
|
||||
return "moo";
|
||||
}
|
||||
|
||||
|
||||
std::string outerLogger()
|
||||
{
|
||||
llinfos << "outside(" << innerLogger() << ")" << llendl;
|
||||
return "bar";
|
||||
}
|
||||
|
||||
|
||||
void uberLogger()
|
||||
{
|
||||
llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl;
|
||||
}
|
||||
|
||||
|
||||
class LogWhileLogging
|
||||
{
|
||||
public:
|
||||
|
|
@ -461,11 +467,11 @@ namespace
|
|||
LogWhileLogging l;
|
||||
llinfos << "meta(" << l << ")" << llendl;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
// handle nested logging
|
||||
void ErrorTestObject::test<7>()
|
||||
|
|
@ -474,31 +480,31 @@ namespace tut
|
|||
ensure_message_contains(0, "inside");
|
||||
ensure_message_contains(1, "outside(moo)");
|
||||
ensure_message_count(2);
|
||||
|
||||
|
||||
uberLogger();
|
||||
ensure_message_contains(2, "inside");
|
||||
ensure_message_contains(3, "inside");
|
||||
ensure_message_contains(4, "outside(moo)");
|
||||
ensure_message_contains(5, "uber(bar,moo)");
|
||||
ensure_message_count(6);
|
||||
|
||||
|
||||
metaLogger();
|
||||
ensure_message_contains(6, "logging");
|
||||
ensure_message_contains(7, "meta(baz)");
|
||||
ensure_message_count(8);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// special handling of llerrs calls
|
||||
void ErrorTestObject::test<8>()
|
||||
{
|
||||
LLError::setPrintLocation(false);
|
||||
std::string location = errorReturningLocation();
|
||||
|
||||
|
||||
ensure_message_contains(0, location + "error");
|
||||
ensure_message_contains(1, "die");
|
||||
ensure_message_count(2);
|
||||
|
||||
|
||||
ensure("fatal callback called", fatalWasCalled);
|
||||
}
|
||||
}
|
||||
|
|
@ -509,7 +515,7 @@ namespace
|
|||
{
|
||||
return "1947-07-08T03:04:05Z";
|
||||
}
|
||||
|
||||
|
||||
void ufoSighting()
|
||||
{
|
||||
llinfos << "ufo" << llendl;
|
||||
|
|
@ -517,35 +523,35 @@ namespace
|
|||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
// time in output (for recorders that need it)
|
||||
void ErrorTestObject::test<9>()
|
||||
{
|
||||
LLError::setTimeFunction(roswell);
|
||||
|
||||
mRecorder.setWantsTime(false);
|
||||
mRecorder->setWantsTime(false);
|
||||
ufoSighting();
|
||||
ensure_message_contains(0, "ufo");
|
||||
ensure_message_does_not_contain(0, roswell());
|
||||
|
||||
mRecorder.setWantsTime(true);
|
||||
|
||||
mRecorder->setWantsTime(true);
|
||||
ufoSighting();
|
||||
ensure_message_contains(1, "ufo");
|
||||
ensure_message_contains(1, roswell());
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// output order
|
||||
void ErrorTestObject::test<10>()
|
||||
{
|
||||
LLError::setPrintLocation(true);
|
||||
LLError::setTimeFunction(roswell);
|
||||
mRecorder.setWantsTime(true);
|
||||
mRecorder->setWantsTime(true);
|
||||
std::string locationAndFunction = writeReturningLocationAndFunction();
|
||||
|
||||
|
||||
ensure_equals("order is time type location function message",
|
||||
mRecorder.message(0),
|
||||
mRecorder->message(0),
|
||||
roswell() + " INFO: " + locationAndFunction + ": apple");
|
||||
}
|
||||
|
||||
|
|
@ -553,30 +559,30 @@ namespace tut
|
|||
// multiple recorders
|
||||
void ErrorTestObject::test<11>()
|
||||
{
|
||||
TestRecorder altRecorder;
|
||||
LLError::addRecorder(&altRecorder);
|
||||
|
||||
TestRecorder* altRecorder(new TestRecorder);
|
||||
LLError::addRecorder(altRecorder);
|
||||
|
||||
llinfos << "boo" << llendl;
|
||||
|
||||
ensure_message_contains(0, "boo");
|
||||
ensure_equals("alt recorder count", altRecorder.countMessages(), 1);
|
||||
ensure_contains("alt recorder message 0", altRecorder.message(0), "boo");
|
||||
|
||||
ensure_equals("alt recorder count", altRecorder->countMessages(), 1);
|
||||
ensure_contains("alt recorder message 0", altRecorder->message(0), "boo");
|
||||
|
||||
LLError::setTimeFunction(roswell);
|
||||
|
||||
TestRecorder anotherRecorder;
|
||||
anotherRecorder.setWantsTime(true);
|
||||
LLError::addRecorder(&anotherRecorder);
|
||||
|
||||
TestRecorder* anotherRecorder(new TestRecorder);
|
||||
anotherRecorder->setWantsTime(true);
|
||||
LLError::addRecorder(anotherRecorder);
|
||||
|
||||
llinfos << "baz" << llendl;
|
||||
|
||||
std::string when = roswell();
|
||||
|
||||
|
||||
ensure_message_does_not_contain(1, when);
|
||||
ensure_equals("alt recorder count", altRecorder.countMessages(), 2);
|
||||
ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when);
|
||||
ensure_equals("another recorder count", anotherRecorder.countMessages(), 1);
|
||||
ensure_contains("another recorder message 0", anotherRecorder.message(0), when);
|
||||
ensure_equals("alt recorder count", altRecorder->countMessages(), 2);
|
||||
ensure_does_not_contain("alt recorder message 1", altRecorder->message(1), when);
|
||||
ensure_equals("another recorder count", anotherRecorder->countMessages(), 1);
|
||||
ensure_contains("another recorder message 0", anotherRecorder->message(0), when);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -610,10 +616,10 @@ namespace tut
|
|||
{
|
||||
LLError::setDefaultLevel(LLError::LEVEL_WARN);
|
||||
LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
|
||||
|
||||
ensure_message_contains(0, "aim west");
|
||||
ensure_message_contains(1, "error");
|
||||
ensure_message_contains(2, "ate eels");
|
||||
|
|
@ -623,7 +629,7 @@ namespace tut
|
|||
ensure_message_contains(6, "big easy");
|
||||
ensure_message_count(7);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// filtering by function, and that it will override class filtering
|
||||
void ErrorTestObject::test<13>()
|
||||
|
|
@ -632,13 +638,13 @@ namespace tut
|
|||
LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN);
|
||||
LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG);
|
||||
LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE);
|
||||
|
||||
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "buy iron");
|
||||
ensure_message_contains(1, "bad word");
|
||||
ensure_message_count(2);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// filtering by file
|
||||
// and that it is overridden by both class and function filtering
|
||||
|
|
@ -652,7 +658,7 @@ namespace tut
|
|||
LLError::LEVEL_NONE);
|
||||
LLError::setFunctionLevel("TestBeta::doError",
|
||||
LLError::LEVEL_NONE);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "any idea");
|
||||
|
|
@ -660,7 +666,7 @@ namespace tut
|
|||
ensure_message_contains(2, "bad word");
|
||||
ensure_message_count(3);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// proper cached, efficient lookup of filtering
|
||||
void ErrorTestObject::test<15>()
|
||||
|
|
@ -690,7 +696,7 @@ namespace tut
|
|||
ensure_message_count(2);
|
||||
ensure_equals("sixth check", LLError::shouldLogCallCount(), 3);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// configuration from LLSD
|
||||
void ErrorTestObject::test<16>()
|
||||
|
|
@ -699,26 +705,26 @@ namespace tut
|
|||
LLSD config;
|
||||
config["print-location"] = true;
|
||||
config["default-level"] = "DEBUG";
|
||||
|
||||
|
||||
LLSD set1;
|
||||
set1["level"] = "WARN";
|
||||
set1["files"][0] = this_file;
|
||||
|
||||
|
||||
LLSD set2;
|
||||
set2["level"] = "INFO";
|
||||
set2["classes"][0] = "TestAlpha";
|
||||
|
||||
|
||||
LLSD set3;
|
||||
set3["level"] = "NONE";
|
||||
set3["functions"][0] = "TestAlpha::doError";
|
||||
set3["functions"][1] = "TestBeta::doError";
|
||||
|
||||
|
||||
config["settings"][0] = set1;
|
||||
config["settings"][1] = set2;
|
||||
config["settings"][2] = set3;
|
||||
|
||||
|
||||
LLError::configure(config);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "any idea");
|
||||
|
|
@ -726,13 +732,13 @@ namespace tut
|
|||
ensure_message_contains(1, "aim west");
|
||||
ensure_message_contains(2, "bad word");
|
||||
ensure_message_count(3);
|
||||
|
||||
|
||||
// make sure reconfiguring works
|
||||
LLSD config2;
|
||||
config2["default-level"] = "WARN";
|
||||
|
||||
|
||||
LLError::configure(config2);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(3, "aim west");
|
||||
|
|
@ -744,13 +750,13 @@ namespace tut
|
|||
ensure_message_contains(8, "big easy");
|
||||
ensure_message_count(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tests left:
|
||||
handling of classes without LOG_CLASS
|
||||
|
||||
live update of filtering from file
|
||||
|
||||
live update of filtering from file
|
||||
|
||||
syslog recorder
|
||||
file recorder
|
||||
cerr/stderr recorder
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm> // std::sort()
|
||||
#include <stdexcept>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
|
@ -42,6 +43,11 @@
|
|||
#include "../test/lltut.h"
|
||||
#include "wrapllerrs.h"
|
||||
|
||||
struct Badness: public std::runtime_error
|
||||
{
|
||||
Badness(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
struct Keyed: public LLInstanceTracker<Keyed, std::string>
|
||||
{
|
||||
Keyed(const std::string& name):
|
||||
|
|
@ -53,6 +59,17 @@ struct Keyed: public LLInstanceTracker<Keyed, std::string>
|
|||
|
||||
struct Unkeyed: public LLInstanceTracker<Unkeyed>
|
||||
{
|
||||
Unkeyed(const std::string& thrw="")
|
||||
{
|
||||
// LLInstanceTracker should respond appropriately if a subclass
|
||||
// constructor throws an exception. Specifically, it should run
|
||||
// LLInstanceTracker's destructor and remove itself from the
|
||||
// underlying container.
|
||||
if (! thrw.empty())
|
||||
{
|
||||
throw Badness(thrw);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -95,6 +112,7 @@ namespace tut
|
|||
void object::test<2>()
|
||||
{
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
Unkeyed* dangling = NULL;
|
||||
{
|
||||
Unkeyed one;
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
|
|
@ -107,7 +125,11 @@ namespace tut
|
|||
ensure_equals(found, two.get());
|
||||
}
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
}
|
||||
// store an unwise pointer to a temp Unkeyed instance
|
||||
dangling = &one;
|
||||
} // make that instance vanish
|
||||
// check the now-invalid pointer to the destroyed instance
|
||||
ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling));
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -229,4 +251,49 @@ namespace tut
|
|||
}
|
||||
ensure(! what.empty());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("exception in subclass ctor");
|
||||
typedef std::set<Unkeyed*> InstanceSet;
|
||||
InstanceSet existing;
|
||||
// We can't use the iterator-range InstanceSet constructor because
|
||||
// beginInstances() returns an iterator that dereferences to an
|
||||
// Unkeyed&, not an Unkeyed*.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
{
|
||||
existing.insert(&*uki);
|
||||
}
|
||||
Unkeyed* puk = NULL;
|
||||
try
|
||||
{
|
||||
// We don't expect the assignment to take place because we expect
|
||||
// Unkeyed to respond to the non-empty string param by throwing.
|
||||
// We know the LLInstanceTracker base-class constructor will have
|
||||
// run before Unkeyed's constructor, therefore the new instance
|
||||
// will have added itself to the underlying set. The whole
|
||||
// question is, when Unkeyed's constructor throws, will
|
||||
// LLInstanceTracker's destructor remove it from the set? I
|
||||
// realize we're testing the C++ implementation more than
|
||||
// Unkeyed's implementation, but this seems an important point to
|
||||
// nail down.
|
||||
puk = new Unkeyed("throw");
|
||||
}
|
||||
catch (const Badness&)
|
||||
{
|
||||
}
|
||||
// Ensure that every member of the new, updated set of Unkeyed
|
||||
// instances was also present in the original set. If that's not true,
|
||||
// it's because our new Unkeyed ended up in the updated set despite
|
||||
// its constructor exception.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
{
|
||||
ensure("failed to remove instance", existing.find(&*uki) != existing.end());
|
||||
}
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
|
|||
|
|
@ -0,0 +1,694 @@
|
|||
/**
|
||||
* @file llleap_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-21
|
||||
* @brief Test for llleap.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleap.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "../test/manageapr.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "wrapllerrs.h"
|
||||
#include "llevents.h"
|
||||
#include "llprocess.h"
|
||||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
#include <functional>
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
static ManageAPR manager;
|
||||
|
||||
StringVec sv(const StringVec& listof) { return listof; }
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#define sleep(secs) _sleep((secs) * 1000)
|
||||
#endif
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data
|
||||
#else
|
||||
// "Then there's Windows... sigh." The "very large message" test is flaky in a
|
||||
// way that seems to point to either the OS (nonblocking writes to pipes) or
|
||||
// possibly the apr_file_write() function. Poring over log messages reveals
|
||||
// that at some point along the way apr_file_write() returns 11 (Resource
|
||||
// temporarily unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- even
|
||||
// though it did write the chunk! Our next write attempt retries the same
|
||||
// chunk, resulting in the chunk being duplicated at the child end, corrupting
|
||||
// the data stream. Much as I would love to be able to fix it for real, such a
|
||||
// fix would appear to require distinguishing bogus EAGAIN returns from real
|
||||
// ones -- how?? Empirically this behavior is only observed when writing a
|
||||
// "very large message". To be able to move forward at all, try to bypass this
|
||||
// particular failure by adjusting the size of a "very large message" on
|
||||
// Windows.
|
||||
const size_t BUFFERED_LENGTH = 65336;
|
||||
#endif // LL_WINDOWS
|
||||
|
||||
void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < timeout; ++i)
|
||||
{
|
||||
// Every iteration, test whether any of the passed LLLeap instances
|
||||
// still exist (are still running).
|
||||
std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end());
|
||||
for ( ; vli != vlend; ++vli)
|
||||
{
|
||||
// getInstance() returns NULL if it's terminated/gone, non-NULL if
|
||||
// it's still running
|
||||
if (LLLeap::getInstance(*vli))
|
||||
break;
|
||||
}
|
||||
// If we made it through all of 'instances' without finding one that's
|
||||
// still running, we're done.
|
||||
if (vli == vlend)
|
||||
{
|
||||
/*==========================================================================*|
|
||||
std::cout << instances.size() << " LLLeap instances terminated in "
|
||||
<< i << " seconds, proceeding" << std::endl;
|
||||
|*==========================================================================*/
|
||||
return;
|
||||
}
|
||||
// Found an instance that's still running. Wait and pump LLProcess.
|
||||
sleep(1);
|
||||
LLEventPumps::instance().obtain("mainloop").post(LLSD());
|
||||
}
|
||||
tut::ensure(STRINGIZE("at least 1 of " << instances.size()
|
||||
<< " LLLeap instances timed out ("
|
||||
<< timeout << " seconds) without terminating"),
|
||||
i < timeout);
|
||||
}
|
||||
|
||||
void waitfor(LLLeap* instance, int timeout=60)
|
||||
{
|
||||
std::vector<LLLeap*> instances;
|
||||
instances.push_back(instance);
|
||||
waitfor(instances, timeout);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llleap_data
|
||||
{
|
||||
llleap_data():
|
||||
reader(".py",
|
||||
// This logic is adapted from vita.viewerclient.receiveEvent()
|
||||
boost::lambda::_1 <<
|
||||
"import re\n"
|
||||
"import os\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
// Don't forget that this Python script is written to some
|
||||
// temp directory somewhere! Its __file__ is useless in
|
||||
// finding indra/lib/python. Use our __FILE__, with
|
||||
// raw-string syntax to deal with Windows pathnames.
|
||||
"mydir = os.path.dirname(r'" << __FILE__ << "')\n"
|
||||
"try:\n"
|
||||
" from llbase import llsd\n"
|
||||
"except ImportError:\n"
|
||||
// We expect mydir to be .../indra/llcommon/tests.
|
||||
" sys.path.insert(0,\n"
|
||||
" os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n"
|
||||
" from indra.base import llsd\n"
|
||||
"\n"
|
||||
"class ProtocolError(Exception):\n"
|
||||
" def __init__(self, msg, data):\n"
|
||||
" Exception.__init__(self, msg)\n"
|
||||
" self.data = data\n"
|
||||
"\n"
|
||||
"class ParseError(ProtocolError):\n"
|
||||
" pass\n"
|
||||
"\n"
|
||||
"def get():\n"
|
||||
" hdr = ''\n"
|
||||
" while ':' not in hdr and len(hdr) < 20:\n"
|
||||
" hdr += sys.stdin.read(1)\n"
|
||||
" if not hdr:\n"
|
||||
" sys.exit(0)\n"
|
||||
" if not hdr.endswith(':'):\n"
|
||||
" raise ProtocolError('Expected len:data, got %r' % hdr, hdr)\n"
|
||||
" try:\n"
|
||||
" length = int(hdr[:-1])\n"
|
||||
" except ValueError:\n"
|
||||
" raise ProtocolError('Non-numeric len %r' % hdr[:-1], hdr[:-1])\n"
|
||||
" parts = []\n"
|
||||
" received = 0\n"
|
||||
" while received < length:\n"
|
||||
" parts.append(sys.stdin.read(length - received))\n"
|
||||
" received += len(parts[-1])\n"
|
||||
" data = ''.join(parts)\n"
|
||||
" assert len(data) == length\n"
|
||||
" try:\n"
|
||||
" return llsd.parse(data)\n"
|
||||
// Seems the old indra.base.llsd module didn't properly
|
||||
// convert IndexError (from running off end of string) to
|
||||
// LLSDParseError.
|
||||
" except (IndexError, llsd.LLSDParseError), e:\n"
|
||||
" msg = 'Bad received packet (%s)' % e\n"
|
||||
" print >>sys.stderr, '%s, %s bytes:' % (msg, len(data))\n"
|
||||
" showmax = 40\n"
|
||||
// We've observed failures with very large packets;
|
||||
// dumping the entire packet wastes time and space.
|
||||
// But if the error states a particular byte offset,
|
||||
// truncate to (near) that offset when dumping data.
|
||||
" location = re.search(r' at (byte|index) ([0-9]+)', str(e))\n"
|
||||
" if not location:\n"
|
||||
" # didn't find offset, dump whole thing, no ellipsis\n"
|
||||
" ellipsis = ''\n"
|
||||
" else:\n"
|
||||
" # found offset within error message\n"
|
||||
" trunc = int(location.group(2)) + showmax\n"
|
||||
" data = data[:trunc]\n"
|
||||
" ellipsis = '... (%s more)' % (length - trunc)\n"
|
||||
" offset = -showmax\n"
|
||||
" for offset in xrange(0, len(data)-showmax, showmax):\n"
|
||||
" print >>sys.stderr, '%04d: %r +' % \\\n"
|
||||
" (offset, data[offset:offset+showmax])\n"
|
||||
" offset += showmax\n"
|
||||
" print >>sys.stderr, '%04d: %r%s' % \\\n"
|
||||
" (offset, data[offset:], ellipsis)\n"
|
||||
" raise ParseError(msg, data)\n"
|
||||
"\n"
|
||||
"# deal with initial stdin message\n"
|
||||
// this will throw if the initial write to stdin doesn't
|
||||
// follow len:data protocol, or if we couldn't find 'pump'
|
||||
// in the dict
|
||||
"_reply = get()['pump']\n"
|
||||
"\n"
|
||||
"def replypump():\n"
|
||||
" return _reply\n"
|
||||
"\n"
|
||||
"def put(req):\n"
|
||||
" sys.stdout.write(':'.join((str(len(req)), req)))\n"
|
||||
" sys.stdout.flush()\n"
|
||||
"\n"
|
||||
"def send(pump, data):\n"
|
||||
" put(llsd.format_notation(dict(pump=pump, data=data)))\n"
|
||||
"\n"
|
||||
"def request(pump, data):\n"
|
||||
" # we expect 'data' is a dict\n"
|
||||
" data['reply'] = _reply\n"
|
||||
" send(pump, data)\n"),
|
||||
// Get the actual pathname of the NamedExtTempFile and trim off
|
||||
// the ".py" extension. (We could cache reader.getName() in a
|
||||
// separate member variable, but I happen to know getName() just
|
||||
// returns a NamedExtTempFile member rather than performing any
|
||||
// computation, so I don't mind calling it twice.) Then take the
|
||||
// basename.
|
||||
reader_module(LLProcess::basename(
|
||||
reader.getName().substr(0, reader.getName().length()-3))),
|
||||
pPYTHON(getenv("PYTHON")),
|
||||
PYTHON(pPYTHON? pPYTHON : "")
|
||||
{
|
||||
ensure("Set PYTHON to interpreter pathname", pPYTHON);
|
||||
}
|
||||
NamedExtTempFile reader;
|
||||
const std::string reader_module;
|
||||
const char* pPYTHON;
|
||||
const std::string PYTHON;
|
||||
};
|
||||
typedef test_group<llleap_data> llleap_group;
|
||||
typedef llleap_group::object object;
|
||||
llleap_group llleapgrp("llleap");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("multiple LLLeap instances");
|
||||
NamedTempFile script("py",
|
||||
"import time\n"
|
||||
"time.sleep(1)\n");
|
||||
std::vector<LLLeap*> instances;
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
// In this case we're simply establishing that two LLLeap instances
|
||||
// can coexist without throwing exceptions or bombing in any other
|
||||
// way. Wait for them to terminate.
|
||||
waitfor(instances);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("stderr to log");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stderr.write('''Hello from Python!\n"
|
||||
"note partial line''')\n");
|
||||
CaptureLog log(LLError::LEVEL_INFO);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
log.messageWith("Hello from Python!");
|
||||
log.messageWith("note partial line");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("bad stdout protocol");
|
||||
NamedTempFile script("py",
|
||||
"print 'Hello from Python!'\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "Hello from Python!");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("leftover stdout");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
// note lack of newline
|
||||
"sys.stdout.write('Hello from Python!')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("Discarding"), "Hello from Python!");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("bad stdout len prefix");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stdout.write('5a2:something')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "5a2:");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("empty plugin vector");
|
||||
std::string threw;
|
||||
try
|
||||
{
|
||||
LLLeap::create("empty", StringVec());
|
||||
}
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
|
||||
ensure_contains("LLLeap::Error", threw, "no plugin");
|
||||
// try the suppress-exception variant
|
||||
ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("bad launch");
|
||||
// Synthesize bogus executable name
|
||||
std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x");
|
||||
CaptureLog log;
|
||||
std::string threw;
|
||||
try
|
||||
{
|
||||
LLLeap::create("bad exe", BADPYTHON);
|
||||
}
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
|
||||
ensure_contains("LLLeap::create() didn't throw", threw, "failed");
|
||||
log.messageWith("failed");
|
||||
log.messageWith(BADPYTHON);
|
||||
// try the suppress-exception variant
|
||||
ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false));
|
||||
}
|
||||
|
||||
// Generic self-contained listener: derive from this and override its
|
||||
// call() method, then tell somebody to post on the pump named getName().
|
||||
// Control will reach your call() override.
|
||||
struct ListenerBase
|
||||
{
|
||||
// Pass the pump name you want; will tweak for uniqueness.
|
||||
ListenerBase(const std::string& name):
|
||||
mPump(name, true)
|
||||
{
|
||||
mPump.listen(name, boost::bind(&ListenerBase::call, this, _1));
|
||||
}
|
||||
|
||||
virtual ~ListenerBase() {} // pacify MSVC
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LLEventPump& getPump() { return mPump; }
|
||||
const LLEventPump& getPump() const { return mPump; }
|
||||
|
||||
std::string getName() const { return mPump.getName(); }
|
||||
void post(const LLSD& data) { mPump.post(data); }
|
||||
|
||||
LLEventStream mPump;
|
||||
};
|
||||
|
||||
// Mimic a dummy little LLEventAPI that merely sends a reply back to its
|
||||
// requester on the "reply" pump.
|
||||
struct AckAPI: public ListenerBase
|
||||
{
|
||||
AckAPI(): ListenerBase("AckAPI") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
LLEventPumps::instance().obtain(request["reply"]).post("ack");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Give LLLeap script a way to post success/failure.
|
||||
struct Result: public ListenerBase
|
||||
{
|
||||
Result(): ListenerBase("Result") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
mData = request;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ensure() const
|
||||
{
|
||||
tut::ensure(std::string("never posted to ") + getName(), mData.isDefined());
|
||||
// Post an empty string for success, non-empty string is failure message.
|
||||
tut::ensure(mData, mData.asString().empty());
|
||||
}
|
||||
|
||||
LLSD mData;
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("round trip");
|
||||
AckAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"from " << reader_module << " import *\n"
|
||||
// make a request on our little API
|
||||
"request(pump='" << api.getName() << "', data={})\n"
|
||||
// wait for its response
|
||||
"resp = get()\n"
|
||||
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
|
||||
" else 'bad: ' + str(resp)\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))));
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
struct ReqIDAPI: public ListenerBase
|
||||
{
|
||||
ReqIDAPI(): ListenerBase("ReqIDAPI") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
// free function from llevents.h
|
||||
sendReply(LLSD(), request);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<9>()
|
||||
{
|
||||
set_test_name("many small messages");
|
||||
// It's not clear to me whether there's value in iterating many times
|
||||
// over a send/receive loop -- I don't think that will exercise any
|
||||
// interesting corner cases. This test first sends a large number of
|
||||
// messages, then receives all the responses. The intent is to ensure
|
||||
// that some of that data stream crosses buffer boundaries, loop
|
||||
// iterations etc. in OS pipes and the LLLeap/LLProcess implementation.
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Note that since reader imports llsd, this
|
||||
// 'import *' gets us llsd too.
|
||||
"sample = llsd.format_notation(dict(pump='" <<
|
||||
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
|
||||
// The whole packet has length prefix too: "len:data"
|
||||
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
|
||||
// guess how many messages it will take to
|
||||
// accumulate BUFFERED_LENGTH
|
||||
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
|
||||
"print >>sys.stderr, 'Sending %s requests' % count\n"
|
||||
"for i in xrange(count):\n"
|
||||
" request('" << api.getName() << "', dict(reqid=i))\n"
|
||||
// The assumption in this specific test that
|
||||
// replies will arrive in the same order as
|
||||
// requests is ONLY valid because the API we're
|
||||
// invoking sends replies instantly. If the API
|
||||
// had to wait for some external event before
|
||||
// sending its reply, replies could arrive in
|
||||
// arbitrary order, and we'd have to tick them
|
||||
// off from a set.
|
||||
"result = ''\n"
|
||||
"for i in xrange(count):\n"
|
||||
" resp = get()\n"
|
||||
" if resp['data']['reqid'] != i:\n"
|
||||
" result = 'expected reqid=%s in %s' % (i, resp)\n"
|
||||
" break\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))),
|
||||
300); // needs more realtime than most tests
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
// This is the body of test<10>, extracted so we can run it over a number
|
||||
// of large-message sizes.
|
||||
void test_large_message(const std::string& PYTHON, const std::string& reader_module,
|
||||
const std::string& test_name, size_t size)
|
||||
{
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Generate a very large string value.
|
||||
"desired = int(sys.argv[1])\n"
|
||||
// 7 chars per item: 6 digits, 1 comma
|
||||
"count = int((desired - 50)/7)\n"
|
||||
"large = ''.join('%06d,' % i for i in xrange(count))\n"
|
||||
// Pass 'large' as reqid because we know the API
|
||||
// will echo reqid, and we want to receive it back.
|
||||
"request('" << api.getName() << "', dict(reqid=large))\n"
|
||||
"try:\n"
|
||||
" resp = get()\n"
|
||||
"except ParseError, e:\n"
|
||||
" # try to find where e.data diverges from expectation\n"
|
||||
// Normally we'd expect a 'pump' key in there,
|
||||
// too, with value replypump(). But Python
|
||||
// serializes keys in a different order than C++,
|
||||
// so incoming data start with 'data'.
|
||||
// Truthfully, though, if we get as far as 'pump'
|
||||
// before we find a difference, something's very
|
||||
// strange.
|
||||
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
|
||||
" chunk = 40\n"
|
||||
" for offset in xrange(0, max(len(e.data), len(expect)), chunk):\n"
|
||||
" if e.data[offset:offset+chunk] != \\\n"
|
||||
" expect[offset:offset+chunk]:\n"
|
||||
" print >>sys.stderr, 'Offset %06d: expect %r,\\n'\\\n"
|
||||
" ' get %r' %\\\n"
|
||||
" (offset,\n"
|
||||
" expect[offset:offset+chunk],\n"
|
||||
" e.data[offset:offset+chunk])\n"
|
||||
" break\n"
|
||||
" else:\n"
|
||||
" print >>sys.stderr, 'incoming data matches expect?!'\n"
|
||||
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
|
||||
" sys.exit(1)\n"
|
||||
"\n"
|
||||
"echoed = resp['data']['reqid']\n"
|
||||
"if echoed == large:\n"
|
||||
" send('" << result.getName() << "', '')\n"
|
||||
" sys.exit(0)\n"
|
||||
// Here we know echoed did NOT match; try to find where
|
||||
"for i in xrange(count):\n"
|
||||
" start = 7*i\n"
|
||||
" end = 7*(i+1)\n"
|
||||
" if end > len(echoed)\\\n"
|
||||
" or echoed[start:end] != large[start:end]:\n"
|
||||
" send('" << result.getName() << "',\n"
|
||||
" 'at offset %s, expected %r but got %r' %\n"
|
||||
" (start, large[start:end], echoed[start:end]))\n"
|
||||
"sys.exit(1)\n");
|
||||
waitfor(LLLeap::create(test_name,
|
||||
sv(list_of
|
||||
(PYTHON)
|
||||
(script.getName())
|
||||
(stringize(size)))),
|
||||
180); // try a longer timeout
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
struct TestLargeMessage: public std::binary_function<size_t, size_t, bool>
|
||||
{
|
||||
TestLargeMessage(const std::string& PYTHON_, const std::string& reader_module_,
|
||||
const std::string& test_name_):
|
||||
PYTHON(PYTHON_),
|
||||
reader_module(reader_module_),
|
||||
test_name(test_name_)
|
||||
{}
|
||||
|
||||
bool operator()(size_t left, size_t right) const
|
||||
{
|
||||
// We don't know whether upper_bound is going to pass the "sought
|
||||
// value" as the left or the right operand. We pass 0 as the
|
||||
// "sought value" so we can distinguish it. Of course that means
|
||||
// the sequence we're searching must not itself contain 0!
|
||||
size_t size;
|
||||
bool success;
|
||||
if (left)
|
||||
{
|
||||
size = left;
|
||||
// Consider our return value carefully. Normal binary_search
|
||||
// (or, in our case, upper_bound) expects a container sorted
|
||||
// in ascending order, and defaults to the std::less
|
||||
// comparator. Our container is in fact in ascending order, so
|
||||
// return consistently with std::less. Here we were called as
|
||||
// compare(item, sought). If std::less were called that way,
|
||||
// 'true' would mean to move right (to higher numbers) within
|
||||
// the sequence: the item being considered is less than the
|
||||
// sought value. For us, that means that test_large_message()
|
||||
// success should return 'true'.
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = right;
|
||||
// Here we were called as compare(sought, item). If std::less
|
||||
// were called that way, 'true' would mean to move left (to
|
||||
// lower numbers) within the sequence: the sought value is
|
||||
// less than the item being considered. For us, that means
|
||||
// test_large_message() FAILURE should return 'true', hence
|
||||
// test_large_message() success should return 'false'.
|
||||
success = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
test_large_message(PYTHON, reader_module, test_name, size);
|
||||
std::cout << "test_large_message(" << size << ") succeeded" << std::endl;
|
||||
return success;
|
||||
}
|
||||
catch (const failure& e)
|
||||
{
|
||||
std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl;
|
||||
return ! success;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string PYTHON, reader_module, test_name;
|
||||
};
|
||||
|
||||
// The point of this function is to try to find a size at which
|
||||
// test_large_message() can succeed. We still want the overall test to
|
||||
// fail; otherwise we won't get the coder's attention -- but if
|
||||
// test_large_message() fails, try to find a plausible size at which it
|
||||
// DOES work.
|
||||
void test_or_split(const std::string& PYTHON, const std::string& reader_module,
|
||||
const std::string& test_name, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
test_large_message(PYTHON, reader_module, test_name, size);
|
||||
}
|
||||
catch (const failure& e)
|
||||
{
|
||||
std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl;
|
||||
// If it still fails below 4K, give up: subdividing any further is
|
||||
// pointless.
|
||||
if (size >= 4096)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Recur with half the size
|
||||
size_t smaller(size/2);
|
||||
test_or_split(PYTHON, reader_module, test_name, smaller);
|
||||
// Recursive call will throw if test_large_message()
|
||||
// failed, therefore we only reach the line below if it
|
||||
// succeeded.
|
||||
std::cout << "but test_large_message(" << smaller << ") succeeded" << std::endl;
|
||||
|
||||
// Binary search for largest size that works. But since
|
||||
// std::binary_search() only returns bool, actually use
|
||||
// std::upper_bound(), consistent with our desire to find
|
||||
// the LARGEST size that works. First generate a sorted
|
||||
// container of all the sizes we intend to try, from
|
||||
// 'smaller' (known to work) to 'size' (known to fail). We
|
||||
// could whomp up magic iterators to do this dynamically,
|
||||
// without actually instantiating a vector, but for a test
|
||||
// program this will do. At least preallocate the vector.
|
||||
// Per TestLargeMessage comments, it's important that this
|
||||
// vector not contain 0.
|
||||
std::vector<size_t> sizes;
|
||||
sizes.reserve((size - smaller)/4096 + 1);
|
||||
for (size_t sz(smaller), szend(size); sz < szend; sz += 4096)
|
||||
sizes.push_back(sz);
|
||||
// our comparator
|
||||
TestLargeMessage tester(PYTHON, reader_module, test_name);
|
||||
// Per TestLargeMessage comments, pass 0 as the sought value.
|
||||
std::vector<size_t>::const_iterator found =
|
||||
std::upper_bound(sizes.begin(), sizes.end(), 0, tester);
|
||||
if (found != sizes.end() && found != sizes.begin())
|
||||
{
|
||||
std::cout << "test_large_message(" << *(found - 1)
|
||||
<< ") is largest that succeeds" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "cannot determine largest test_large_message(size) "
|
||||
<< "that succeeds" << std::endl;
|
||||
}
|
||||
}
|
||||
catch (const failure&)
|
||||
{
|
||||
// The recursive test_or_split() call above has already
|
||||
// handled the exception. We don't want our caller to see
|
||||
// innermost exception; propagate outermost (below).
|
||||
}
|
||||
}
|
||||
// In any case, because we reached here through failure of
|
||||
// our original test_large_message(size) call, ensure failure
|
||||
// propagates.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<10>()
|
||||
{
|
||||
set_test_name("very large message");
|
||||
test_or_split(PYTHON, reader_module, get_test_name(), BUFFERED_LENGTH);
|
||||
}
|
||||
} // namespace tut
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -40,41 +40,15 @@ typedef U32 uint32_t;
|
|||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
|
||||
/*==========================================================================*|
|
||||
// Whoops, seems Linden's Boost package and the viewer are built with
|
||||
// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem
|
||||
// pathname operations produces Windows link errors:
|
||||
// unresolved external symbol "private: static class std::codecvt<unsigned short,
|
||||
// char,int> const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()"
|
||||
// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()"
|
||||
// See:
|
||||
// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html
|
||||
// which points to:
|
||||
// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx
|
||||
|
||||
// As we're not trying to preserve compatibility with old Boost.Filesystem
|
||||
// code, but rather writing brand-new code, use the newest available
|
||||
// Filesystem API.
|
||||
#define BOOST_FILESYSTEM_VERSION 3
|
||||
#include "boost/filesystem.hpp"
|
||||
#include "boost/filesystem/v3/fstream.hpp"
|
||||
|*==========================================================================*/
|
||||
#include "boost/range.hpp"
|
||||
#include "boost/foreach.hpp"
|
||||
#include "boost/function.hpp"
|
||||
#include "boost/lambda/lambda.hpp"
|
||||
#include "boost/lambda/bind.hpp"
|
||||
namespace lambda = boost::lambda;
|
||||
/*==========================================================================*|
|
||||
// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams!
|
||||
#include "boost/iostreams/stream.hpp"
|
||||
#include "boost/iostreams/device/file_descriptor.hpp"
|
||||
|*==========================================================================*/
|
||||
|
||||
#include "../llsd.h"
|
||||
#include "../llsdserialize.h"
|
||||
|
|
@ -82,236 +56,17 @@ namespace lambda = boost::lambda;
|
|||
#include "../llformat.h"
|
||||
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/manageapr.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "stringize.h"
|
||||
|
||||
static ManageAPR manager;
|
||||
|
||||
std::vector<U8> string_to_vector(const std::string& str)
|
||||
{
|
||||
return std::vector<U8>(str.begin(), str.end());
|
||||
}
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
// We want to call strerror_r(), but alarmingly, there are two different
|
||||
// variants. The one that returns int always populates the passed buffer
|
||||
// (except in case of error), whereas the other one always returns a valid
|
||||
// char* but might or might not populate the passed buffer. How do we know
|
||||
// which one we're getting? Define adapters for each and let the compiler
|
||||
// select the applicable adapter.
|
||||
|
||||
// strerror_r() returns char*
|
||||
std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret)
|
||||
{
|
||||
return strerror_ret;
|
||||
}
|
||||
|
||||
// strerror_r() returns int
|
||||
std::string message_from(int orig_errno, const char* buffer, int strerror_ret)
|
||||
{
|
||||
if (strerror_ret == 0)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
// Here strerror_r() has set errno. Since strerror_r() has already failed,
|
||||
// seems like a poor bet to call it again to diagnose its own error...
|
||||
int stre_errno = errno;
|
||||
if (stre_errno == ERANGE)
|
||||
{
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (buffer too small)");
|
||||
}
|
||||
if (stre_errno == EINVAL)
|
||||
{
|
||||
return STRINGIZE("unknown errno " << orig_errno);
|
||||
}
|
||||
// Here we don't even understand the errno from strerror_r()!
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (error " << stre_errno << ')');
|
||||
}
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-(
|
||||
std::string temp_directory_path()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
char buffer[4096];
|
||||
GetTempPathA(sizeof(buffer), buffer);
|
||||
return buffer;
|
||||
|
||||
#else // LL_DARWIN, LL_LINUX
|
||||
static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" };
|
||||
BOOST_FOREACH(const char* var, vars)
|
||||
{
|
||||
const char* found = getenv(var);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
return "/tmp";
|
||||
#endif // LL_DARWIN, LL_LINUX
|
||||
}
|
||||
|
||||
// Windows presents a kinda sorta compatibility layer. Code to the yucky
|
||||
// Windows names because they're less likely than the Posix names to collide
|
||||
// with any other names in this source.
|
||||
#if LL_WINDOWS
|
||||
#define _remove DeleteFileA
|
||||
#else // ! LL_WINDOWS
|
||||
#define _open open
|
||||
#define _write write
|
||||
#define _close close
|
||||
#define _remove remove
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// Create a text file with specified content "somewhere in the
|
||||
// filesystem," cleaning up when it goes out of scope.
|
||||
class NamedTempFile
|
||||
{
|
||||
public:
|
||||
// Function that accepts an ostream ref and (presumably) writes stuff to
|
||||
// it, e.g.:
|
||||
// (lambda::_1 << "the value is " << 17 << '\n')
|
||||
typedef boost::function<void(std::ostream&)> Streamer;
|
||||
|
||||
NamedTempFile(const std::string& ext, const std::string& content):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, lambda::_1 << content);
|
||||
}
|
||||
|
||||
// Disambiguate when passing string literal
|
||||
NamedTempFile(const std::string& ext, const char* content):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, lambda::_1 << content);
|
||||
}
|
||||
|
||||
NamedTempFile(const std::string& ext, const Streamer& func):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, func);
|
||||
}
|
||||
|
||||
~NamedTempFile()
|
||||
{
|
||||
_remove(mPath.c_str());
|
||||
}
|
||||
|
||||
std::string getName() const { return mPath; }
|
||||
|
||||
private:
|
||||
void createFile(const std::string& ext, const Streamer& func)
|
||||
{
|
||||
// Silly maybe, but use 'ext' as the name prefix. Strip off a leading
|
||||
// '.' if present.
|
||||
int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0;
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
// Make sure mPath ends with a directory separator, if it doesn't already.
|
||||
if (mPath.empty() ||
|
||||
! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/'))
|
||||
{
|
||||
mPath.append("/");
|
||||
}
|
||||
|
||||
// mkstemp() accepts and modifies a char* template string. Generate
|
||||
// the template string, then copy to modifiable storage.
|
||||
// mkstemp() requires its template string to end in six X's.
|
||||
mPath += ext.substr(pfx_offset) + "XXXXXX";
|
||||
// Copy to vector<char>
|
||||
std::vector<char> pathtemplate(mPath.begin(), mPath.end());
|
||||
// append a nul byte for classic-C semantics
|
||||
pathtemplate.push_back('\0');
|
||||
// std::vector promises that a pointer to the 0th element is the same
|
||||
// as a pointer to a contiguous classic-C array
|
||||
int fd(mkstemp(&pathtemplate[0]));
|
||||
if (fd == -1)
|
||||
{
|
||||
// The documented errno values (http://linux.die.net/man/3/mkstemp)
|
||||
// are used in a somewhat unusual way, so provide context-specific
|
||||
// errors.
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath
|
||||
<< "\") could not create unique file " << LL_ENDL;
|
||||
}
|
||||
if (errno == EINVAL)
|
||||
{
|
||||
LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '"
|
||||
<< mPath << "'" << LL_ENDL;
|
||||
}
|
||||
// Shrug, something else
|
||||
int mkst_errno = errno;
|
||||
char buffer[256];
|
||||
LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: "
|
||||
<< message_from(mkst_errno, buffer,
|
||||
strerror_r(mkst_errno, buffer, sizeof(buffer)))
|
||||
<< LL_ENDL;
|
||||
}
|
||||
// mkstemp() seems to have worked! Capture the modified filename.
|
||||
// Avoid the nul byte we appended.
|
||||
mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1));
|
||||
|
||||
/*==========================================================================*|
|
||||
// Define an ostream on the open fd. Tell it to close fd on destruction.
|
||||
boost::iostreams::stream<boost::iostreams::file_descriptor_sink>
|
||||
out(fd, boost::iostreams::close_handle);
|
||||
|*==========================================================================*/
|
||||
|
||||
// Write desired content.
|
||||
std::ostringstream out;
|
||||
// Stream stuff to it.
|
||||
func(out);
|
||||
|
||||
std::string data(out.str());
|
||||
int written(_write(fd, data.c_str(), data.length()));
|
||||
int closed(_close(fd));
|
||||
llassert_always(written == data.length() && closed == 0);
|
||||
|
||||
#else // LL_WINDOWS
|
||||
// GetTempFileName() is documented to require a MAX_PATH buffer.
|
||||
char tempname[MAX_PATH];
|
||||
// Use 'ext' as filename prefix, but skip leading '.' if any.
|
||||
// The 0 param is very important: requests iterating until we get a
|
||||
// unique name.
|
||||
if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname))
|
||||
{
|
||||
// I always have to look up this call... :-P
|
||||
LPSTR msgptr;
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
LPSTR(&msgptr), // have to cast (char**) to (char*)
|
||||
0, NULL );
|
||||
LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \""
|
||||
<< (ext.c_str() + pfx_offset) << "\") failed: "
|
||||
<< msgptr << LL_ENDL;
|
||||
LocalFree(msgptr);
|
||||
}
|
||||
// GetTempFileName() appears to have worked! Capture the actual
|
||||
// filename.
|
||||
mPath = tempname;
|
||||
// Open the file and stream content to it. Destructor will close.
|
||||
std::ofstream out(tempname);
|
||||
func(out);
|
||||
|
||||
#endif // LL_WINDOWS
|
||||
}
|
||||
|
||||
void peep()
|
||||
{
|
||||
std::cout << "File '" << mPath << "' contains:\n";
|
||||
std::ifstream reader(mPath.c_str());
|
||||
std::string line;
|
||||
while (std::getline(reader, line))
|
||||
std::cout << line << '\n';
|
||||
std::cout << "---\n";
|
||||
}
|
||||
|
||||
std::string mPath;
|
||||
};
|
||||
|
||||
namespace tut
|
||||
{
|
||||
struct sd_xml_data
|
||||
|
|
@ -1783,7 +1538,7 @@ namespace tut
|
|||
const char* PYTHON(getenv("PYTHON"));
|
||||
ensure("Set $PYTHON to the Python interpreter", PYTHON);
|
||||
|
||||
NamedTempFile scriptfile(".py", script);
|
||||
NamedTempFile scriptfile("py", script);
|
||||
|
||||
#if LL_WINDOWS
|
||||
std::string q("\"");
|
||||
|
|
@ -1802,14 +1557,15 @@ namespace tut
|
|||
}
|
||||
|
||||
#else // LL_DARWIN, LL_LINUX
|
||||
LLProcessLauncher py;
|
||||
py.setExecutable(PYTHON);
|
||||
py.addArgument(scriptfile.getName());
|
||||
ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0);
|
||||
LLProcess::Params params;
|
||||
params.executable = PYTHON;
|
||||
params.args.add(scriptfile.getName());
|
||||
LLProcessPtr py(LLProcess::create(params));
|
||||
ensure(STRINGIZE("Couldn't launch " << desc << " script"), py);
|
||||
// Implementing timeout would mean messing with alarm() and
|
||||
// catching SIGALRM... later maybe...
|
||||
int status(0);
|
||||
if (waitpid(py.getProcessID(), &status, 0) == -1)
|
||||
if (waitpid(py->getProcessID(), &status, 0) == -1)
|
||||
{
|
||||
int waitpid_errno(errno);
|
||||
ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: "
|
||||
|
|
@ -1888,12 +1644,12 @@ namespace tut
|
|||
" else:\n"
|
||||
" assert False, 'Too many data items'\n";
|
||||
|
||||
// Create a something.llsd file containing 'data' serialized to
|
||||
// Create an llsdXXXXXX file containing 'data' serialized to
|
||||
// notation. It's important to separate with newlines because Python's
|
||||
// llsd module doesn't support parsing from a file stream, only from a
|
||||
// string, so we have to know how much of the file to read into a
|
||||
// string.
|
||||
NamedTempFile file(".llsd",
|
||||
NamedTempFile file("llsd",
|
||||
// NamedTempFile's boost::function constructor
|
||||
// takes a callable. To this callable it passes the
|
||||
// std::ostream with which it's writing the
|
||||
|
|
@ -1926,7 +1682,7 @@ namespace tut
|
|||
// Create an empty data file. This is just a placeholder for our
|
||||
// script to write into. Create it to establish a unique name that
|
||||
// we know.
|
||||
NamedTempFile file(".llsd", "");
|
||||
NamedTempFile file("llsd", "");
|
||||
|
||||
python("write Python notation",
|
||||
lambda::_1 <<
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* @file llstreamqueue_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-05
|
||||
* @brief Test for llstreamqueue.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llstreamqueue.h"
|
||||
// STL headers
|
||||
#include <vector>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llstreamqueue_data
|
||||
{
|
||||
llstreamqueue_data():
|
||||
// we want a buffer with actual bytes in it, not an empty vector
|
||||
buffer(10)
|
||||
{}
|
||||
// As LLStreamQueue is merely a typedef for
|
||||
// LLGenericStreamQueue<char>, and no logic in LLGenericStreamQueue is
|
||||
// specific to the <char> instantiation, we're comfortable for now
|
||||
// testing only the narrow-char version.
|
||||
LLStreamQueue strq;
|
||||
// buffer for use in multiple tests
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
typedef test_group<llstreamqueue_data> llstreamqueue_group;
|
||||
typedef llstreamqueue_group::object object;
|
||||
llstreamqueue_group llstreamqueuegrp("llstreamqueue");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("empty LLStreamQueue");
|
||||
ensure_equals("brand-new LLStreamQueue isn't empty",
|
||||
strq.size(), 0);
|
||||
ensure_equals("brand-new LLStreamQueue returns data",
|
||||
strq.asSource().read(&buffer[0], buffer.size()), 0);
|
||||
strq.asSink().close();
|
||||
ensure_equals("closed empty LLStreamQueue not at EOF",
|
||||
strq.asSource().read(&buffer[0], buffer.size()), -1);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("one internal block, one buffer");
|
||||
LLStreamQueue::Sink sink(strq.asSink());
|
||||
ensure_equals("write(\"\")", sink.write("", 0), 0);
|
||||
ensure_equals("0 write should leave LLStreamQueue empty (size())",
|
||||
strq.size(), 0);
|
||||
ensure_equals("0 write should leave LLStreamQueue empty (peek())",
|
||||
strq.peek(&buffer[0], buffer.size()), 0);
|
||||
// The meaning of "atomic" is that it must be smaller than our buffer.
|
||||
std::string atomic("atomic");
|
||||
ensure("test data exceeds buffer", atomic.length() < buffer.size());
|
||||
ensure_equals(STRINGIZE("write(\"" << atomic << "\")"),
|
||||
sink.write(&atomic[0], atomic.length()), atomic.length());
|
||||
ensure_equals("size() after write()", strq.size(), atomic.length());
|
||||
size_t peeklen(strq.peek(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"),
|
||||
peeklen, atomic.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + peeklen), atomic);
|
||||
ensure_equals("size() after peek()", strq.size(), atomic.length());
|
||||
// peek() should not consume. Use a different buffer to prove it isn't
|
||||
// just leftover data from the first peek().
|
||||
std::vector<char> again(buffer.size());
|
||||
peeklen = size_t(strq.peek(&again[0], again.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"),
|
||||
peeklen, atomic.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"),
|
||||
std::string(again.begin(), again.begin() + peeklen), atomic);
|
||||
// now consume.
|
||||
std::vector<char> third(buffer.size());
|
||||
size_t readlen(strq.read(&third[0], third.size()));
|
||||
ensure_equals(STRINGIZE("read(\"" << atomic << "\")"),
|
||||
readlen, atomic.length());
|
||||
ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"),
|
||||
std::string(third.begin(), third.begin() + readlen), atomic);
|
||||
ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0);
|
||||
ensure_equals("size() after read()", strq.size(), 0);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("basic skip()");
|
||||
std::string lovecraft("lovecraft");
|
||||
ensure("test data exceeds buffer", lovecraft.length() < buffer.size());
|
||||
ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"),
|
||||
strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length());
|
||||
size_t peeklen(strq.peek(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"),
|
||||
peeklen, lovecraft.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft);
|
||||
std::streamsize skip1(4);
|
||||
ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1);
|
||||
ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1);
|
||||
size_t readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"),
|
||||
readlen, lovecraft.length() - skip1);
|
||||
ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + readlen),
|
||||
lovecraft.substr(skip1));
|
||||
ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("skip() multiple blocks");
|
||||
std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" };
|
||||
std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length());
|
||||
std::streamsize leave(5); // len("craft") above
|
||||
std::streamsize skip(total - leave);
|
||||
std::streamsize written(0);
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
written += strq.write(&block[0], block.length());
|
||||
ensure_equals("size() after write()", strq.size(), written);
|
||||
}
|
||||
std::streamsize skiplen(strq.skip(skip));
|
||||
ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip);
|
||||
ensure_equals("size() after skip()", strq.size(), leave);
|
||||
size_t readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals("read(\"craft\")", readlen, leave);
|
||||
ensure_equals("read(\"craft\") result",
|
||||
std::string(buffer.begin(), buffer.begin() + readlen), "craft");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("concatenate blocks");
|
||||
std::string blocks[] = { "abcd", "efghij", "klmnopqrs" };
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
strq.write(&block[0], block.length());
|
||||
}
|
||||
std::vector<char> longbuffer(30);
|
||||
std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size()));
|
||||
ensure_equals("read() multiple blocks",
|
||||
readlen, blocks[0].length() + blocks[1].length() + blocks[2].length());
|
||||
ensure_equals("read() multiple blocks result",
|
||||
std::string(longbuffer.begin(), longbuffer.begin() + readlen),
|
||||
blocks[0] + blocks[1] + blocks[2]);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("split blocks");
|
||||
std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" };
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
strq.write(&block[0], block.length());
|
||||
}
|
||||
strq.close();
|
||||
// We've already verified what strq.size() should be at this point;
|
||||
// see above test named "skip() multiple blocks"
|
||||
std::streamsize chksize(strq.size());
|
||||
std::streamsize readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals("read() 0", readlen, buffer.size());
|
||||
ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij");
|
||||
chksize -= readlen;
|
||||
ensure_equals("size() after read() 0", strq.size(), chksize);
|
||||
readlen = strq.read(&buffer[0], buffer.size());
|
||||
ensure_equals("read() 1", readlen, buffer.size());
|
||||
ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst");
|
||||
chksize -= readlen;
|
||||
ensure_equals("size() after read() 1", strq.size(), chksize);
|
||||
readlen = strq.read(&buffer[0], buffer.size());
|
||||
ensure_equals("read() 2", readlen, chksize);
|
||||
ensure_equals("read() 2 result",
|
||||
std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz");
|
||||
ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -29,7 +29,11 @@
|
|||
#include "linden_common.h"
|
||||
#include "../test/lltut.h"
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include "../llstring.h"
|
||||
#include "StringVec.h"
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
|
@ -750,4 +754,118 @@ namespace tut
|
|||
ensure("empty substr.", !LLStringUtil::endsWith(empty, value));
|
||||
ensure("empty everything.", !LLStringUtil::endsWith(empty, empty));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void string_index_object_t::test<41>()
|
||||
{
|
||||
set_test_name("getTokens(\"delims\")");
|
||||
ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec());
|
||||
ensure_equals("only delims",
|
||||
LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec());
|
||||
ensure_equals("sequence of delims",
|
||||
LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one"));
|
||||
// nat considers this a dubious implementation side effect, but I'd
|
||||
// hate to change it now...
|
||||
ensure_equals("noncontiguous tokens",
|
||||
LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one"));
|
||||
ensure_equals("space-padded tokens",
|
||||
LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two"));
|
||||
ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one"));
|
||||
}
|
||||
|
||||
// Shorthand for verifying that getTokens() behaves the same when you
|
||||
// don't pass a string of escape characters, when you pass an empty string
|
||||
// (different overloads), and when you pass a string of characters that
|
||||
// aren't actually present.
|
||||
void ensure_getTokens(const std::string& desc,
|
||||
const std::string& string,
|
||||
const std::string& drop_delims,
|
||||
const std::string& keep_delims,
|
||||
const std::string& quotes,
|
||||
const std::vector<std::string>& expect)
|
||||
{
|
||||
ensure_equals(desc + " - no esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes),
|
||||
expect);
|
||||
ensure_equals(desc + " - empty esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""),
|
||||
expect);
|
||||
ensure_equals(desc + " - unused esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"),
|
||||
expect);
|
||||
}
|
||||
|
||||
void ensure_getTokens(const std::string& desc,
|
||||
const std::string& string,
|
||||
const std::string& drop_delims,
|
||||
const std::string& keep_delims,
|
||||
const std::vector<std::string>& expect)
|
||||
{
|
||||
ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void string_index_object_t::test<42>()
|
||||
{
|
||||
set_test_name("getTokens(\"delims\", etc.)");
|
||||
// Signatures to test in this method:
|
||||
// getTokens(string, drop_delims, keep_delims [, quotes [, escapes]])
|
||||
// If you omit keep_delims, you get the older function (test above).
|
||||
|
||||
// cases like the getTokens(string, delims) tests above
|
||||
ensure_getTokens("empty string", "", " ", "", StringVec());
|
||||
ensure_getTokens("only delims",
|
||||
" \r\n ", " \r\n", "", StringVec());
|
||||
ensure_getTokens("sequence of delims",
|
||||
",,, one ,,,", ", ", "", list_of("one"));
|
||||
// Note contrast with the case in the previous method
|
||||
ensure_getTokens("noncontiguous tokens",
|
||||
", ,, , one ,,,", ", ", "", list_of("one"));
|
||||
ensure_getTokens("space-padded tokens",
|
||||
", one , two ,", ", ", "",
|
||||
list_of("one")("two"));
|
||||
ensure_getTokens("no delims", "one", ",", "", list_of("one"));
|
||||
|
||||
// drop_delims vs. keep_delims
|
||||
ensure_getTokens("arithmetic",
|
||||
" ab+def / xx* yy ", " ", "+-*/",
|
||||
list_of("ab")("+")("def")("/")("xx")("*")("yy"));
|
||||
|
||||
// quotes
|
||||
ensure_getTokens("no quotes",
|
||||
"She said, \"Don't go.\"", " ", ",", "",
|
||||
list_of("She")("said")(",")("\"Don't")("go.\""));
|
||||
ensure_getTokens("quotes",
|
||||
"She said, \"Don't go.\"", " ", ",", "\"",
|
||||
list_of("She")("said")(",")("Don't go."));
|
||||
ensure_getTokens("quotes and delims",
|
||||
"run c:/'Documents and Settings'/someone", " ", "", "'",
|
||||
list_of("run")("c:/Documents and Settings/someone"));
|
||||
ensure_getTokens("unmatched quote",
|
||||
"baby don't leave", " ", "", "'",
|
||||
list_of("baby")("don't")("leave"));
|
||||
ensure_getTokens("adjacent quoted",
|
||||
"abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'",
|
||||
list_of("abcdef \"ghijkl' mnopqr"));
|
||||
ensure_getTokens("quoted empty string",
|
||||
"--set SomeVar ''", " ", "", "'",
|
||||
list_of("--set")("SomeVar")(""));
|
||||
|
||||
// escapes
|
||||
// Don't use backslash as an escape for these tests -- you'll go nuts
|
||||
// between the C++ string scanner and getTokens() escapes. Test with
|
||||
// something else!
|
||||
ensure_equals("escaped delims",
|
||||
LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"),
|
||||
list_of(" a")("-")("dog-gone phrase"));
|
||||
ensure_equals("escaped quotes",
|
||||
LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"),
|
||||
list_of("say:")("this isn't working."));
|
||||
ensure_equals("escaped escape",
|
||||
LLStringUtil::getTokens("want x^^2", " ", "", "", "^"),
|
||||
list_of("want")("x^2"));
|
||||
ensure_equals("escape at end",
|
||||
LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"),
|
||||
list_of("it's up")("there^"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
"""\
|
||||
@file setpython.py
|
||||
@author Nat Goodspeed
|
||||
@date 2011-07-13
|
||||
@brief Set PYTHON environment variable for tests that care.
|
||||
|
||||
$LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
Copyright (c) 2011, Linden Research, Inc.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["PYTHON"] = sys.executable
|
||||
sys.exit(subprocess.call(sys.argv[1:]))
|
||||
|
|
@ -29,7 +29,22 @@
|
|||
#if ! defined(LL_WRAPLLERRS_H)
|
||||
#define LL_WRAPLLERRS_H
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
#include <tut/tut.hpp>
|
||||
#include "llerrorcontrol.h"
|
||||
#include "stringize.h"
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
// statically reference the function in test.cpp... it's short, we could
|
||||
// replicate, but better to reuse
|
||||
extern void wouldHaveCrashed(const std::string& message);
|
||||
|
||||
struct WrapLL_ERRS
|
||||
{
|
||||
|
|
@ -70,4 +85,118 @@ struct WrapLL_ERRS
|
|||
LLError::FatalFunction mPriorFatal;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLError::addRecorder() accepts ownership of the passed Recorder* -- it
|
||||
* expects to be able to delete it later. CaptureLog isa Recorder whose
|
||||
* pointer we want to be able to pass without any ownership implications.
|
||||
* For such cases, instantiate a new RecorderProxy(yourRecorder) and pass
|
||||
* that. Your heap RecorderProxy might later be deleted, but not yourRecorder.
|
||||
*/
|
||||
class RecorderProxy: public LLError::Recorder
|
||||
{
|
||||
public:
|
||||
RecorderProxy(LLError::Recorder* recorder):
|
||||
mRecorder(recorder)
|
||||
{}
|
||||
|
||||
virtual void recordMessage(LLError::ELevel level, const std::string& message)
|
||||
{
|
||||
mRecorder->recordMessage(level, message);
|
||||
}
|
||||
|
||||
virtual bool wantsTime()
|
||||
{
|
||||
return mRecorder->wantsTime();
|
||||
}
|
||||
|
||||
private:
|
||||
LLError::Recorder* mRecorder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture log messages. This is adapted (simplified) from the one in
|
||||
* llerror_test.cpp.
|
||||
*/
|
||||
class CaptureLog : public LLError::Recorder, public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG):
|
||||
// Mostly what we're trying to accomplish by saving and resetting
|
||||
// LLError::Settings is to bypass the default RecordToStderr and
|
||||
// RecordToWinDebug Recorders. As these are visible only inside
|
||||
// llerror.cpp, we can't just call LLError::removeRecorder() with
|
||||
// each. For certain tests we need to produce, capture and examine
|
||||
// DEBUG log messages -- but we don't want to spam the user's console
|
||||
// with that output. If it turns out that saveAndResetSettings() has
|
||||
// some bad effect, give up and just let the DEBUG level log messages
|
||||
// display.
|
||||
mOldSettings(LLError::saveAndResetSettings()),
|
||||
mProxy(new RecorderProxy(this))
|
||||
{
|
||||
LLError::setFatalFunction(wouldHaveCrashed);
|
||||
LLError::setDefaultLevel(level);
|
||||
LLError::addRecorder(mProxy);
|
||||
}
|
||||
|
||||
~CaptureLog()
|
||||
{
|
||||
LLError::removeRecorder(mProxy);
|
||||
delete mProxy;
|
||||
LLError::restoreSettings(mOldSettings);
|
||||
}
|
||||
|
||||
void recordMessage(LLError::ELevel level,
|
||||
const std::string& message)
|
||||
{
|
||||
mMessages.push_back(message);
|
||||
}
|
||||
|
||||
/// Don't assume the message we want is necessarily the LAST log message
|
||||
/// emitted by the underlying code; search backwards through all messages
|
||||
/// for the sought string.
|
||||
std::string messageWith(const std::string& search, bool required=true)
|
||||
{
|
||||
for (MessageList::const_reverse_iterator rmi(mMessages.rbegin()), rmend(mMessages.rend());
|
||||
rmi != rmend; ++rmi)
|
||||
{
|
||||
if (rmi->find(search) != std::string::npos)
|
||||
return *rmi;
|
||||
}
|
||||
// failed to find any such message
|
||||
if (! required)
|
||||
return std::string();
|
||||
|
||||
throw tut::failure(STRINGIZE("failed to find '" << search
|
||||
<< "' in captured log messages:\n"
|
||||
<< boost::ref(*this)));
|
||||
}
|
||||
|
||||
std::ostream& streamto(std::ostream& out) const
|
||||
{
|
||||
MessageList::const_iterator mi(mMessages.begin()), mend(mMessages.end());
|
||||
if (mi != mend)
|
||||
{
|
||||
// handle first message separately: it doesn't get a newline
|
||||
out << *mi++;
|
||||
for ( ; mi != mend; ++mi)
|
||||
{
|
||||
// every subsequent message gets a newline
|
||||
out << '\n' << *mi;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
typedef std::list<std::string> MessageList;
|
||||
MessageList mMessages;
|
||||
LLError::Settings* mOldSettings;
|
||||
LLError::Recorder* mProxy;
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream& operator<<(std::ostream& out, const CaptureLog& log)
|
||||
{
|
||||
return log.streamto(out);
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_WRAPLLERRS_H) */
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ kdu_subband kdu_resolution::access_subband(int ) { kdu_subband a; return a; }
|
|||
void kdu_resolution::get_dims(kdu_dims& ) { }
|
||||
int kdu_resolution::which() { return 0; }
|
||||
int kdu_resolution::get_valid_band_indices(int &) { return 1; }
|
||||
//kdu_decoder::kdu_decoder(kdu_subband , kdu_sample_allocator*, bool , float, int, kdu_thread_env*, kdu_thread_queue*) { }
|
||||
kdu_synthesis::kdu_synthesis(kdu_resolution, kdu_sample_allocator*, bool, float, kdu_thread_env*, kdu_thread_queue*) { }
|
||||
kdu_params::kdu_params(const char*, bool, bool, bool, bool, bool) { }
|
||||
kdu_params::~kdu_params() { }
|
||||
|
|
|
|||
|
|
@ -548,6 +548,7 @@ LLCurl::Multi::Multi(F32 idle_time_out)
|
|||
mErrorCount(0),
|
||||
mState(STATE_READY),
|
||||
mDead(FALSE),
|
||||
mValid(TRUE),
|
||||
mMutexp(NULL),
|
||||
mDeletionMutexp(NULL),
|
||||
mEasyMutexp(NULL)
|
||||
|
|
@ -583,22 +584,33 @@ LLCurl::Multi::Multi(F32 idle_time_out)
|
|||
|
||||
LLCurl::Multi::~Multi()
|
||||
{
|
||||
cleanup() ;
|
||||
cleanup(true) ;
|
||||
|
||||
delete mDeletionMutexp ;
|
||||
mDeletionMutexp = NULL ;
|
||||
}
|
||||
|
||||
void LLCurl::Multi::cleanup()
|
||||
void LLCurl::Multi::cleanup(bool deleted)
|
||||
{
|
||||
if(!mCurlMultiHandle)
|
||||
{
|
||||
return ; //nothing to clean.
|
||||
}
|
||||
llassert_always(deleted || !mValid) ;
|
||||
|
||||
LLMutexLock lock(mDeletionMutexp);
|
||||
|
||||
// Clean up active
|
||||
for(easy_active_list_t::iterator iter = mEasyActiveList.begin();
|
||||
iter != mEasyActiveList.end(); ++iter)
|
||||
{
|
||||
Easy* easy = *iter;
|
||||
check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle()));
|
||||
|
||||
if(deleted)
|
||||
{
|
||||
easy->mResponder = NULL ; //avoid triggering mResponder.
|
||||
}
|
||||
delete easy;
|
||||
}
|
||||
mEasyActiveList.clear();
|
||||
|
|
@ -610,11 +622,9 @@ void LLCurl::Multi::cleanup()
|
|||
|
||||
check_curl_multi_code(LLCurl::deleteMultiHandle(mCurlMultiHandle));
|
||||
mCurlMultiHandle = NULL ;
|
||||
|
||||
|
||||
delete mMutexp ;
|
||||
mMutexp = NULL ;
|
||||
delete mDeletionMutexp ;
|
||||
mDeletionMutexp = NULL ;
|
||||
delete mEasyMutexp ;
|
||||
mEasyMutexp = NULL ;
|
||||
|
||||
|
|
@ -644,10 +654,20 @@ void LLCurl::Multi::unlock()
|
|||
|
||||
void LLCurl::Multi::markDead()
|
||||
{
|
||||
LLMutexLock lock(mDeletionMutexp) ;
|
||||
{
|
||||
LLMutexLock lock(mDeletionMutexp) ;
|
||||
|
||||
mDead = TRUE ;
|
||||
LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
|
||||
if(mCurlMultiHandle != NULL)
|
||||
{
|
||||
mDead = TRUE ;
|
||||
LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//not valid, delete it.
|
||||
delete this;
|
||||
}
|
||||
|
||||
void LLCurl::Multi::setState(LLCurl::Multi::ePerformState state)
|
||||
|
|
@ -741,10 +761,14 @@ bool LLCurl::Multi::doPerform()
|
|||
setState(STATE_COMPLETED) ;
|
||||
mIdleTimer.reset() ;
|
||||
}
|
||||
else if(mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
|
||||
else if(!mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
|
||||
{
|
||||
dead = true ;
|
||||
}
|
||||
else if(mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut - 1.f) //idle for too long, mark it invalid.
|
||||
{
|
||||
mValid = FALSE ;
|
||||
}
|
||||
|
||||
return dead ;
|
||||
}
|
||||
|
|
@ -966,6 +990,7 @@ void LLCurlThread::killMulti(LLCurl::Multi* multi)
|
|||
return ;
|
||||
}
|
||||
|
||||
<<<<<<< local
|
||||
if(multi->isValid())
|
||||
{
|
||||
multi->markDead() ;
|
||||
|
|
@ -974,6 +999,9 @@ void LLCurlThread::killMulti(LLCurl::Multi* multi)
|
|||
{
|
||||
deleteMulti(multi) ;
|
||||
}
|
||||
=======
|
||||
multi->markDead() ;
|
||||
>>>>>>> other
|
||||
}
|
||||
|
||||
//private
|
||||
|
|
@ -992,6 +1020,10 @@ void LLCurlThread::deleteMulti(LLCurl::Multi* multi)
|
|||
void LLCurlThread::cleanupMulti(LLCurl::Multi* multi)
|
||||
{
|
||||
multi->cleanup() ;
|
||||
if(multi->isDead()) //check if marked dead during cleaning up.
|
||||
{
|
||||
deleteMulti(multi) ;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ public:
|
|||
ePerformState getState() ;
|
||||
|
||||
bool isCompleted() ;
|
||||
bool isValid() {return mCurlMultiHandle != NULL ;}
|
||||
bool isValid() {return mCurlMultiHandle != NULL && mValid;}
|
||||
bool isDead() {return mDead;}
|
||||
|
||||
bool waitToComplete() ;
|
||||
|
|
@ -318,7 +318,7 @@ public:
|
|||
|
||||
private:
|
||||
void easyFree(LLCurl::Easy*);
|
||||
void cleanup() ;
|
||||
void cleanup(bool deleted = false) ;
|
||||
|
||||
CURLM* mCurlMultiHandle;
|
||||
|
||||
|
|
@ -333,6 +333,7 @@ private:
|
|||
ePerformState mState;
|
||||
|
||||
BOOL mDead ;
|
||||
BOOL mValid ;
|
||||
LLMutex* mMutexp ;
|
||||
LLMutex* mDeletionMutexp ;
|
||||
LLMutex* mEasyMutexp ;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
|
|
@ -72,43 +73,14 @@ namespace tut
|
|||
template<> template<>
|
||||
void llsdmessage_object::test<1>()
|
||||
{
|
||||
bool threw = false;
|
||||
std::string threw;
|
||||
// This should fail...
|
||||
try
|
||||
{
|
||||
LLSDMessage localListener;
|
||||
}
|
||||
catch (const LLEventPump::DupPumpName&)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
catch (const std::runtime_error& ex)
|
||||
{
|
||||
// This clause is because on Linux, on the viewer side, for this
|
||||
// one test program (though not others!), the
|
||||
// LLEventPump::DupPumpName exception isn't caught by the clause
|
||||
// above. Warn the user...
|
||||
std::cerr << "Failed to catch " << typeid(ex).name() << std::endl;
|
||||
// But if the expected exception was thrown, allow the test to
|
||||
// succeed anyway. Not sure how else to handle this odd case.
|
||||
if (std::string(typeid(ex).name()) == typeid(LLEventPump::DupPumpName).name())
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't even recognize this exception. Let it propagate
|
||||
// out to TUT to fail the test.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Utterly failed to catch expected exception!" << std::endl;
|
||||
// This case is full of fail. We HAVE to address it.
|
||||
throw;
|
||||
}
|
||||
ensure("second LLSDMessage should throw", threw);
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)
|
||||
ensure("second LLSDMessage should throw", ! threw.empty());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "llpluginprocessparent.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginmessageclasses.h"
|
||||
#include "stringize.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
|
|
@ -133,8 +134,8 @@ LLPluginProcessParent::~LLPluginProcessParent()
|
|||
// and remove it from our map
|
||||
mSharedMemoryRegions.erase(iter);
|
||||
}
|
||||
|
||||
mProcess.kill();
|
||||
|
||||
LLProcess::kill(mProcess);
|
||||
killSockets();
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +160,8 @@ void LLPluginProcessParent::errorState(void)
|
|||
|
||||
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug)
|
||||
{
|
||||
mProcess.setExecutable(launcher_filename);
|
||||
mProcess.setWorkingDirectory(plugin_dir);
|
||||
mProcessParams.executable = launcher_filename;
|
||||
mProcessParams.cwd = plugin_dir;
|
||||
mPluginFile = plugin_filename;
|
||||
mPluginDir = plugin_dir;
|
||||
mCPUUsage = 0.0f;
|
||||
|
|
@ -371,10 +372,8 @@ void LLPluginProcessParent::idle(void)
|
|||
// Launch the plugin process.
|
||||
|
||||
// Only argument to the launcher is the port number we're listening on
|
||||
std::stringstream stream;
|
||||
stream << mBoundPort;
|
||||
mProcess.addArgument(stream.str());
|
||||
if(mProcess.launch() != 0)
|
||||
mProcessParams.args.add(stringize(mBoundPort));
|
||||
if (! (mProcess = LLProcess::create(mProcessParams)))
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
|
|
@ -388,19 +387,18 @@ void LLPluginProcessParent::idle(void)
|
|||
// The command we're constructing would look like this on the command line:
|
||||
// osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
|
||||
|
||||
std::stringstream cmd;
|
||||
|
||||
mDebugger.setExecutable("/usr/bin/osascript");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("tell application \"Terminal\"");
|
||||
mDebugger.addArgument("-e");
|
||||
cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
|
||||
mDebugger.addArgument(cmd.str());
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("do script \"continue\" in win");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("end tell");
|
||||
mDebugger.launch();
|
||||
LLProcess::Params params;
|
||||
params.executable = "/usr/bin/osascript";
|
||||
params.args.add("-e");
|
||||
params.args.add("tell application \"Terminal\"");
|
||||
params.args.add("-e");
|
||||
params.args.add(STRINGIZE("set win to do script \"gdb -pid "
|
||||
<< mProcess->getProcessID() << "\""));
|
||||
params.args.add("-e");
|
||||
params.args.add("do script \"continue\" in win");
|
||||
params.args.add("-e");
|
||||
params.args.add("end tell");
|
||||
mDebugger = LLProcess::create(params);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
@ -470,7 +468,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_EXITING:
|
||||
if(!mProcess.isRunning())
|
||||
if (! LLProcess::isRunning(mProcess))
|
||||
{
|
||||
setState(STATE_CLEANUP);
|
||||
}
|
||||
|
|
@ -498,7 +496,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_CLEANUP:
|
||||
mProcess.kill();
|
||||
LLProcess::kill(mProcess);
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
|
@ -1077,7 +1075,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
|
|||
{
|
||||
bool result = false;
|
||||
|
||||
if(!mProcess.isRunning())
|
||||
if (! LLProcess::isRunning(mProcess))
|
||||
{
|
||||
LL_WARNS("Plugin") << "child exited" << LL_ENDL;
|
||||
result = true;
|
||||
|
|
|
|||
|
|
@ -30,13 +30,14 @@
|
|||
#define LL_LLPLUGINPROCESSPARENT_H
|
||||
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#include "llpluginmessage.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
#include "lliosocket.h"
|
||||
#include "llthread.h"
|
||||
#include "llsd.h"
|
||||
|
||||
class LLPluginProcessParentOwner
|
||||
{
|
||||
|
|
@ -139,26 +140,27 @@ private:
|
|||
};
|
||||
EState mState;
|
||||
void setState(EState state);
|
||||
|
||||
|
||||
bool pluginLockedUp();
|
||||
bool pluginLockedUpOrQuit();
|
||||
|
||||
bool accept();
|
||||
|
||||
|
||||
LLSocket::ptr_t mListenSocket;
|
||||
LLSocket::ptr_t mSocket;
|
||||
U32 mBoundPort;
|
||||
|
||||
LLProcessLauncher mProcess;
|
||||
|
||||
|
||||
LLProcess::Params mProcessParams;
|
||||
LLProcessPtr mProcess;
|
||||
|
||||
std::string mPluginFile;
|
||||
std::string mPluginDir;
|
||||
|
||||
LLPluginProcessParentOwner *mOwner;
|
||||
|
||||
|
||||
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
|
||||
sharedMemoryRegionsType mSharedMemoryRegions;
|
||||
|
||||
|
||||
LLSD mMessageClassVersions;
|
||||
std::string mPluginVersionString;
|
||||
|
||||
|
|
@ -171,7 +173,7 @@ private:
|
|||
bool mBlocked;
|
||||
bool mPolledInput;
|
||||
|
||||
LLProcessLauncher mDebugger;
|
||||
LLProcessPtr mDebugger;
|
||||
|
||||
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
|
||||
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ void APIENTRY gl_debug_callback(GLenum source,
|
|||
llwarns << "Severity: " << std::hex << severity << llendl;
|
||||
llwarns << "Message: " << message << llendl;
|
||||
llwarns << "-----------------------" << llendl;
|
||||
if (severity == GL_DEBUG_SEVERITY_HIGH_ARB)
|
||||
{
|
||||
llerrs << "Halting on GL Error" << llendl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -572,6 +576,15 @@ bool LLGLManager::initGL()
|
|||
#endif
|
||||
}
|
||||
|
||||
if (mGLVersion >= 2.1f && LLImageGL::sCompressTextures)
|
||||
{ //use texture compression
|
||||
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
|
||||
}
|
||||
else
|
||||
{ //GL version is < 3.0, always disable texture compression
|
||||
LLImageGL::sCompressTextures = false;
|
||||
}
|
||||
|
||||
// Trailing space necessary to keep "nVidia Corpor_ati_on" cards
|
||||
// from being recognized as ATI.
|
||||
if (mGLVendor.substr(0,4) == "ATI ")
|
||||
|
|
@ -592,11 +605,8 @@ bool LLGLManager::initGL()
|
|||
#endif // LL_WINDOWS
|
||||
|
||||
#if (LL_WINDOWS || LL_LINUX) && !LL_MESA_HEADLESS
|
||||
// release 7277 is a point at which we verify that ATI OpenGL
|
||||
// drivers get pretty stable with SL, ~Catalyst 8.2,
|
||||
// for both Win32 and Linux.
|
||||
if (mDriverVersionRelease < 7277 &&
|
||||
mDriverVersionRelease != 0) // 0 == Undetectable driver version - these get to pretend to be new ATI drivers, though that decision may be revisited.
|
||||
// count any pre OpenGL 3.0 implementation as an old driver
|
||||
if (mGLVersion < 3.f)
|
||||
{
|
||||
mATIOldDriver = TRUE;
|
||||
}
|
||||
|
|
@ -735,6 +745,11 @@ bool LLGLManager::initGL()
|
|||
}
|
||||
#endif
|
||||
|
||||
if (mIsIntel && mGLVersion <= 3.f)
|
||||
{ //never try to use framebuffer objects on older intel drivers (crashy)
|
||||
mHasFramebufferObject = FALSE;
|
||||
}
|
||||
|
||||
if (mHasFramebufferObject)
|
||||
{
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &mMaxSamples);
|
||||
|
|
@ -1897,7 +1912,7 @@ void LLGLState::checkClientArrays(const std::string& msg, U32 data_mask)
|
|||
glClientActiveTextureARB(GL_TEXTURE0_ARB);
|
||||
gGL.getTexUnit(0)->activate();
|
||||
|
||||
if (gGLManager.mHasVertexShader)
|
||||
if (gGLManager.mHasVertexShader && LLGLSLShader::sNoFixedFunction)
|
||||
{ //make sure vertex attribs are all disabled
|
||||
GLint count;
|
||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS_ARB, &count);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ BOOL LLImageGL::sGlobalUseAnisotropic = FALSE;
|
|||
F32 LLImageGL::sLastFrameTime = 0.f;
|
||||
BOOL LLImageGL::sAllowReadBackRaw = FALSE ;
|
||||
LLImageGL* LLImageGL::sDefaultGLTexture = NULL ;
|
||||
bool LLImageGL::sCompressTextures = false;
|
||||
|
||||
std::set<LLImageGL*> LLImageGL::sImageList;
|
||||
|
||||
|
|
@ -409,6 +410,8 @@ void LLImageGL::init(BOOL usemipmaps)
|
|||
mDiscardLevelInAtlas = -1 ;
|
||||
mTexelsInAtlas = 0 ;
|
||||
mTexelsInGLTexture = 0 ;
|
||||
|
||||
mAllowCompression = true;
|
||||
|
||||
mTarget = GL_TEXTURE_2D;
|
||||
mBindTarget = LLTexUnit::TT_TEXTURE;
|
||||
|
|
@ -637,7 +640,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
stop_glerror();
|
||||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in);
|
||||
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in, mAllowCompression);
|
||||
if (gl_level == 0)
|
||||
{
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
|
@ -679,7 +682,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
LLImageGL::setManualImage(mTarget, 0, mFormatInternal,
|
||||
w, h,
|
||||
mFormatPrimary, mFormatType,
|
||||
data_in);
|
||||
data_in, mAllowCompression);
|
||||
analyzeAlpha(data_in, w, h);
|
||||
stop_glerror();
|
||||
|
||||
|
|
@ -737,7 +740,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
stop_glerror();
|
||||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data);
|
||||
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data, mAllowCompression);
|
||||
if (m == 0)
|
||||
{
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
|
@ -795,7 +798,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h,
|
||||
mFormatPrimary, mFormatType, (GLvoid *)data_in);
|
||||
mFormatPrimary, mFormatType, (GLvoid *)data_in, mAllowCompression);
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
||||
updatePickMask(w, h, data_in);
|
||||
|
|
@ -1042,7 +1045,7 @@ void LLImageGL::deleteTextures(S32 numTextures, U32 *textures, bool immediate)
|
|||
}
|
||||
|
||||
// static
|
||||
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels)
|
||||
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression)
|
||||
{
|
||||
bool use_scratch = false;
|
||||
U32* scratch = NULL;
|
||||
|
|
@ -1105,6 +1108,36 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
|
|||
}
|
||||
}
|
||||
|
||||
if (LLImageGL::sCompressTextures && allow_compression)
|
||||
{
|
||||
switch (intformat)
|
||||
{
|
||||
case GL_RGB:
|
||||
case GL_RGB8:
|
||||
intformat = GL_COMPRESSED_RGB;
|
||||
break;
|
||||
case GL_RGBA:
|
||||
case GL_RGBA8:
|
||||
intformat = GL_COMPRESSED_RGBA;
|
||||
break;
|
||||
case GL_LUMINANCE:
|
||||
case GL_LUMINANCE8:
|
||||
intformat = GL_COMPRESSED_LUMINANCE;
|
||||
break;
|
||||
case GL_LUMINANCE_ALPHA:
|
||||
case GL_LUMINANCE8_ALPHA8:
|
||||
intformat = GL_COMPRESSED_LUMINANCE_ALPHA;
|
||||
break;
|
||||
case GL_ALPHA:
|
||||
case GL_ALPHA8:
|
||||
intformat = GL_COMPRESSED_ALPHA;
|
||||
break;
|
||||
default:
|
||||
llwarns << "Could not compress format: " << std::hex << intformat << llendl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stop_glerror();
|
||||
glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, use_scratch ? scratch : pixels);
|
||||
stop_glerror();
|
||||
|
|
|
|||
|
|
@ -94,12 +94,13 @@ public:
|
|||
|
||||
void setSize(S32 width, S32 height, S32 ncomponents);
|
||||
void setComponents(S32 ncomponents) { mComponents = (S8)ncomponents ;}
|
||||
void setAllowCompression(bool allow) { mAllowCompression = allow; }
|
||||
|
||||
// These 3 functions currently wrap glGenTextures(), glDeleteTextures(), and glTexImage2D()
|
||||
// for tracking purposes and will be deprecated in the future
|
||||
static void generateTextures(S32 numTextures, U32 *textures);
|
||||
static void deleteTextures(S32 numTextures, U32 *textures, bool immediate = false);
|
||||
static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels);
|
||||
static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression = true);
|
||||
|
||||
BOOL createGLTexture() ;
|
||||
BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE,
|
||||
|
|
@ -210,6 +211,8 @@ private:
|
|||
U32 mTexelsInAtlas ;
|
||||
U32 mTexelsInGLTexture;
|
||||
|
||||
bool mAllowCompression;
|
||||
|
||||
protected:
|
||||
LLGLenum mTarget; // Normally GL_TEXTURE2D, sometimes something else (ex. cube maps)
|
||||
LLTexUnit::eTextureType mBindTarget; // Normally TT_TEXTURE, sometimes something else (ex. cube maps)
|
||||
|
|
@ -245,7 +248,7 @@ public:
|
|||
static BOOL sGlobalUseAnisotropic;
|
||||
static LLImageGL* sDefaultGLTexture ;
|
||||
static BOOL sAutomatedTest;
|
||||
|
||||
static bool sCompressTextures; //use GL texture compression
|
||||
#if DEBUG_MISS
|
||||
BOOL mMissed; // Missed on last bind?
|
||||
BOOL getMissed() const { return mMissed; };
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
|
|||
|
||||
{
|
||||
clear_glerror();
|
||||
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL, false);
|
||||
if (glGetError() != GL_NO_ERROR)
|
||||
{
|
||||
llwarns << "Could not allocate color buffer for render target." << llendl;
|
||||
|
|
@ -223,7 +223,7 @@ bool LLRenderTarget::allocateDepth()
|
|||
U32 internal_type = LLTexUnit::getInternalType(mUsage);
|
||||
stop_glerror();
|
||||
clear_glerror();
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1062,8 +1062,9 @@ void LLShaderMgr::initAttribsAndUniforms()
|
|||
mReservedUniforms.push_back("proj_shadow_res");
|
||||
mReservedUniforms.push_back("depth_cutoff");
|
||||
mReservedUniforms.push_back("norm_cutoff");
|
||||
mReservedUniforms.push_back("shadow_target_width");
|
||||
|
||||
llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_NORM_CUTOFF+1);
|
||||
llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH+1);
|
||||
|
||||
mReservedUniforms.push_back("tc_scale");
|
||||
mReservedUniforms.push_back("rcp_screen_res");
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ public:
|
|||
DEFERRED_PROJ_SHADOW_RES,
|
||||
DEFERRED_DEPTH_CUTOFF,
|
||||
DEFERRED_NORM_CUTOFF,
|
||||
DEFERRED_SHADOW_TARGET_WIDTH,
|
||||
|
||||
FXAA_TC_SCALE,
|
||||
FXAA_RCP_SCREEN_RES,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@
|
|||
#include "llglslshader.h"
|
||||
#include "llmemory.h"
|
||||
|
||||
#if LL_DARWIN
|
||||
#define LL_VBO_POOLING 1
|
||||
#else
|
||||
#define LL_VBO_POOLING 0
|
||||
#endif
|
||||
|
||||
//Next Highest Power Of Two
|
||||
//helper function, returns first number > v that is a power of 2, or v if v is already a power of 2
|
||||
U32 nhpo2(U32 v)
|
||||
|
|
@ -49,6 +55,35 @@ U32 nhpo2(U32 v)
|
|||
return r;
|
||||
}
|
||||
|
||||
//which power of 2 is i?
|
||||
//assumes i is a power of 2 > 0
|
||||
U32 wpo2(U32 i)
|
||||
{
|
||||
llassert(i > 0);
|
||||
llassert(nhpo2(i) == i);
|
||||
|
||||
U32 r = 0;
|
||||
|
||||
while (i >>= 1) ++r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
const U32 LL_VBO_BLOCK_SIZE = 2048;
|
||||
|
||||
U32 vbo_block_size(U32 size)
|
||||
{ //what block size will fit size?
|
||||
U32 mod = size % LL_VBO_BLOCK_SIZE;
|
||||
return mod == 0 ? size : size + (LL_VBO_BLOCK_SIZE-mod);
|
||||
}
|
||||
|
||||
U32 vbo_block_index(U32 size)
|
||||
{
|
||||
return vbo_block_size(size)/LL_VBO_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
|
@ -57,7 +92,11 @@ LLVBOPool LLVertexBuffer::sStreamVBOPool(GL_STREAM_DRAW_ARB, GL_ARRAY_BUFFER_ARB
|
|||
LLVBOPool LLVertexBuffer::sDynamicVBOPool(GL_DYNAMIC_DRAW_ARB, GL_ARRAY_BUFFER_ARB);
|
||||
LLVBOPool LLVertexBuffer::sStreamIBOPool(GL_STREAM_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB);
|
||||
LLVBOPool LLVertexBuffer::sDynamicIBOPool(GL_DYNAMIC_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB);
|
||||
|
||||
U32 LLVBOPool::sBytesPooled = 0;
|
||||
U32 LLVBOPool::sIndexBytesPooled = 0;
|
||||
U32 LLVertexBuffer::sAllocatedIndexBytes = 0;
|
||||
U32 LLVertexBuffer::sIndexCount = 0;
|
||||
|
||||
LLPrivateMemoryPool* LLVertexBuffer::sPrivatePoolp = NULL;
|
||||
U32 LLVertexBuffer::sBindCount = 0;
|
||||
|
|
@ -74,6 +113,7 @@ U32 LLVertexBuffer::sLastMask = 0;
|
|||
bool LLVertexBuffer::sVBOActive = false;
|
||||
bool LLVertexBuffer::sIBOActive = false;
|
||||
U32 LLVertexBuffer::sAllocatedBytes = 0;
|
||||
U32 LLVertexBuffer::sVertexCount = 0;
|
||||
bool LLVertexBuffer::sMapped = false;
|
||||
bool LLVertexBuffer::sUseStreamDraw = true;
|
||||
bool LLVertexBuffer::sUseVAO = false;
|
||||
|
|
@ -134,39 +174,35 @@ public:
|
|||
};
|
||||
|
||||
|
||||
//which power of 2 is i?
|
||||
//assumes i is a power of 2 > 0
|
||||
U32 wpo2(U32 i)
|
||||
{
|
||||
llassert(i > 0);
|
||||
llassert(nhpo2(i) == i);
|
||||
|
||||
U32 r = 0;
|
||||
|
||||
while (i >>= 1) ++r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
volatile U8* LLVBOPool::allocate(U32& name, U32 size)
|
||||
{
|
||||
llassert(nhpo2(size) == size);
|
||||
llassert(vbo_block_size(size) == size);
|
||||
|
||||
volatile U8* ret = NULL;
|
||||
|
||||
U32 i = wpo2(size);
|
||||
#if LL_VBO_POOLING
|
||||
|
||||
U32 i = vbo_block_index(size);
|
||||
|
||||
if (mFreeList.size() <= i)
|
||||
{
|
||||
mFreeList.resize(i+1);
|
||||
}
|
||||
|
||||
volatile U8* ret = NULL;
|
||||
|
||||
if (mFreeList[i].empty())
|
||||
{
|
||||
//make a new buffer
|
||||
glGenBuffersARB(1, &name);
|
||||
glBindBufferARB(mType, name);
|
||||
LLVertexBuffer::sAllocatedBytes += size;
|
||||
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
LLVertexBuffer::sAllocatedBytes += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVertexBuffer::sAllocatedIndexBytes += size;
|
||||
}
|
||||
|
||||
if (LLVertexBuffer::sDisableVBOMapping || mUsage != GL_DYNAMIC_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -185,19 +221,55 @@ volatile U8* LLVBOPool::allocate(U32& name, U32 size)
|
|||
name = mFreeList[i].front().mGLName;
|
||||
ret = mFreeList[i].front().mClientData;
|
||||
|
||||
sBytesPooled -= size;
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled -= size;
|
||||
}
|
||||
|
||||
mFreeList[i].pop_front();
|
||||
}
|
||||
#else //no pooling
|
||||
|
||||
glGenBuffersARB(1, &name);
|
||||
glBindBufferARB(mType, name);
|
||||
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
LLVertexBuffer::sAllocatedBytes += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVertexBuffer::sAllocatedIndexBytes += size;
|
||||
}
|
||||
|
||||
if (LLVertexBuffer::sDisableVBOMapping || mUsage != GL_DYNAMIC_DRAW_ARB)
|
||||
{
|
||||
glBufferDataARB(mType, size, 0, mUsage);
|
||||
ret = (U8*) ll_aligned_malloc_16(size);
|
||||
}
|
||||
else
|
||||
{ //always use a true hint of static draw when allocating non-client-backed buffers
|
||||
glBufferDataARB(mType, size, 0, GL_STATIC_DRAW_ARB);
|
||||
}
|
||||
|
||||
glBindBufferARB(mType, 0);
|
||||
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LLVBOPool::release(U32 name, volatile U8* buffer, U32 size)
|
||||
{
|
||||
llassert(nhpo2(size) == size);
|
||||
llassert(vbo_block_size(size) == size);
|
||||
|
||||
U32 i = wpo2(size);
|
||||
#if LL_VBO_POOLING
|
||||
|
||||
U32 i = vbo_block_index(size);
|
||||
|
||||
llassert(mFreeList.size() > i);
|
||||
|
||||
|
|
@ -211,9 +283,29 @@ void LLVBOPool::release(U32 name, volatile U8* buffer, U32 size)
|
|||
}
|
||||
else
|
||||
{
|
||||
sBytesPooled += size;
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled += size;
|
||||
}
|
||||
mFreeList[i].push_back(rec);
|
||||
}
|
||||
#else //no pooling
|
||||
glDeleteBuffersARB(1, &name);
|
||||
ll_aligned_free_16((U8*) buffer);
|
||||
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVertexBuffer::sAllocatedIndexBytes -= size;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LLVBOPool::cleanup()
|
||||
|
|
@ -237,8 +329,16 @@ void LLVBOPool::cleanup()
|
|||
|
||||
l.pop_front();
|
||||
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
sBytesPooled -= size;
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled -= size;
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled -= size;
|
||||
LLVertexBuffer::sAllocatedIndexBytes -= size;
|
||||
}
|
||||
}
|
||||
|
||||
size *= 2;
|
||||
|
|
@ -898,6 +998,9 @@ LLVertexBuffer::~LLVertexBuffer()
|
|||
|
||||
mFence = NULL;
|
||||
|
||||
sVertexCount -= mNumVerts;
|
||||
sIndexCount -= mNumIndices;
|
||||
|
||||
llassert_always(!mMappedData && !mMappedIndexData);
|
||||
};
|
||||
|
||||
|
|
@ -929,7 +1032,7 @@ void LLVertexBuffer::waitFence() const
|
|||
|
||||
void LLVertexBuffer::genBuffer(U32 size)
|
||||
{
|
||||
mSize = nhpo2(size);
|
||||
mSize = vbo_block_size(size);
|
||||
|
||||
if (mUsage == GL_STREAM_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -945,7 +1048,7 @@ void LLVertexBuffer::genBuffer(U32 size)
|
|||
|
||||
void LLVertexBuffer::genIndices(U32 size)
|
||||
{
|
||||
mIndicesSize = nhpo2(size);
|
||||
mIndicesSize = vbo_block_size(size);
|
||||
|
||||
if (mUsage == GL_STREAM_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -1121,7 +1224,9 @@ void LLVertexBuffer::updateNumVerts(S32 nverts)
|
|||
createGLBuffer(needed_size);
|
||||
}
|
||||
|
||||
sVertexCount -= mNumVerts;
|
||||
mNumVerts = nverts;
|
||||
sVertexCount += mNumVerts;
|
||||
}
|
||||
|
||||
void LLVertexBuffer::updateNumIndices(S32 nindices)
|
||||
|
|
@ -1137,7 +1242,9 @@ void LLVertexBuffer::updateNumIndices(S32 nindices)
|
|||
createGLIndices(needed_size);
|
||||
}
|
||||
|
||||
sIndexCount -= mNumIndices;
|
||||
mNumIndices = nindices;
|
||||
sIndexCount += mNumIndices;
|
||||
}
|
||||
|
||||
void LLVertexBuffer::allocateBuffer(S32 nverts, S32 nindices, bool create)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class LLVBOPool
|
|||
{
|
||||
public:
|
||||
static U32 sBytesPooled;
|
||||
static U32 sIndexBytesPooled;
|
||||
|
||||
LLVBOPool(U32 vboUsage, U32 vboType)
|
||||
: mUsage(vboUsage)
|
||||
|
|
@ -332,6 +333,9 @@ public:
|
|||
static bool sIBOActive;
|
||||
static U32 sLastMask;
|
||||
static U32 sAllocatedBytes;
|
||||
static U32 sAllocatedIndexBytes;
|
||||
static U32 sVertexCount;
|
||||
static U32 sIndexCount;
|
||||
static U32 sBindCount;
|
||||
static U32 sSetCount;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ include(LLRender)
|
|||
include(LLWindow)
|
||||
include(LLVFS)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
|
|
@ -24,7 +23,6 @@ include_directories(
|
|||
${LLWINDOW_INCLUDE_DIRS}
|
||||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(llui_SOURCE_FILES
|
||||
|
|
@ -83,7 +81,6 @@ set(llui_SOURCE_FILES
|
|||
llscrolllistcolumn.cpp
|
||||
llscrolllistctrl.cpp
|
||||
llscrolllistitem.cpp
|
||||
llsdparam.cpp
|
||||
llsearcheditor.cpp
|
||||
llslider.cpp
|
||||
llsliderctrl.cpp
|
||||
|
|
@ -100,11 +97,13 @@ set(llui_SOURCE_FILES
|
|||
lltextutil.cpp
|
||||
lltextvalidate.cpp
|
||||
lltimectrl.cpp
|
||||
lltrans.cpp
|
||||
lltransutil.cpp
|
||||
lltoggleablemenu.cpp
|
||||
lltoolbar.cpp
|
||||
lltooltip.cpp
|
||||
llui.cpp
|
||||
lluicolor.cpp
|
||||
lluicolortable.cpp
|
||||
lluictrl.cpp
|
||||
lluictrlfactory.cpp
|
||||
|
|
@ -121,6 +120,7 @@ set(llui_SOURCE_FILES
|
|||
llview.cpp
|
||||
llviewquery.cpp
|
||||
llwindowshade.cpp
|
||||
llxuiparser.cpp
|
||||
)
|
||||
|
||||
set(llui_HEADER_FILES
|
||||
|
|
@ -189,7 +189,6 @@ set(llui_HEADER_FILES
|
|||
llscrolllistcolumn.h
|
||||
llscrolllistctrl.h
|
||||
llscrolllistitem.h
|
||||
llsdparam.h
|
||||
llsliderctrl.h
|
||||
llslider.h
|
||||
llspinctrl.h
|
||||
|
|
@ -208,6 +207,7 @@ set(llui_HEADER_FILES
|
|||
lltoggleablemenu.h
|
||||
lltoolbar.h
|
||||
lltooltip.h
|
||||
lltrans.h
|
||||
lltransutil.h
|
||||
lluicolortable.h
|
||||
lluiconstants.h
|
||||
|
|
@ -215,6 +215,7 @@ set(llui_HEADER_FILES
|
|||
lluictrl.h
|
||||
lluifwd.h
|
||||
llui.h
|
||||
lluicolor.h
|
||||
lluiimage.h
|
||||
lluistring.h
|
||||
llundo.h
|
||||
|
|
@ -228,6 +229,7 @@ set(llui_HEADER_FILES
|
|||
llview.h
|
||||
llviewquery.h
|
||||
llwindowshade.h
|
||||
llxuiparser.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${llui_HEADER_FILES}
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ void LLMultiFloater::setVisible(BOOL visible)
|
|||
|
||||
BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask)
|
||||
{
|
||||
if (key == 'W' && mask == (MASK_CONTROL|MASK_SHIFT))
|
||||
if (key == 'W' && mask == MASK_CONTROL)
|
||||
{
|
||||
LLFloater* floater = getActiveFloater();
|
||||
// is user closeable and is system closeable
|
||||
|
|
|
|||
|
|
@ -399,6 +399,7 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par
|
|||
: mName(p.name),
|
||||
mType(p.type),
|
||||
mMessage(p.value),
|
||||
mFooter(p.footer.value),
|
||||
mLabel(p.label),
|
||||
mIcon(p.icon),
|
||||
mURL(p.url.value),
|
||||
|
|
@ -870,6 +871,16 @@ std::string LLNotification::getMessage() const
|
|||
return message;
|
||||
}
|
||||
|
||||
std::string LLNotification::getFooter() const
|
||||
{
|
||||
if (!mTemplatep)
|
||||
return std::string();
|
||||
|
||||
std::string footer = mTemplatep->mFooter;
|
||||
LLStringUtil::format(footer, mSubstitutions);
|
||||
return footer;
|
||||
}
|
||||
|
||||
std::string LLNotification::getLabel() const
|
||||
{
|
||||
std::string label = mTemplatep->mLabel;
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@ public:
|
|||
|
||||
std::string getType() const;
|
||||
std::string getMessage() const;
|
||||
std::string getFooter() const;
|
||||
std::string getLabel() const;
|
||||
std::string getURL() const;
|
||||
S32 getURLOption() const;
|
||||
|
|
|
|||
|
|
@ -167,6 +167,17 @@ struct LLNotificationTemplate
|
|||
{}
|
||||
};
|
||||
|
||||
struct Footer : public LLInitParam::Block<Footer>
|
||||
{
|
||||
Mandatory<std::string> value;
|
||||
|
||||
Footer()
|
||||
: value("value")
|
||||
{
|
||||
addSynonym(value, "");
|
||||
}
|
||||
};
|
||||
|
||||
struct Params : public LLInitParam::Block<Params>
|
||||
{
|
||||
Mandatory<std::string> name;
|
||||
|
|
@ -184,7 +195,8 @@ struct LLNotificationTemplate
|
|||
Optional<FormRef> form_ref;
|
||||
Optional<ENotificationPriority,
|
||||
NotificationPriorityValues> priority;
|
||||
Multiple<Tag> tags;
|
||||
Multiple<Tag> tags;
|
||||
Optional<Footer> footer;
|
||||
|
||||
|
||||
Params()
|
||||
|
|
@ -202,7 +214,8 @@ struct LLNotificationTemplate
|
|||
url("url"),
|
||||
unique("unique"),
|
||||
form_ref(""),
|
||||
tags("tag")
|
||||
tags("tag"),
|
||||
footer("footer")
|
||||
{}
|
||||
|
||||
};
|
||||
|
|
@ -231,6 +244,8 @@ struct LLNotificationTemplate
|
|||
// The text used to display the notification. Replaceable parameters
|
||||
// are enclosed in square brackets like this [].
|
||||
std::string mMessage;
|
||||
// The text used to display the notification, but under the form.
|
||||
std::string mFooter;
|
||||
// The label for the notification; used for
|
||||
// certain classes of notification (those with a window and a window title).
|
||||
// Also used when a notification pops up underneath the current one.
|
||||
|
|
|
|||
|
|
@ -105,28 +105,6 @@ LLStyle::Params::Params()
|
|||
|
||||
namespace LLInitParam
|
||||
{
|
||||
Param::Param(BaseBlock* enclosing_block)
|
||||
: mIsProvided(false)
|
||||
{
|
||||
const U8* my_addr = reinterpret_cast<const U8*>(this);
|
||||
const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block);
|
||||
mEnclosingBlockOffset = (U16)(my_addr - block_addr);
|
||||
}
|
||||
|
||||
void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){}
|
||||
void BaseBlock::addSynonym(Param& param, const std::string& synonym) {}
|
||||
param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;}
|
||||
|
||||
void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size)
|
||||
{
|
||||
descriptor.mCurrentBlockPtr = this;
|
||||
}
|
||||
bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; }
|
||||
void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {}
|
||||
bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_value, S32 max_value) const { return true; }
|
||||
bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; }
|
||||
bool BaseBlock::validateBlock(bool emit_errors) const { return true; }
|
||||
|
||||
ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color)
|
||||
: super_t(color)
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -70,21 +70,6 @@ S32 LLUIImage::getHeight() const
|
|||
return 0;
|
||||
}
|
||||
|
||||
namespace LLInitParam
|
||||
{
|
||||
BlockDescriptor::BlockDescriptor() {}
|
||||
ParamDescriptor::ParamDescriptor(param_handle_t p,
|
||||
merge_func_t merge_func,
|
||||
deserialize_func_t deserialize_func,
|
||||
serialize_func_t serialize_func,
|
||||
validation_func_t validation_func,
|
||||
inspect_func_t inspect_func,
|
||||
S32 min_count,
|
||||
S32 max_count){}
|
||||
ParamDescriptor::~ParamDescriptor() {}
|
||||
|
||||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
struct LLUrlEntryData
|
||||
|
|
|
|||
|
|
@ -63,40 +63,6 @@ S32 LLUIImage::getHeight() const
|
|||
|
||||
namespace LLInitParam
|
||||
{
|
||||
BlockDescriptor::BlockDescriptor() {}
|
||||
ParamDescriptor::ParamDescriptor(param_handle_t p,
|
||||
merge_func_t merge_func,
|
||||
deserialize_func_t deserialize_func,
|
||||
serialize_func_t serialize_func,
|
||||
validation_func_t validation_func,
|
||||
inspect_func_t inspect_func,
|
||||
S32 min_count,
|
||||
S32 max_count){}
|
||||
ParamDescriptor::~ParamDescriptor() {}
|
||||
|
||||
void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name){}
|
||||
param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;}
|
||||
void BaseBlock::addSynonym(Param& param, const std::string& synonym) {}
|
||||
|
||||
void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size)
|
||||
{
|
||||
descriptor.mCurrentBlockPtr = this;
|
||||
}
|
||||
|
||||
Param::Param(BaseBlock* enclosing_block)
|
||||
: mIsProvided(false)
|
||||
{
|
||||
const U8* my_addr = reinterpret_cast<const U8*>(this);
|
||||
const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block);
|
||||
mEnclosingBlockOffset = 0x7FFFffff & ((U32)(my_addr - block_addr));
|
||||
}
|
||||
|
||||
bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; }
|
||||
void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {}
|
||||
bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const { return true; }
|
||||
bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; }
|
||||
bool BaseBlock::validateBlock(bool emit_errors) const { return true; }
|
||||
|
||||
ParamValue<LLUIColor, TypeValues<LLUIColor> >::ParamValue(const LLUIColor& color)
|
||||
: super_t(color)
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,13 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask)
|
|||
std::string fullpath;
|
||||
S32 result;
|
||||
|
||||
// File masks starting with "/" will match nothing, so we consider them invalid.
|
||||
if (LLStringUtil::startsWith(mask, getDirDelimiter()))
|
||||
{
|
||||
llwarns << "Invalid file mask: " << mask << llendl;
|
||||
llassert(!"Invalid file mask");
|
||||
}
|
||||
|
||||
LLDirIterator iter(dirname, mask);
|
||||
while (iter.next(filename))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1477,7 +1477,8 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen &size, BO
|
|||
}
|
||||
else
|
||||
{
|
||||
llinfos << "Created OpenGL " << llformat("%d.%d", attribs[1], attribs[3]) << " context." << llendl;
|
||||
llinfos << "Created OpenGL " << llformat("%d.%d", attribs[1], attribs[3]) <<
|
||||
(LLRender::sGLCoreProfile ? " core" : " compatibility") << " context." << llendl;
|
||||
done = true;
|
||||
|
||||
if (LLRender::sGLCoreProfile)
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
# -*- cmake -*-
|
||||
|
||||
project(llxuixml)
|
||||
|
||||
include(00-Common)
|
||||
include(LLCommon)
|
||||
include(LLMath)
|
||||
include(LLXML)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
${LLMATH_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(llxuixml_SOURCE_FILES
|
||||
llinitparam.cpp
|
||||
lltrans.cpp
|
||||
lluicolor.cpp
|
||||
llxuiparser.cpp
|
||||
)
|
||||
|
||||
set(llxuixml_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
|
||||
llinitparam.h
|
||||
lltrans.h
|
||||
llregistry.h
|
||||
lluicolor.h
|
||||
llxuiparser.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${llxuixml_HEADER_FILES}
|
||||
PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
list(APPEND llxuixml_SOURCE_FILES ${llxuixml_HEADER_FILES})
|
||||
|
||||
add_library (llxuixml ${llxuixml_SOURCE_FILES})
|
||||
# Libraries on which this library depends, needed for Linux builds
|
||||
# Sort by high-level to low-level
|
||||
target_link_libraries(llxuixml
|
||||
llxml
|
||||
llcommon
|
||||
llmath
|
||||
)
|
||||
|
|
@ -1188,9 +1188,7 @@ void *updatethreadproc(void*)
|
|||
|
||||
llinfos << "Clearing cache..." << llendl;
|
||||
|
||||
char mask[LL_MAX_PATH]; /* Flawfinder: ignore */
|
||||
snprintf(mask, LL_MAX_PATH, "%s*.*", gDirUtilp->getDirDelimiter().c_str());
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask);
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*");
|
||||
|
||||
llinfos << "Clear complete." << llendl;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ include(LLUI)
|
|||
include(LLVFS)
|
||||
include(LLWindow)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(LScript)
|
||||
include(Linking)
|
||||
include(NDOF)
|
||||
|
|
@ -66,7 +65,6 @@ include_directories(
|
|||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLWINDOW_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
${LSCRIPT_INCLUDE_DIRS}
|
||||
${LSCRIPT_INCLUDE_DIRS}/lscript_compile
|
||||
${LLLOGIN_INCLUDE_DIRS}
|
||||
|
|
@ -237,7 +235,7 @@ set(viewer_SOURCE_FILES
|
|||
llfloatertelehub.cpp
|
||||
llfloatertestinspectors.cpp
|
||||
llfloatertestlistview.cpp
|
||||
llfloatertexturefetchdebugger.cpp
|
||||
llfloatertexturefetchdebugger.cpp
|
||||
llfloatertools.cpp
|
||||
llfloatertopobjects.cpp
|
||||
llfloatertos.cpp
|
||||
|
|
@ -491,6 +489,7 @@ set(viewer_SOURCE_FILES
|
|||
lltoastnotifypanel.cpp
|
||||
lltoastpanel.cpp
|
||||
lltoastscripttextbox.cpp
|
||||
lltoastscriptquestion.cpp
|
||||
lltool.cpp
|
||||
lltoolbarview.cpp
|
||||
lltoolbrush.cpp
|
||||
|
|
@ -794,7 +793,7 @@ set(viewer_HEADER_FILES
|
|||
llfloatertelehub.h
|
||||
llfloatertestinspectors.h
|
||||
llfloatertestlistview.h
|
||||
llfloatertexturefetchdebugger.h
|
||||
llfloatertexturefetchdebugger.h
|
||||
llfloatertools.h
|
||||
llfloatertopobjects.h
|
||||
llfloatertos.h
|
||||
|
|
@ -1039,6 +1038,7 @@ set(viewer_HEADER_FILES
|
|||
lltoastnotifypanel.h
|
||||
lltoastpanel.h
|
||||
lltoastscripttextbox.h
|
||||
lltoastscriptquestion.h
|
||||
lltool.h
|
||||
lltoolbarview.h
|
||||
lltoolbrush.h
|
||||
|
|
@ -1748,7 +1748,6 @@ target_link_libraries(${VIEWER_BINARY_NAME}
|
|||
${LLVFS_LIBRARIES}
|
||||
${LLWINDOW_LIBRARIES}
|
||||
${LLXML_LIBRARIES}
|
||||
${LLXUIXML_LIBRARIES}
|
||||
${LSCRIPT_LIBRARIES}
|
||||
${LLMATH_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,76 @@
|
|||
<?xml version="1.0"?>
|
||||
<llsd>
|
||||
<map>
|
||||
<key>help</key>
|
||||
<!-- Please insert new keys in alphabetical order. -->
|
||||
<key>analyzeperformance</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>display this help message</string>
|
||||
|
||||
<key>short</key>
|
||||
<string>h</string>
|
||||
<string>When used in conjunction with logperformance, analyzes result of log against baseline.</string>
|
||||
<key>map-to</key>
|
||||
<string>AnalyzePerformance</string>
|
||||
</map>
|
||||
|
||||
<key>port</key>
|
||||
<key>autologin</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>log in as last saved user</string>
|
||||
<key>map-to</key>
|
||||
<string>AutoLogin</string>
|
||||
</map>
|
||||
|
||||
<key>channel</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<!-- Special case. Not mapped to a setting. -->
|
||||
</map>
|
||||
|
||||
<key>console</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserConnectionPort</string>
|
||||
<string>ShowConsoleWindow</string>
|
||||
</map>
|
||||
|
||||
<key>cooperative</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Yield some idle time to local host.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>YieldTime</string>
|
||||
</map>
|
||||
|
||||
<key>crashonstartup</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Crashes on startup. For QA use.</string>
|
||||
<key>map-to</key>
|
||||
<string>CrashOnStartup</string>
|
||||
</map>
|
||||
|
||||
<key>debugsession</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Run as if RenderDebugGL is TRUE, but log errors until end of session.</string>
|
||||
<key>map-to</key>
|
||||
<string>DebugSession</string>
|
||||
</map>
|
||||
|
||||
<key>debugviews</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>DebugViews</string>
|
||||
</map>
|
||||
|
||||
<key>disablecrashlogger</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Disables the crash logger and lets the OS handle crashes</string>
|
||||
<key>map-to</key>
|
||||
<string>DisableCrashLogger</string>
|
||||
</map>
|
||||
|
||||
<key>drop</key>
|
||||
|
|
@ -26,20 +81,21 @@
|
|||
<string>PacketDropPercentage</string>
|
||||
</map>
|
||||
|
||||
<key>inbw</key>
|
||||
<key>god</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>desc</key>
|
||||
<string>Log in a god if you have god access.</string>
|
||||
<key>map-to</key>
|
||||
<string>InBandwidth</string>
|
||||
<string>ConnectAsGod</string>
|
||||
</map>
|
||||
|
||||
<key>outbw</key>
|
||||
<key>graphicslevel</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Set the detail level.
|
||||
0 - low, 1 - medium, 2 - high, 3 - ultra</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>OutBandwidth</string>
|
||||
</map>
|
||||
|
||||
<key>grid</key>
|
||||
|
|
@ -52,6 +108,82 @@
|
|||
<string>CmdLineGridChoice</string>
|
||||
</map>
|
||||
|
||||
<key>help</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>display this help message</string>
|
||||
|
||||
<key>short</key>
|
||||
<string>h</string>
|
||||
</map>
|
||||
|
||||
<key>helperuri</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>helper web CGI prefix to use</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>CmdLineHelperURI</string>
|
||||
</map>
|
||||
|
||||
<key>ignorepixeldepth</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Ignore pixel depth settings.</string>
|
||||
<key>map-to</key>
|
||||
<string>IgnorePixelDepth</string>
|
||||
</map>
|
||||
|
||||
<key>inbw</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>InBandwidth</string>
|
||||
</map>
|
||||
|
||||
<key>leap</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>command line to run an LLSD Event API Plugin</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<!-- you can specify multiple such plugins -->
|
||||
<key>compose</key>
|
||||
<boolean>true</boolean>
|
||||
<key>map-to</key>
|
||||
<string>LeapCommand</string>
|
||||
</map>
|
||||
|
||||
<key>logfile</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserLogFile</string>
|
||||
</map>
|
||||
|
||||
<key>login</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>3 tokens: first, last and password</string>
|
||||
<key>count</key>
|
||||
<integer>3</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserLoginInfo</string>
|
||||
</map>
|
||||
|
||||
<key>loginpage</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Login authentication page to use.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>LoginPage</string>
|
||||
</map>
|
||||
|
||||
<key>loginuri</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
|
|
@ -64,46 +196,14 @@
|
|||
<string>CmdLineLoginURI</string>
|
||||
</map>
|
||||
|
||||
<key>helperuri</key>
|
||||
<key>logmetrics</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>helper web CGI prefix to use</string>
|
||||
<string>Log metrics for benchmarking</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>CmdLineHelperURI</string>
|
||||
</map>
|
||||
|
||||
<key>debugviews</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>DebugViews</string>
|
||||
</map>
|
||||
|
||||
<key>skin</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>ui/branding skin folder to use</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>SkinFolder</string>
|
||||
</map>
|
||||
|
||||
<key>autologin</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>log in as last saved user</string>
|
||||
<key>map-to</key>
|
||||
<string>AutoLogin</string>
|
||||
</map>
|
||||
|
||||
<key>quitafter</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>QuitAfterSeconds</string>
|
||||
<string>LogMetrics</string>
|
||||
</map>
|
||||
|
||||
<key>logperformance</key>
|
||||
|
|
@ -114,38 +214,26 @@
|
|||
<string>LogPerformance</string>
|
||||
</map>
|
||||
|
||||
<key>logmetrics</key>
|
||||
<key>multiple</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Log metrics for benchmarking</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<string>Allow multiple viewers.</string>
|
||||
<key>map-to</key>
|
||||
<string>LogMetrics</string>
|
||||
</map>
|
||||
|
||||
<key>analyzeperformance</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>When used in conjunction with logperformance, analyzes result of log against baseline.</string>
|
||||
<key>map-to</key>
|
||||
<string>AnalyzePerformance</string>
|
||||
<string>AllowMultipleViewers</string>
|
||||
</map>
|
||||
|
||||
<key>debugsession</key>
|
||||
<key>noaudio</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Run as if RenderDebugGL is TRUE, but log errors until end of session.</string>
|
||||
<key>map-to</key>
|
||||
<string>DebugSession</string>
|
||||
<string>NoAudio</string>
|
||||
</map>
|
||||
|
||||
<key>replaysession</key>
|
||||
<key>noinvlib</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>After login, replay last recorded session and quit.</string>
|
||||
<string>Do not request the inventory library.</string>
|
||||
<key>map-to</key>
|
||||
<string>ReplaySession</string>
|
||||
<string>NoInventoryLibrary</string>
|
||||
</map>
|
||||
|
||||
<key>nonotifications</key>
|
||||
|
|
@ -156,22 +244,10 @@
|
|||
<string>IgnoreAllNotifications</string>
|
||||
</map>
|
||||
|
||||
<key>rotate</key>
|
||||
<key>nopreload</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>RotateRight</string>
|
||||
</map>
|
||||
|
||||
<key>noaudio</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>NoAudio</string>
|
||||
</map>
|
||||
|
||||
<key>nosound</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>NoAudio</string>
|
||||
<string>NoPreload</string>
|
||||
</map>
|
||||
|
||||
<key>noprobe</key>
|
||||
|
|
@ -186,10 +262,40 @@
|
|||
<string>NoQuickTime</string>
|
||||
</map>
|
||||
|
||||
<key>nopreload</key>
|
||||
<key>nosound</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>NoPreload</string>
|
||||
<string>NoAudio</string>
|
||||
</map>
|
||||
|
||||
<key>no-verify-ssl-cert</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>NoVerifySSLCert</string>
|
||||
</map>
|
||||
|
||||
<key>novoice</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Disable voice.</string>
|
||||
<key>map-to</key>
|
||||
<string>CmdLineDisableVoice</string>
|
||||
</map>
|
||||
|
||||
<key>outbw</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>OutBandwidth</string>
|
||||
</map>
|
||||
|
||||
<key>port</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserConnectionPort</string>
|
||||
</map>
|
||||
|
||||
<key>purge</key>
|
||||
|
|
@ -200,37 +306,50 @@
|
|||
<string>PurgeCacheOnNextStartup</string>
|
||||
</map>
|
||||
|
||||
<key>noinvlib</key>
|
||||
<key>qa</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Do not request the inventory library.</string>
|
||||
<string>Activated debugging menu in Advanced Settings.</string>
|
||||
<key>map-to</key>
|
||||
<string>NoInventoryLibrary</string>
|
||||
<string>QAMode</string>
|
||||
</map>
|
||||
|
||||
<key>logfile</key>
|
||||
<key>quitafter</key>
|
||||
<map>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserLogFile</string>
|
||||
<string>QuitAfterSeconds</string>
|
||||
</map>
|
||||
|
||||
<key>graphicslevel</key>
|
||||
|
||||
<key>replaysession</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Set the detail level.
|
||||
0 - low, 1 - medium, 2 - high, 3 - ultra</string>
|
||||
<string>After login, replay last recorded session and quit.</string>
|
||||
<key>map-to</key>
|
||||
<string>ReplaySession</string>
|
||||
</map>
|
||||
|
||||
<key>rotate</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>RotateRight</string>
|
||||
</map>
|
||||
|
||||
<key>safe</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Reset preferences, run in safe mode.</string>
|
||||
<key>map-to</key>
|
||||
<string>SafeMode</string>
|
||||
</map>
|
||||
|
||||
<key>sessionsettings</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Specify the filename of a configuration file that contains temporary per-session configuration overrides.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
|
||||
<key>setdefault</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>specify the value of a particular configuration variable which can be overridden by settings.xml.</string>
|
||||
<key>count</key>
|
||||
<integer>2</integer>
|
||||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
|
|
@ -245,6 +364,15 @@
|
|||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
<key>setdefault</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>specify the value of a particular configuration variable which can be overridden by settings.xml.</string>
|
||||
<key>count</key>
|
||||
<integer>2</integer>
|
||||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
<key>settings</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
|
|
@ -254,83 +382,14 @@
|
|||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
<key>sessionsettings</key>
|
||||
<key>skin</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Specify the filename of a configuration file that contains temporary per-session configuration overrides.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
<key>usersessionsettings</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Specify the filename of a configuration file that contains temporary per-session configuration user overrides.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
|
||||
<key>login</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>3 tokens: first, last and password</string>
|
||||
<key>count</key>
|
||||
<integer>3</integer>
|
||||
<key>map-to</key>
|
||||
<string>UserLoginInfo</string>
|
||||
</map>
|
||||
|
||||
<key>god</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Log in a god if you have god access.</string>
|
||||
<key>map-to</key>
|
||||
<string>ConnectAsGod</string>
|
||||
</map>
|
||||
|
||||
<key>console</key>
|
||||
<map>
|
||||
<string>ui/branding skin folder to use</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>ShowConsoleWindow</string>
|
||||
</map>
|
||||
|
||||
<key>safe</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Reset preferences, run in safe mode.</string>
|
||||
<key>map-to</key>
|
||||
<string>SafeMode</string>
|
||||
</map>
|
||||
|
||||
<key>multiple</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Allow multiple viewers.</string>
|
||||
<key>map-to</key>
|
||||
<string>AllowMultipleViewers</string>
|
||||
</map>
|
||||
|
||||
<key>novoice</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Disable voice.</string>
|
||||
<key>map-to</key>
|
||||
<string>CmdLineDisableVoice</string>
|
||||
</map>
|
||||
|
||||
<key>url</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Startup location</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>last_option</key>
|
||||
<boolean>true</boolean>
|
||||
<!-- Special case. Not mapped to a setting. -->
|
||||
<string>SkinFolder</string>
|
||||
</map>
|
||||
|
||||
<key>slurl</key>
|
||||
|
|
@ -346,69 +405,24 @@
|
|||
<!-- Special case. Not mapped to a setting. -->
|
||||
</map>
|
||||
|
||||
<key>ignorepixeldepth</key>
|
||||
<key>url</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Ignore pixel depth settings.</string>
|
||||
<key>map-to</key>
|
||||
<string>IgnorePixelDepth</string>
|
||||
</map>
|
||||
|
||||
<key>cooperative</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Yield some idle time to local host.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>YieldTime</string>
|
||||
</map>
|
||||
|
||||
<key>no-verify-ssl-cert</key>
|
||||
<map>
|
||||
<key>map-to</key>
|
||||
<string>NoVerifySSLCert</string>
|
||||
</map>
|
||||
|
||||
<key>channel</key>
|
||||
<map>
|
||||
<string>Startup location</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>last_option</key>
|
||||
<boolean>true</boolean>
|
||||
<!-- Special case. Not mapped to a setting. -->
|
||||
</map>
|
||||
|
||||
<key>loginpage</key>
|
||||
<key>usersessionsettings</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Login authentication page to use.</string>
|
||||
<string>Specify the filename of a configuration file that contains temporary per-session configuration user overrides.</string>
|
||||
<key>count</key>
|
||||
<integer>1</integer>
|
||||
<key>map-to</key>
|
||||
<string>LoginPage</string>
|
||||
</map>
|
||||
|
||||
<key>qa</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Activated debugging menu in Advanced Settings.</string>
|
||||
<key>map-to</key>
|
||||
<string>QAMode</string>
|
||||
</map>
|
||||
|
||||
<key>crashonstartup</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Crashes on startup. For QA use.</string>
|
||||
<key>map-to</key>
|
||||
<string>CrashOnStartup</string>
|
||||
</map>
|
||||
|
||||
<key>disablecrashlogger</key>
|
||||
<map>
|
||||
<key>desc</key>
|
||||
<string>Disables the crash logger and lets the OS handle crashes</string>
|
||||
<key>map-to</key>
|
||||
<string>DisableCrashLogger</string>
|
||||
<!-- Special case. Mapped to settings procedurally. -->
|
||||
</map>
|
||||
</map>
|
||||
</llsd>
|
||||
|
|
|
|||
|
|
@ -4601,6 +4601,17 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>LeapCommand</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Zero or more command lines to run LLSD Event API Plugin programs.</string>
|
||||
<key>Persist</key>
|
||||
<integer>0</integer>
|
||||
<key>Type</key>
|
||||
<string>LLSD</string>
|
||||
<key>Value</key>
|
||||
<array />
|
||||
</map>
|
||||
<key>LSLFindCaseInsensitivity</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -7164,7 +7175,7 @@
|
|||
<key>QAModeEventHostPort</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Port on which lleventhost should listen</string>
|
||||
<string>DEPRECATED: Port on which lleventhost should listen</string>
|
||||
<key>Persist</key>
|
||||
<integer>0</integer>
|
||||
<key>Type</key>
|
||||
|
|
@ -7642,6 +7653,17 @@
|
|||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>RenderCompressTextures</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enable texture compression on OpenGL 3.0 and later implementations (EXPERIMENTAL, requires restart)</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderPerformanceTest</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -9134,28 +9156,19 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderUseShaderLOD</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Whether we want to have different shaders for LOD</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>RenderUseShaderNearParticles</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Whether we want to use shaders on near particles</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
|
||||
<key>RenderAutoHideSurfaceAreaLimit</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Maximum surface area of a set of proximal objects inworld before automatically hiding geometry to prevent system overload.</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
|
||||
<key>RenderVBOEnable</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ out vec4 frag_color;
|
|||
#define frag_color gl_FragColor
|
||||
#endif
|
||||
|
||||
uniform float minimum_alpha;
|
||||
|
||||
uniform sampler2D diffuseMap;
|
||||
|
||||
VARYING vec4 post_pos;
|
||||
VARYING float pos_zd2;
|
||||
VARYING float pos_w;
|
||||
VARYING float target_pos_x;
|
||||
VARYING vec4 vertex_color;
|
||||
VARYING vec2 vary_texcoord0;
|
||||
|
||||
|
|
@ -41,12 +41,20 @@ void main()
|
|||
{
|
||||
float alpha = diffuseLookup(vary_texcoord0.xy).a * vertex_color.a;
|
||||
|
||||
if (alpha < minimum_alpha)
|
||||
if (alpha < 0.05) // treat as totally transparent
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
if (alpha < 0.88) // treat as semi-transparent
|
||||
{
|
||||
if (fract(0.5*floor(target_pos_x / pos_w )) < 0.25)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
frag_color = vec4(1,1,1,1);
|
||||
|
||||
gl_FragDepth = max(post_pos.z/post_pos.w*0.5+0.5, 0.0);
|
||||
gl_FragDepth = max(pos_zd2/pos_w+0.5, 0.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,15 @@
|
|||
|
||||
uniform mat4 texture_matrix0;
|
||||
uniform mat4 modelview_projection_matrix;
|
||||
uniform float shadow_target_width;
|
||||
|
||||
ATTRIBUTE vec3 position;
|
||||
ATTRIBUTE vec4 diffuse_color;
|
||||
ATTRIBUTE vec2 texcoord0;
|
||||
|
||||
VARYING vec4 post_pos;
|
||||
VARYING float pos_zd2;
|
||||
VARYING float pos_w;
|
||||
VARYING float target_pos_x;
|
||||
VARYING vec4 vertex_color;
|
||||
VARYING vec2 vary_texcoord0;
|
||||
|
||||
|
|
@ -39,8 +42,11 @@ void passTextureIndex();
|
|||
void main()
|
||||
{
|
||||
//transform vertex
|
||||
vec4 pos = modelview_projection_matrix*vec4(position.xyz, 1.0);
|
||||
post_pos = pos;
|
||||
vec4 pre_pos = vec4(position.xyz, 1.0);
|
||||
vec4 pos = modelview_projection_matrix * pre_pos;
|
||||
target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x;
|
||||
pos_w = pos.w;
|
||||
pos_zd2 = pos.z * 0.5;
|
||||
|
||||
gl_Position = vec4(pos.x, pos.y, pos.w*0.5, pos.w);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,20 +58,22 @@ uniform float shadow_bias;
|
|||
|
||||
uniform mat4 inv_proj;
|
||||
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += shadow_bias;
|
||||
|
||||
stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here
|
||||
|
||||
float cs = shadow2DRect(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -101,7 +103,7 @@ void main()
|
|||
|
||||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25)*w;
|
||||
shadow += pcfShadow(shadowMap3, lpos)*w;
|
||||
weight += w;
|
||||
shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0);
|
||||
}
|
||||
|
|
@ -114,7 +116,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.y, 0.0)/transition_domain.y;
|
||||
w -= max(near_split.z-spos.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap2, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +128,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.x, 0.0)/transition_domain.x;
|
||||
w -= max(near_split.y-spos.z, 0.0)/transition_domain.y;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap1, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +140,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(near_split.x-spos.z, 0.0)/transition_domain.x;
|
||||
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0)*w;
|
||||
shadow += pcfShadow(shadowMap0, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,20 +71,22 @@ vec4 getPosition(vec2 pos_screen)
|
|||
return pos;
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += shadow_bias;
|
||||
|
||||
stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here
|
||||
|
||||
float cs = shadow2DRect(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -114,7 +116,7 @@ void main()
|
|||
|
||||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25)*w;
|
||||
shadow += pcfShadow(shadowMap3, lpos)*w;
|
||||
weight += w;
|
||||
shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0);
|
||||
}
|
||||
|
|
@ -127,7 +129,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.y, 0.0)/transition_domain.y;
|
||||
w -= max(near_split.z-spos.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap2, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +141,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.x, 0.0)/transition_domain.x;
|
||||
w -= max(near_split.y-spos.z, 0.0)/transition_domain.y;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap1, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +153,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(near_split.x-spos.z, 0.0)/transition_domain.x;
|
||||
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0)*w;
|
||||
shadow += pcfShadow(shadowMap0, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,20 +70,22 @@ vec4 getPosition(vec2 pos_screen)
|
|||
return pos;
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += shadow_bias;
|
||||
|
||||
stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here
|
||||
|
||||
float cs = shadow2DRect(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(scl, -scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, scl, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-scl, -scl, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -113,7 +115,7 @@ void main()
|
|||
|
||||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25)*w;
|
||||
shadow += pcfShadow(shadowMap3, lpos)*w;
|
||||
weight += w;
|
||||
shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0);
|
||||
}
|
||||
|
|
@ -126,7 +128,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.y, 0.0)/transition_domain.y;
|
||||
w -= max(near_split.z-spos.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap2, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +140,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.x, 0.0)/transition_domain.x;
|
||||
w -= max(near_split.y-spos.z, 0.0)/transition_domain.y;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap1, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +152,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(near_split.x-spos.z, 0.0)/transition_domain.x;
|
||||
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0)*w;
|
||||
shadow += pcfShadow(shadowMap0, lpos)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,42 +78,42 @@ vec4 getPosition(vec2 pos_screen)
|
|||
return pos;
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += shadow_bias*scl;
|
||||
|
||||
|
||||
stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); // add some jitter to X sample pos according to Y to disguise the snapping going on here
|
||||
|
||||
float cs = shadow2DRect(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, 1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, -1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, 1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, -1.5, 0.0)).x, cs);
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, -1.5, 0.0)).x;
|
||||
|
||||
return shadow/5.0;
|
||||
|
||||
//return shadow;
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += spot_shadow_bias*scl;
|
||||
stc.x = floor(proj_shadow_res.x * stc.x + fract(pos_screen.y*0.666666666)) / proj_shadow_res.x; // snap
|
||||
|
||||
float cs = shadow2D(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
vec2 off = 1.5/proj_shadow_res;
|
||||
vec2 off = 1.0/proj_shadow_res;
|
||||
off.y *= 1.5;
|
||||
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, -off.y, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
|
||||
//return shadow;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
void main()
|
||||
|
|
@ -166,7 +166,7 @@ void main()
|
|||
|
||||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25)*w;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w;
|
||||
weight += w;
|
||||
shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0);
|
||||
}
|
||||
|
|
@ -179,7 +179,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.y, 0.0)/transition_domain.y;
|
||||
w -= max(near_split.z-spos.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.5, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.x, 0.0)/transition_domain.x;
|
||||
w -= max(near_split.y-spos.z, 0.0)/transition_domain.y;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(near_split.x-spos.z, 0.0)/transition_domain.x;
|
||||
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0)*w;
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -237,11 +237,11 @@ void main()
|
|||
|
||||
//spotlight shadow 1
|
||||
vec4 lpos = shadow_matrix[4]*spos;
|
||||
frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8);
|
||||
frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen);
|
||||
|
||||
//spotlight shadow 2
|
||||
lpos = shadow_matrix[5]*spos;
|
||||
frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8);
|
||||
frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen);
|
||||
|
||||
//frag_color.rgb = pos.xyz;
|
||||
//frag_color.b = shadow;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ vec2 getKern(int i)
|
|||
kern[5] = vec2(-0.7071, -0.7071) * 0.750*0.750;
|
||||
kern[6] = vec2(-0.7071, 0.7071) * 0.875*0.875;
|
||||
kern[7] = vec2(0.7071, -0.7071) * 1.000*1.000;
|
||||
|
||||
|
||||
return kern[i];
|
||||
}
|
||||
|
||||
|
|
@ -139,42 +139,42 @@ float calcAmbientOcclusion(vec4 pos, vec3 norm)
|
|||
return min(ret, 1.0);
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += shadow_bias*scl;
|
||||
|
||||
stc.x = floor(stc.x + fract(pos_screen.y*0.666666666));
|
||||
|
||||
float cs = shadow2DRect(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, 1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(1.5, -1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, 1.5, 0.0)).x, cs);
|
||||
shadow += max(shadow2DRect(shadowMap, stc.xyz+vec3(-1.5, -1.5, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
|
||||
//return shadow;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x;
|
||||
shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl)
|
||||
float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen)
|
||||
{
|
||||
stc.xyz /= stc.w;
|
||||
stc.z += spot_shadow_bias*scl;
|
||||
stc.x = floor(proj_shadow_res.x * stc.x + fract(pos_screen.y*0.666666666)) / proj_shadow_res.x; // snap
|
||||
|
||||
float cs = shadow2D(shadowMap, stc.xyz).x;
|
||||
float shadow = cs;
|
||||
|
||||
vec2 off = 1.5/proj_shadow_res;
|
||||
vec2 off = 1.0/proj_shadow_res;
|
||||
off.y *= 1.5;
|
||||
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x, cs);
|
||||
shadow += max(shadow2D(shadowMap, stc.xyz+vec3(-off.x, -off.y, 0.0)).x, cs);
|
||||
|
||||
return shadow/5.0;
|
||||
|
||||
//return shadow;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x*2.0, off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(off.x, -off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x;
|
||||
shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x;
|
||||
|
||||
return shadow*0.2;
|
||||
}
|
||||
|
||||
void main()
|
||||
|
|
@ -227,7 +227,7 @@ void main()
|
|||
|
||||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25)*w;
|
||||
shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w;
|
||||
weight += w;
|
||||
shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0);
|
||||
}
|
||||
|
|
@ -240,7 +240,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.y, 0.0)/transition_domain.y;
|
||||
w -= max(near_split.z-spos.z, 0.0)/transition_domain.z;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap2, lpos, 0.5, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(spos.z-far_split.x, 0.0)/transition_domain.x;
|
||||
w -= max(near_split.y-spos.z, 0.0)/transition_domain.y;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75)*w;
|
||||
shadow += pcfShadow(shadowMap1, lpos, 0.75, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ void main()
|
|||
float w = 1.0;
|
||||
w -= max(near_split.x-spos.z, 0.0)/transition_domain.x;
|
||||
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0)*w;
|
||||
shadow += pcfShadow(shadowMap0, lpos, 1.0, pos_screen)*w;
|
||||
weight += w;
|
||||
}
|
||||
|
||||
|
|
@ -298,11 +298,11 @@ void main()
|
|||
|
||||
//spotlight shadow 1
|
||||
vec4 lpos = shadow_matrix[4]*spos;
|
||||
frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8);
|
||||
frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen);
|
||||
|
||||
//spotlight shadow 2
|
||||
lpos = shadow_matrix[5]*spos;
|
||||
frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8);
|
||||
frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen);
|
||||
|
||||
//frag_color.rgb = pos.xyz;
|
||||
//frag_color.b = shadow;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ WLSkyDetail 1 128
|
|||
Disregard128DefaultDrawDistance 1 1
|
||||
Disregard96DefaultDrawDistance 1 1
|
||||
RenderTextureMemoryMultiple 1 1.0
|
||||
RenderCompressTextures 1 1
|
||||
RenderShaderLightingMaxLevel 1 3
|
||||
RenderDeferred 1 1
|
||||
RenderDeferredSSAO 1 1
|
||||
|
|
@ -71,6 +72,38 @@ RenderFSAASamples 1 16
|
|||
RenderMaxTextureIndex 1 16
|
||||
|
||||
|
||||
//
|
||||
// Low Graphics Settings (fixed function)
|
||||
//
|
||||
list LowFixedFunction
|
||||
RenderAnisotropic 1 0
|
||||
RenderAvatarCloth 1 0
|
||||
RenderAvatarLODFactor 1 0
|
||||
RenderAvatarPhysicsLODFactor 1 0
|
||||
RenderAvatarMaxVisible 1 3
|
||||
RenderAvatarVP 1 0
|
||||
RenderFarClip 1 64
|
||||
RenderFlexTimeFactor 1 0
|
||||
RenderGlowResolutionPow 1 8
|
||||
RenderMaxPartCount 1 0
|
||||
RenderObjectBump 1 0
|
||||
RenderLocalLights 1 0
|
||||
RenderReflectionDetail 1 0
|
||||
RenderTerrainDetail 1 0
|
||||
RenderTerrainLODFactor 1 1
|
||||
RenderTransparentWater 1 0
|
||||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 1.125
|
||||
VertexShaderEnable 1 0
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
RenderDeferredSSAO 1 0
|
||||
RenderShadowDetail 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
|
||||
//
|
||||
// Low Graphics Settings
|
||||
//
|
||||
|
|
@ -94,7 +127,7 @@ RenderTransparentWater 1 0
|
|||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 1.125
|
||||
VertexShaderEnable 1 0
|
||||
VertexShaderEnable 1 1
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
|
|
@ -222,6 +255,12 @@ RenderVBOEnable 1 1
|
|||
list Class3
|
||||
RenderVBOEnable 1 1
|
||||
|
||||
//
|
||||
// VRAM > 512MB
|
||||
//
|
||||
list VRAMGT512
|
||||
RenderCompressTextures 1 0
|
||||
|
||||
//
|
||||
// No Pixel Shaders available
|
||||
//
|
||||
|
|
@ -302,6 +341,7 @@ RenderMaxTextureIndex 1 1
|
|||
list Intel
|
||||
RenderAnisotropic 1 0
|
||||
RenderVBOEnable 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
list GeForce2
|
||||
RenderAnisotropic 1 0
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ WLSkyDetail 1 128
|
|||
Disregard128DefaultDrawDistance 1 1
|
||||
Disregard96DefaultDrawDistance 1 1
|
||||
RenderTextureMemoryMultiple 1 1.0
|
||||
RenderCompressTextures 1 1
|
||||
RenderShaderLightingMaxLevel 1 3
|
||||
RenderDeferred 1 1
|
||||
RenderDeferredSSAO 1 1
|
||||
|
|
@ -68,6 +69,37 @@ RenderShadowDetail 1 2
|
|||
RenderFSAASamples 1 16
|
||||
RenderMaxTextureIndex 1 16
|
||||
|
||||
//
|
||||
// Low Graphics Settings (fixed function)
|
||||
//
|
||||
list LowFixedFunction
|
||||
RenderAnisotropic 1 0
|
||||
RenderAvatarCloth 1 0
|
||||
RenderAvatarLODFactor 1 0
|
||||
RenderAvatarPhysicsLODFactor 1 0
|
||||
RenderAvatarMaxVisible 1 3
|
||||
RenderAvatarVP 1 0
|
||||
RenderFarClip 1 64
|
||||
RenderFlexTimeFactor 1 0
|
||||
RenderGlowResolutionPow 1 8
|
||||
RenderLocalLights 1 0
|
||||
RenderMaxPartCount 1 0
|
||||
RenderObjectBump 1 0
|
||||
RenderReflectionDetail 1 0
|
||||
RenderTerrainDetail 1 0
|
||||
RenderTerrainLODFactor 1 1
|
||||
RenderTransparentWater 1 0
|
||||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 0.5
|
||||
VertexShaderEnable 1 1
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
RenderDeferredSSAO 1 0
|
||||
RenderShadowDetail 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
//
|
||||
// Low Graphics Settings
|
||||
//
|
||||
|
|
@ -219,6 +251,12 @@ RenderVBOEnable 1 1
|
|||
list Class3
|
||||
RenderVBOEnable 1 1
|
||||
|
||||
//
|
||||
// VRAM > 512MB
|
||||
//
|
||||
list VRAMGT512
|
||||
RenderCompressTextures 1 0
|
||||
|
||||
//
|
||||
// No Pixel Shaders available
|
||||
//
|
||||
|
|
@ -291,10 +329,15 @@ RenderObjectBump 0 0
|
|||
list OpenGLPre15
|
||||
RenderVBOEnable 1 0
|
||||
|
||||
list OpenGLPre30
|
||||
RenderDeferred 0 0
|
||||
RenderMaxTextureIndex 1 1
|
||||
|
||||
list Intel
|
||||
RenderAnisotropic 1 0
|
||||
// Avoid some Intel crashes on Linux
|
||||
RenderCubeMap 0 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
list GeForce2
|
||||
RenderAnisotropic 1 0
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ WLSkyDetail 1 128
|
|||
Disregard128DefaultDrawDistance 1 1
|
||||
Disregard96DefaultDrawDistance 1 1
|
||||
RenderTextureMemoryMultiple 1 0.5
|
||||
RenderCompressTextures 1 1
|
||||
RenderShaderLightingMaxLevel 1 3
|
||||
RenderDeferred 1 1
|
||||
RenderDeferredSSAO 1 1
|
||||
|
|
@ -70,6 +71,37 @@ RenderUseStreamVBO 1 1
|
|||
RenderFSAASamples 1 16
|
||||
RenderMaxTextureIndex 1 16
|
||||
|
||||
//
|
||||
// Low Graphics Settings (fixed function)
|
||||
//
|
||||
list LowFixedFunction
|
||||
RenderAnisotropic 1 0
|
||||
RenderAvatarCloth 1 0
|
||||
RenderAvatarLODFactor 1 0
|
||||
RenderAvatarPhysicsLODFactor 1 0
|
||||
RenderAvatarMaxVisible 1 3
|
||||
RenderAvatarVP 1 0
|
||||
RenderFarClip 1 64
|
||||
RenderFlexTimeFactor 1 0
|
||||
RenderGlowResolutionPow 1 8
|
||||
RenderLocalLights 1 0
|
||||
RenderMaxPartCount 1 0
|
||||
RenderObjectBump 1 0
|
||||
RenderReflectionDetail 1 0
|
||||
RenderTerrainDetail 1 0
|
||||
RenderTerrainLODFactor 1 1
|
||||
RenderTransparentWater 1 0
|
||||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 0.5
|
||||
VertexShaderEnable 1 0
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
RenderDeferredSSAO 1 0
|
||||
RenderShadowDetail 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
//
|
||||
// Low Graphics Settings
|
||||
//
|
||||
|
|
@ -93,7 +125,7 @@ RenderTransparentWater 1 0
|
|||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 0.5
|
||||
VertexShaderEnable 1 0
|
||||
VertexShaderEnable 1 1
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
|
|
@ -247,6 +279,12 @@ RenderDeferred 0 0
|
|||
RenderDeferredSSAO 0 0
|
||||
RenderShadowDetail 0 0
|
||||
|
||||
//
|
||||
// VRAM > 512MB
|
||||
//
|
||||
list VRAMGT512
|
||||
RenderCompressTextures 1 0
|
||||
|
||||
//
|
||||
// "Default" setups for safe, low, medium, high
|
||||
//
|
||||
|
|
@ -286,6 +324,7 @@ RenderObjectBump 0 0
|
|||
list OpenGLPre15
|
||||
RenderVBOEnable 1 0
|
||||
|
||||
|
||||
list TexUnit8orLess
|
||||
RenderDeferredSSAO 0 0
|
||||
|
||||
|
|
@ -295,6 +334,7 @@ RenderDeferredSSAO 1 0
|
|||
list Intel
|
||||
RenderAnisotropic 1 0
|
||||
RenderLocalLights 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
list GeForce2
|
||||
RenderAnisotropic 1 0
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ WLSkyDetail 1 128
|
|||
Disregard128DefaultDrawDistance 1 1
|
||||
Disregard96DefaultDrawDistance 1 1
|
||||
RenderTextureMemoryMultiple 1 1.0
|
||||
RenderCompressTextures 1 1
|
||||
RenderShaderLightingMaxLevel 1 3
|
||||
RenderDeferred 1 0
|
||||
RenderDeferredSSAO 1 0
|
||||
|
|
@ -70,6 +71,37 @@ RenderUseStreamVBO 1 1
|
|||
RenderFSAASamples 1 16
|
||||
RenderMaxTextureIndex 1 16
|
||||
|
||||
//
|
||||
// Low Graphics Settings (fixed function)
|
||||
//
|
||||
list LowFixedFunction
|
||||
RenderAnisotropic 1 0
|
||||
RenderAvatarCloth 1 0
|
||||
RenderAvatarLODFactor 1 0
|
||||
RenderAvatarPhysicsLODFactor 1 0
|
||||
RenderAvatarMaxVisible 1 3
|
||||
RenderAvatarVP 1 0
|
||||
RenderFarClip 1 64
|
||||
RenderFlexTimeFactor 1 0
|
||||
RenderGlowResolutionPow 1 8
|
||||
RenderLocalLights 1 0
|
||||
RenderMaxPartCount 1 0
|
||||
RenderObjectBump 1 0
|
||||
RenderReflectionDetail 1 0
|
||||
RenderTerrainDetail 1 0
|
||||
RenderTerrainLODFactor 1 1
|
||||
RenderTransparentWater 1 0
|
||||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 0.5
|
||||
VertexShaderEnable 1 0
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
RenderDeferredSSAO 1 0
|
||||
RenderShadowDetail 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
//
|
||||
// Low Graphics Settings
|
||||
//
|
||||
|
|
@ -93,7 +125,7 @@ RenderTransparentWater 1 0
|
|||
RenderTreeLODFactor 1 0
|
||||
RenderUseImpostors 1 1
|
||||
RenderVolumeLODFactor 1 0.5
|
||||
VertexShaderEnable 1 0
|
||||
VertexShaderEnable 1 1
|
||||
WindLightUseAtmosShaders 1 0
|
||||
WLSkyDetail 1 48
|
||||
RenderDeferred 1 0
|
||||
|
|
@ -221,6 +253,12 @@ RenderVBOEnable 1 1
|
|||
list Class3
|
||||
RenderVBOEnable 1 1
|
||||
|
||||
//
|
||||
// VRAM > 512MB
|
||||
//
|
||||
list VRAMGT512
|
||||
RenderCompressTextures 1 0
|
||||
|
||||
//
|
||||
// No Pixel Shaders available
|
||||
//
|
||||
|
|
@ -299,6 +337,7 @@ RenderMaxTextureIndex 1 1
|
|||
list Intel
|
||||
RenderAnisotropic 1 0
|
||||
RenderVBOEnable 1 0
|
||||
RenderFSAASamples 1 0
|
||||
|
||||
list GeForce2
|
||||
RenderAnisotropic 1 0
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ ATI Radeon X800 .*ATI.*Radeon ?X8.* 2 1
|
|||
ATI Radeon X900 .*ATI.*Radeon ?X9.* 2 1
|
||||
ATI Radeon Xpress .*ATI.*Radeon Xpress.* 0 1
|
||||
ATI Rage 128 .*ATI.*Rage 128.* 0 1
|
||||
ATI R300 (9700) .*R300.* 1 1
|
||||
ATI R350 (9800) .*R350.* 1 1
|
||||
ATI R580 (X1900) .*R580.* 3 1
|
||||
ATI RC410 (Xpress 200) .*RC410.* 0 0
|
||||
|
|
|
|||
|
|
@ -110,22 +110,34 @@ export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}"
|
|||
# fi
|
||||
#fi
|
||||
|
||||
export SL_ENV='LD_LIBRARY_PATH="`pwd`"/lib:"${LD_LIBRARY_PATH}"'
|
||||
export SL_CMD='$LL_WRAPPER bin/do-not-directly-run-secondlife-bin'
|
||||
export SL_OPT="`cat etc/gridargs.dat` $@"
|
||||
export LD_LIBRARY_PATH="$PWD/lib:${LD_LIBRARY_PATH}"
|
||||
|
||||
# Run the program
|
||||
eval ${SL_ENV} ${SL_CMD} ${SL_OPT} || LL_RUN_ERR=runerr
|
||||
# Have to deal specially with gridargs.dat; typical contents look like:
|
||||
# --channel "Second Life Developer" --settings settings_developer.xml
|
||||
# Simply embedding $(<etc/gridargs.dat) into a command line treats each of
|
||||
# Second, Life and Developer as separate args -- no good. We need bash to
|
||||
# process quotes using eval.
|
||||
# First read it without scanning, then scan that string. Break quoted words
|
||||
# into a bash array. Note that if gridargs.dat is empty, or contains only
|
||||
# whitespace, the resulting gridargs array will be empty -- zero entries --
|
||||
# therefore "${gridargs[@]}" entirely vanishes from the command line below,
|
||||
# just as we want.
|
||||
eval gridargs=("$(<etc/gridargs.dat)")
|
||||
|
||||
# Run the program.
|
||||
# Don't quote $LL_WRAPPER because, if empty, it should simply vanish from the
|
||||
# command line. But DO quote "$@": preserve separate args as individually
|
||||
# quoted. Similar remarks about the contents of gridargs.
|
||||
$LL_WRAPPER bin/do-not-directly-run-secondlife-bin "${gridargs[@]}" "$@"
|
||||
LL_RUN_ERR=$?
|
||||
|
||||
# Handle any resulting errors
|
||||
if [ -n "$LL_RUN_ERR" ]; then
|
||||
LL_RUN_ERR_MSG=""
|
||||
if [ "$LL_RUN_ERR" = "runerr" ]; then
|
||||
# generic error running the binary
|
||||
echo '*** Bad shutdown. ***'
|
||||
if [ "`uname -m`" = "x86_64" ]; then
|
||||
echo
|
||||
cat << EOFMARKER
|
||||
if [ $LL_RUN_ERR -ne 0 ]; then
|
||||
# generic error running the binary
|
||||
echo '*** Bad shutdown ($LL_RUN_ERR). ***'
|
||||
if [ "$(uname -m)" = "x86_64" ]; then
|
||||
echo
|
||||
cat << EOFMARKER
|
||||
You are running the Second Life Viewer on a x86_64 platform. The
|
||||
most common problems when launching the Viewer (particularly
|
||||
'bin/do-not-directly-run-secondlife-bin: not found' and 'error while
|
||||
|
|
@ -134,10 +146,8 @@ distribution's 32-bit compatibility packages.
|
|||
For example, on Ubuntu and other Debian-based Linuxes you might run:
|
||||
$ sudo apt-get install ia32-libs ia32-libs-gtk ia32-libs-kde ia32-libs-sdl
|
||||
EOFMARKER
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
echo
|
||||
echo '*******************************************************'
|
||||
|
|
|
|||
|
|
@ -112,6 +112,8 @@
|
|||
#include "llnotifications.h"
|
||||
#include "llnotificationsutil.h"
|
||||
|
||||
#include "llleap.h"
|
||||
|
||||
// Third party library includes
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
|
@ -125,7 +127,6 @@
|
|||
#endif
|
||||
|
||||
#include "llapr.h"
|
||||
#include "apr_dso.h"
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "llviewerkeyboard.h"
|
||||
|
|
@ -162,6 +163,7 @@
|
|||
#include "llcontainerview.h"
|
||||
#include "lltooltip.h"
|
||||
|
||||
#include "llsdutil.h"
|
||||
#include "llsdserialize.h"
|
||||
|
||||
#include "llworld.h"
|
||||
|
|
@ -529,6 +531,7 @@ static void settings_to_globals()
|
|||
LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLCoreProfile");
|
||||
|
||||
LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic");
|
||||
LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures");
|
||||
LLVOVolume::sLODFactor = gSavedSettings.getF32("RenderVolumeLODFactor");
|
||||
LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f;
|
||||
LLVolumeImplFlexible::sUpdateFactor = gSavedSettings.getF32("RenderFlexTimeFactor");
|
||||
|
|
@ -546,7 +549,7 @@ static void settings_to_globals()
|
|||
gAgentPilot.setNumRuns(gSavedSettings.getS32("StatsNumRuns"));
|
||||
gAgentPilot.setQuitAfterRuns(gSavedSettings.getBOOL("StatsQuitAfterRuns"));
|
||||
gAgent.setHideGroupTitle(gSavedSettings.getBOOL("RenderHideGroupTitle"));
|
||||
|
||||
|
||||
gDebugWindowProc = gSavedSettings.getBOOL("DebugWindowProc");
|
||||
gShowObjectUpdates = gSavedSettings.getBOOL("ShowObjectUpdates");
|
||||
LLWorldMapView::sMapScale = gSavedSettings.getF32("MapScale");
|
||||
|
|
@ -1014,6 +1017,15 @@ bool LLAppViewer::init()
|
|||
}
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
if (gGLManager.mIsIntel &&
|
||||
LLFeatureManager::getInstance()->getGPUClass() > 0 &&
|
||||
gGLManager.mGLVersion <= 3.f)
|
||||
{
|
||||
LLNotificationsUtil::add("IntelOldDriver");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// save the graphics card
|
||||
gDebugInfo["GraphicsCard"] = LLFeatureManager::getInstance()->getGPUString();
|
||||
|
|
@ -1038,11 +1050,38 @@ bool LLAppViewer::init()
|
|||
|
||||
|
||||
gGLActive = FALSE;
|
||||
|
||||
// Iterate over --leap command-line options. But this is a bit tricky: if
|
||||
// there's only one, it won't be an array at all.
|
||||
LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand"));
|
||||
LL_DEBUGS("InitInfo") << "LeapCommand: " << LeapCommand << LL_ENDL;
|
||||
if (LeapCommand.isDefined() && ! LeapCommand.isArray())
|
||||
{
|
||||
// If LeapCommand is actually a scalar value, make an array of it.
|
||||
// Have to do it in two steps because LeapCommand.append(LeapCommand)
|
||||
// trashes content! :-P
|
||||
LLSD item(LeapCommand);
|
||||
LeapCommand.append(item);
|
||||
}
|
||||
BOOST_FOREACH(const std::string& leap, llsd::inArray(LeapCommand))
|
||||
{
|
||||
LL_INFOS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL;
|
||||
// We don't have any better description of this plugin than the
|
||||
// user-specified command line. Passing "" causes LLLeap to derive a
|
||||
// description from the command line itself.
|
||||
// Suppress LLLeap::Error exception: trust LLLeap's own logging. We
|
||||
// don't consider any one --leap command mission-critical, so if one
|
||||
// fails, log it, shrug and carry on.
|
||||
LLLeap::create("", leap, false); // exception=false
|
||||
}
|
||||
|
||||
if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0)
|
||||
{
|
||||
loadEventHostModule(gSavedSettings.getS32("QAModeEventHostPort"));
|
||||
LL_WARNS("InitInfo") << "QAModeEventHostPort DEPRECATED: "
|
||||
<< "lleventhost no longer supported as a dynamic library"
|
||||
<< LL_ENDL;
|
||||
}
|
||||
|
||||
|
||||
LLViewerMedia::initClass();
|
||||
LL_INFOS("InitInfo") << "Viewer media initialized." << LL_ENDL ;
|
||||
|
||||
|
|
@ -1520,21 +1559,27 @@ bool LLAppViewer::cleanup()
|
|||
if (! isError())
|
||||
{
|
||||
std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
|
||||
logdir += gDirUtilp->getDirDelimiter();
|
||||
gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp");
|
||||
}
|
||||
|
||||
// *TODO - generalize this and move DSO wrangling to a helper class -brad
|
||||
std::set<struct apr_dso_handle_t *>::const_iterator i;
|
||||
for(i = mPlugins.begin(); i != mPlugins.end(); ++i)
|
||||
{
|
||||
int (*ll_plugin_stop_func)(void) = NULL;
|
||||
apr_status_t rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll_plugin_stop_func, *i, "ll_plugin_stop");
|
||||
ll_plugin_stop_func();
|
||||
|
||||
rv = apr_dso_unload(*i);
|
||||
}
|
||||
mPlugins.clear();
|
||||
// Kill off LLLeap objects. We can find them all because LLLeap is derived
|
||||
// from LLInstanceTracker. But collect instances first: LLInstanceTracker
|
||||
// specifically forbids adding/deleting instances while iterating.
|
||||
std::vector<LLLeap*> leaps;
|
||||
leaps.reserve(LLLeap::instanceCount());
|
||||
for (LLLeap::instance_iter li(LLLeap::beginInstances()), lend(LLLeap::endInstances());
|
||||
li != lend; ++li)
|
||||
{
|
||||
leaps.push_back(&*li);
|
||||
}
|
||||
// Okay, now trash them all. We don't have to NULL or erase the entry
|
||||
// in 'leaps' because the whole vector is going away momentarily.
|
||||
BOOST_FOREACH(LLLeap* leap, leaps)
|
||||
{
|
||||
delete leap;
|
||||
}
|
||||
} // destroy 'leaps'
|
||||
|
||||
//flag all elements as needing to be destroyed immediately
|
||||
// to ensure shutdown order
|
||||
|
|
@ -1769,8 +1814,7 @@ bool LLAppViewer::cleanup()
|
|||
if (mPurgeOnExit)
|
||||
{
|
||||
llinfos << "Purging all cache files on exit" << llendflush;
|
||||
std::string mask = gDirUtilp->getDirDelimiter() + "*.*";
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask);
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*");
|
||||
}
|
||||
|
||||
removeMarkerFile(); // Any crashes from here on we'll just have to ignore
|
||||
|
|
@ -3004,8 +3048,7 @@ void LLAppViewer::cleanupSavedSettings()
|
|||
|
||||
void LLAppViewer::removeCacheFiles(const std::string& file_mask)
|
||||
{
|
||||
std::string mask = gDirUtilp->getDirDelimiter() + file_mask;
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), mask);
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), file_mask);
|
||||
}
|
||||
|
||||
void LLAppViewer::writeSystemInfo()
|
||||
|
|
@ -3864,8 +3907,7 @@ void LLAppViewer::purgeCache()
|
|||
LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << LL_ENDL;
|
||||
LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE);
|
||||
LLVOCache::getInstance()->removeCache(LL_PATH_CACHE);
|
||||
std::string mask = "*.*";
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), mask);
|
||||
gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), "*.*");
|
||||
}
|
||||
|
||||
std::string LLAppViewer::getSecondLifeTitle() const
|
||||
|
|
@ -4973,87 +5015,6 @@ void LLAppViewer::handleLoginComplete()
|
|||
writeDebugInfo();
|
||||
}
|
||||
|
||||
// *TODO - generalize this and move DSO wrangling to a helper class -brad
|
||||
void LLAppViewer::loadEventHostModule(S32 listen_port)
|
||||
{
|
||||
std::string dso_name =
|
||||
#if LL_WINDOWS
|
||||
"lleventhost.dll";
|
||||
#elif LL_DARWIN
|
||||
"liblleventhost.dylib";
|
||||
#else
|
||||
"liblleventhost.so";
|
||||
#endif
|
||||
|
||||
std::string dso_path = gDirUtilp->findFile(dso_name,
|
||||
gDirUtilp->getAppRODataDir(),
|
||||
gDirUtilp->getExecutableDir());
|
||||
|
||||
if(dso_path == "")
|
||||
{
|
||||
llerrs << "QAModeEventHost requested but module \"" << dso_name << "\" not found!" << llendl;
|
||||
return;
|
||||
}
|
||||
|
||||
LL_INFOS("eventhost") << "Found lleventhost at '" << dso_path << "'" << LL_ENDL;
|
||||
#if ! defined(LL_WINDOWS)
|
||||
{
|
||||
std::string outfile("/tmp/lleventhost.file.out");
|
||||
std::string command("file '" + dso_path + "' > '" + outfile + "' 2>&1");
|
||||
int rc = system(command.c_str());
|
||||
if (rc != 0)
|
||||
{
|
||||
LL_WARNS("eventhost") << command << " ==> " << rc << ':' << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("eventhost") << command << ':' << LL_ENDL;
|
||||
}
|
||||
{
|
||||
std::ifstream reader(outfile.c_str());
|
||||
std::string line;
|
||||
while (std::getline(reader, line))
|
||||
{
|
||||
size_t len = line.length();
|
||||
if (len && line[len-1] == '\n')
|
||||
line.erase(len-1);
|
||||
LL_INFOS("eventhost") << line << LL_ENDL;
|
||||
}
|
||||
}
|
||||
remove(outfile.c_str());
|
||||
}
|
||||
#endif // LL_WINDOWS
|
||||
|
||||
apr_dso_handle_t * eventhost_dso_handle = NULL;
|
||||
apr_pool_t * eventhost_dso_memory_pool = NULL;
|
||||
|
||||
//attempt to load the shared library
|
||||
apr_pool_create(&eventhost_dso_memory_pool, NULL);
|
||||
apr_status_t rv = apr_dso_load(&eventhost_dso_handle,
|
||||
dso_path.c_str(),
|
||||
eventhost_dso_memory_pool);
|
||||
llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle));
|
||||
llassert_always(eventhost_dso_handle != NULL);
|
||||
|
||||
int (*ll_plugin_start_func)(LLSD const &) = NULL;
|
||||
rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll_plugin_start_func, eventhost_dso_handle, "ll_plugin_start");
|
||||
|
||||
llassert_always(! ll_apr_warn_status(rv, eventhost_dso_handle));
|
||||
llassert_always(ll_plugin_start_func != NULL);
|
||||
|
||||
LLSD args;
|
||||
args["listen_port"] = listen_port;
|
||||
|
||||
int status = ll_plugin_start_func(args);
|
||||
|
||||
if(status != 0)
|
||||
{
|
||||
llerrs << "problem loading eventhost plugin, status: " << status << llendl;
|
||||
}
|
||||
|
||||
mPlugins.insert(eventhost_dso_handle);
|
||||
}
|
||||
|
||||
void LLAppViewer::launchUpdater()
|
||||
{
|
||||
LLSD query_map = LLSD::emptyMap();
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ class LLTextureFetch;
|
|||
class LLWatchdogTimeout;
|
||||
class LLUpdaterService;
|
||||
|
||||
struct apr_dso_handle_t;
|
||||
|
||||
class LLAppViewer : public LLApp
|
||||
{
|
||||
public:
|
||||
|
|
@ -220,8 +218,6 @@ private:
|
|||
|
||||
void sendLogoutRequest();
|
||||
void disconnectViewer();
|
||||
|
||||
void loadEventHostModule(S32 listen_port);
|
||||
|
||||
// *FIX: the app viewer class should be some sort of singleton, no?
|
||||
// Perhaps its child class is the singleton and this should be an abstract base.
|
||||
|
|
@ -270,8 +266,6 @@ private:
|
|||
|
||||
LLAllocator mAlloc;
|
||||
|
||||
std::set<struct apr_dso_handle_t*> mPlugins;
|
||||
|
||||
LLFrameTimer mMemCheckTimer;
|
||||
|
||||
boost::scoped_ptr<LLUpdaterService> mUpdater;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue