Automated merge with http://hg.secondlife.com/viewer-release
commit
a5b0147df4
|
|
@ -90,9 +90,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>9868bfa0b6954e4884c49c6f30068c80</string>
|
||||
<string>847f1b55c0549b7abe6628f1ea242b48</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/256201/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120509.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>998ff5f7a5a9be8c0717e1a0eab05e0c</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/256201/arch/Linux/installer/apr_suite-1.4.5-linux-20120509.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>44bc19a2491e8bd3ac4424c0b641f069</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/256201/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120509.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,546 @@
|
|||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/// English Status string query, for logging etc.
|
||||
std::string getStatusString() const;
|
||||
/// 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="");
|
||||
|
||||
#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 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. 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) */
|
||||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
@ -134,7 +135,10 @@ LLPluginProcessParent::~LLPluginProcessParent()
|
|||
mSharedMemoryRegions.erase(iter);
|
||||
}
|
||||
|
||||
mProcess.kill();
|
||||
if (mProcess)
|
||||
{
|
||||
mProcess->kill();
|
||||
}
|
||||
killSockets();
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +163,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 +375,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 +390,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 +471,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_EXITING:
|
||||
if(!mProcess.isRunning())
|
||||
if (! mProcess->isRunning())
|
||||
{
|
||||
setState(STATE_CLEANUP);
|
||||
}
|
||||
|
|
@ -498,7 +499,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_CLEANUP:
|
||||
mProcess.kill();
|
||||
mProcess->kill();
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
|
@ -1077,7 +1078,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
|
|||
{
|
||||
bool result = false;
|
||||
|
||||
if(!mProcess.isRunning())
|
||||
if (! mProcess->isRunning())
|
||||
{
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -30,7 +30,6 @@ include(LLUI)
|
|||
include(LLVFS)
|
||||
include(LLWindow)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(LScript)
|
||||
include(Linking)
|
||||
include(NDOF)
|
||||
|
|
@ -65,7 +64,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}
|
||||
|
|
@ -1745,7 +1743,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>
|
||||
|
|
|
|||
|
|
@ -4590,6 +4590,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>
|
||||
|
|
@ -7153,7 +7164,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>
|
||||
|
|
|
|||
|
|
@ -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 '*******************************************************'
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@
|
|||
#include "llnotifications.h"
|
||||
#include "llnotificationsutil.h"
|
||||
|
||||
#include "llleap.h"
|
||||
|
||||
// Third party library includes
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
|
|
@ -124,7 +126,6 @@
|
|||
#endif
|
||||
|
||||
#include "llapr.h"
|
||||
#include "apr_dso.h"
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "llviewerkeyboard.h"
|
||||
|
|
@ -161,6 +162,7 @@
|
|||
#include "llcontainerview.h"
|
||||
#include "lltooltip.h"
|
||||
|
||||
#include "llsdutil.h"
|
||||
#include "llsdserialize.h"
|
||||
|
||||
#include "llworld.h"
|
||||
|
|
@ -1038,11 +1040,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 ;
|
||||
|
||||
|
|
@ -1515,17 +1544,24 @@ bool LLAppViewer::cleanup()
|
|||
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
|
||||
|
|
@ -4958,87 +4994,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;
|
||||
|
|
|
|||
|
|
@ -342,6 +342,15 @@ bool LLCommandLineParser::parseCommandLine(int argc, char **argv)
|
|||
return parseAndStoreResults(clp);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - Break out this funky parsing logic into separate method
|
||||
// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line
|
||||
// overload that supports quoted tokens)
|
||||
// - Unless this logic offers significant semantic benefits, replace it with
|
||||
// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a
|
||||
// string-valued variable to the empty string, because empty strings are
|
||||
// eliminated below.
|
||||
|
||||
bool LLCommandLineParser::parseCommandLineString(const std::string& str)
|
||||
{
|
||||
// Split the string content into tokens
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@
|
|||
|
||||
#include "lltrans.h"
|
||||
#include "llui.h"
|
||||
#include "llprocess.h"
|
||||
#include "llsdutil.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
// static
|
||||
const std::string LLExternalEditor::sFilenameMarker = "%s";
|
||||
|
|
@ -45,19 +48,8 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
|
|||
return EC_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
// Add the filename marker if missing.
|
||||
if (cmd.find(sFilenameMarker) == std::string::npos)
|
||||
{
|
||||
cmd += " \"" + sFilenameMarker + "\"";
|
||||
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
|
||||
}
|
||||
|
||||
string_vec_t tokens;
|
||||
if (tokenize(tokens, cmd) < 2) // 2 = bin + at least one arg (%s)
|
||||
{
|
||||
llwarns << "Error parsing editor command" << llendl;
|
||||
return EC_PARSE_ERROR;
|
||||
}
|
||||
tokenize(tokens, cmd);
|
||||
|
||||
// Check executable for existence.
|
||||
std::string bin_path = tokens[0];
|
||||
|
|
@ -68,51 +60,48 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
|
|||
}
|
||||
|
||||
// Save command.
|
||||
mProcess.setExecutable(bin_path);
|
||||
mArgs.clear();
|
||||
mProcessParams = LLProcess::Params();
|
||||
mProcessParams.executable = bin_path;
|
||||
for (size_t i = 1; i < tokens.size(); ++i)
|
||||
{
|
||||
if (i > 1) mArgs += " ";
|
||||
mArgs += "\"" + tokens[i] + "\"";
|
||||
mProcessParams.args.add(tokens[i]);
|
||||
}
|
||||
llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl;
|
||||
|
||||
// Add the filename marker if missing.
|
||||
if (cmd.find(sFilenameMarker) == std::string::npos)
|
||||
{
|
||||
mProcessParams.args.add(sFilenameMarker);
|
||||
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
|
||||
}
|
||||
|
||||
llinfos << "Setting command [" << mProcessParams << "]" << llendl;
|
||||
|
||||
return EC_SUCCESS;
|
||||
}
|
||||
|
||||
LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path)
|
||||
{
|
||||
std::string args = mArgs;
|
||||
if (mProcess.getExecutable().empty() || args.empty())
|
||||
if (std::string(mProcessParams.executable).empty() || mProcessParams.args.empty())
|
||||
{
|
||||
llwarns << "Editor command not set" << llendl;
|
||||
return EC_NOT_SPECIFIED;
|
||||
}
|
||||
|
||||
// Copy params block so we can replace sFilenameMarker
|
||||
LLProcess::Params params;
|
||||
params.executable = mProcessParams.executable;
|
||||
|
||||
// Substitute the filename marker in the command with the actual passed file name.
|
||||
LLStringUtil::replaceString(args, sFilenameMarker, file_path);
|
||||
|
||||
// Split command into separate tokens.
|
||||
string_vec_t tokens;
|
||||
tokenize(tokens, args);
|
||||
|
||||
// Set process arguments taken from the command.
|
||||
mProcess.clearArguments();
|
||||
for (string_vec_t::const_iterator arg_it = tokens.begin(); arg_it != tokens.end(); ++arg_it)
|
||||
BOOST_FOREACH(const std::string& arg, mProcessParams.args)
|
||||
{
|
||||
mProcess.addArgument(*arg_it);
|
||||
std::string fixed(arg);
|
||||
LLStringUtil::replaceString(fixed, sFilenameMarker, file_path);
|
||||
params.args.add(fixed);
|
||||
}
|
||||
|
||||
// Run the editor.
|
||||
llinfos << "Running editor command [" << mProcess.getExecutable() + " " + args << "]" << llendl;
|
||||
int result = mProcess.launch();
|
||||
if (result == 0)
|
||||
{
|
||||
// Prevent killing the process in destructor (will add it to the zombies list).
|
||||
mProcess.orphan();
|
||||
}
|
||||
|
||||
return result == 0 ? EC_SUCCESS : EC_FAILED_TO_RUN;
|
||||
// Run the editor. Prevent killing the process in destructor.
|
||||
params.autokill = false;
|
||||
return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN;
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -130,6 +119,12 @@ std::string LLExternalEditor::getErrorMessage(EErrorCode code)
|
|||
return LLTrans::getString("Unknown");
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - Unit-test this with tests like LLStringUtil::getTokens() (the
|
||||
// command-line overload that supports quoted tokens)
|
||||
// - Unless there are significant semantic differences, eliminate this method
|
||||
// and use LLStringUtil::getTokens() instead.
|
||||
|
||||
// static
|
||||
size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#ifndef LL_LLEXTERNALEDITOR_H
|
||||
#define LL_LLEXTERNALEDITOR_H
|
||||
|
||||
#include <llprocesslauncher.h>
|
||||
#include "llprocess.h"
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
|
|
@ -97,9 +97,7 @@ private:
|
|||
*/
|
||||
static const std::string sSetting;
|
||||
|
||||
|
||||
std::string mArgs;
|
||||
LLProcessLauncher mProcess;
|
||||
LLProcess::Params mProcessParams;
|
||||
};
|
||||
|
||||
#endif // LL_LLEXTERNALEDITOR_H
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@
|
|||
#include "lldeleteutils.h"
|
||||
#include "imageids.h"
|
||||
#include "indra_constants.h"
|
||||
#include "llinitparam.h"
|
||||
|
||||
//#include "linden_common.h"
|
||||
//#include "llpreprocessor.h"
|
||||
#include "llallocator.h"
|
||||
|
|
@ -124,7 +126,5 @@
|
|||
// Library includes from llmessage project
|
||||
#include "llcachename.h"
|
||||
|
||||
// Library includes from llxuixml
|
||||
#include "llinitparam.h"
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@
|
|||
#include "llviewerprecompiledheaders.h"
|
||||
#include "llvoicevivox.h"
|
||||
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include "llsdutil.h"
|
||||
|
||||
// Linden library includes
|
||||
|
|
@ -47,6 +45,7 @@
|
|||
#include "llbase64.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llappviewer.h" // for gDisconnected, gDisableVoice
|
||||
#include "llprocess.h"
|
||||
|
||||
// Viewer includes
|
||||
#include "llmutelist.h" // to check for muted avatars
|
||||
|
|
@ -242,59 +241,21 @@ void LLVivoxVoiceClientCapResponder::result(const LLSD& content)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if LL_WINDOWS
|
||||
static HANDLE sGatewayHandle = 0;
|
||||
static LLProcessPtr sGatewayPtr;
|
||||
|
||||
static bool isGatewayRunning()
|
||||
{
|
||||
bool result = false;
|
||||
if(sGatewayHandle != 0)
|
||||
{
|
||||
DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0);
|
||||
if(waitresult != WAIT_OBJECT_0)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static void killGateway()
|
||||
{
|
||||
if(sGatewayHandle != 0)
|
||||
{
|
||||
TerminateProcess(sGatewayHandle,0);
|
||||
}
|
||||
}
|
||||
|
||||
#else // Mac and linux
|
||||
|
||||
static pid_t sGatewayPID = 0;
|
||||
static bool isGatewayRunning()
|
||||
{
|
||||
bool result = false;
|
||||
if(sGatewayPID != 0)
|
||||
{
|
||||
// A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists.
|
||||
if(kill(sGatewayPID, 0) == 0)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return sGatewayPtr && sGatewayPtr->isRunning();
|
||||
}
|
||||
|
||||
static void killGateway()
|
||||
{
|
||||
if(sGatewayPID != 0)
|
||||
if (sGatewayPtr)
|
||||
{
|
||||
kill(sGatewayPID, SIGTERM);
|
||||
sGatewayPtr->kill();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LLVivoxVoiceClient::LLVivoxVoiceClient() :
|
||||
|
|
@ -790,7 +751,7 @@ void LLVivoxVoiceClient::stateMachine()
|
|||
}
|
||||
else if(!isGatewayRunning())
|
||||
{
|
||||
if(true)
|
||||
if (true) // production build, not test
|
||||
{
|
||||
// Launch the voice daemon
|
||||
|
||||
|
|
@ -809,102 +770,33 @@ void LLVivoxVoiceClient::stateMachine()
|
|||
#endif
|
||||
// See if the vivox executable exists
|
||||
llstat s;
|
||||
if(!LLFile::stat(exe_path, &s))
|
||||
if (!LLFile::stat(exe_path, &s))
|
||||
{
|
||||
// vivox executable exists. Build the command line and launch the daemon.
|
||||
LLProcess::Params params;
|
||||
params.executable = exe_path;
|
||||
// SLIM SDK: these arguments are no longer necessary.
|
||||
// std::string args = " -p tcp -h -c";
|
||||
std::string args;
|
||||
std::string cmd;
|
||||
std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
|
||||
|
||||
if(loglevel.empty())
|
||||
{
|
||||
loglevel = "-1"; // turn logging off completely
|
||||
}
|
||||
|
||||
args += " -ll ";
|
||||
args += loglevel;
|
||||
|
||||
LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL;
|
||||
|
||||
#if LL_WINDOWS
|
||||
PROCESS_INFORMATION pinfo;
|
||||
STARTUPINFOA sinfo;
|
||||
|
||||
memset(&sinfo, 0, sizeof(sinfo));
|
||||
|
||||
std::string exe_dir = gDirUtilp->getAppRODataDir();
|
||||
cmd = "SLVoice.exe";
|
||||
cmd += args;
|
||||
params.args.add("-ll");
|
||||
params.args.add(loglevel);
|
||||
params.cwd = gDirUtilp->getAppRODataDir();
|
||||
sGatewayPtr = LLProcess::create(params);
|
||||
|
||||
// So retarded. Windows requires that the second parameter to CreateProcessA be writable (non-const) string...
|
||||
char *args2 = new char[args.size() + 1];
|
||||
strcpy(args2, args.c_str());
|
||||
if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo))
|
||||
{
|
||||
// DWORD dwErr = GetLastError();
|
||||
}
|
||||
else
|
||||
{
|
||||
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
|
||||
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
|
||||
sGatewayHandle = pinfo.hProcess;
|
||||
CloseHandle(pinfo.hThread); // stops leaks - nothing else
|
||||
}
|
||||
|
||||
delete[] args2;
|
||||
#else // LL_WINDOWS
|
||||
// This should be the same for mac and linux
|
||||
{
|
||||
std::vector<std::string> arglist;
|
||||
arglist.push_back(exe_path);
|
||||
|
||||
// Split the argument string into separate strings for each argument
|
||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
|
||||
boost::char_separator<char> sep(" ");
|
||||
tokenizer tokens(args, sep);
|
||||
tokenizer::iterator token_iter;
|
||||
|
||||
for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
|
||||
{
|
||||
arglist.push_back(*token_iter);
|
||||
}
|
||||
|
||||
// create an argv vector for the child process
|
||||
char **fakeargv = new char*[arglist.size() + 1];
|
||||
int i;
|
||||
for(i=0; i < arglist.size(); i++)
|
||||
fakeargv[i] = const_cast<char*>(arglist[i].c_str());
|
||||
|
||||
fakeargv[i] = NULL;
|
||||
|
||||
fflush(NULL); // flush all buffers before the child inherits them
|
||||
pid_t id = vfork();
|
||||
if(id == 0)
|
||||
{
|
||||
// child
|
||||
execv(exe_path.c_str(), fakeargv);
|
||||
|
||||
// If we reach this point, the exec failed.
|
||||
// Use _exit() instead of exit() per the vfork man page.
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
// parent
|
||||
delete[] fakeargv;
|
||||
sGatewayPID = id;
|
||||
}
|
||||
#endif // LL_WINDOWS
|
||||
mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
// SLIM SDK: port changed from 44124 to 44125.
|
||||
// We can connect to a client gateway running on another host. This is useful for testing.
|
||||
// To do this, launch the gateway on a nearby host like this:
|
||||
|
|
|
|||
|
|
@ -1025,45 +1025,47 @@ class Linux_i686Manifest(LinuxManifest):
|
|||
super(Linux_i686Manifest, self).construct()
|
||||
|
||||
if self.prefix("../packages/lib/release", dst="lib"):
|
||||
self.path("libapr-1.so")
|
||||
self.path("libapr-1.so.0")
|
||||
self.path("libapr-1.so.0.4.2")
|
||||
self.path("libaprutil-1.so")
|
||||
self.path("libaprutil-1.so.0")
|
||||
self.path("libaprutil-1.so.0.3.10")
|
||||
self.path("libbreakpad_client.so.0.0.0")
|
||||
self.path("libbreakpad_client.so.0")
|
||||
self.path("libbreakpad_client.so")
|
||||
self.path("libapr-1.so*")
|
||||
self.path("libaprutil-1.so*")
|
||||
self.path("libbreakpad_client.so*")
|
||||
self.path("libcollada14dom.so")
|
||||
self.path("libdb-5.1.so")
|
||||
self.path("libdb-5.so")
|
||||
self.path("libdb.so")
|
||||
self.path("libcrypto.so.1.0.0")
|
||||
self.path("libexpat.so.1.5.2")
|
||||
self.path("libdb*.so")
|
||||
self.path("libcrypto.so.*")
|
||||
self.path("libexpat.so.*")
|
||||
self.path("libssl.so.1.0.0")
|
||||
self.path("libglod.so")
|
||||
self.path("libminizip.so")
|
||||
self.path("libuuid.so")
|
||||
self.path("libuuid.so.16")
|
||||
self.path("libuuid.so.16.0.22")
|
||||
self.path("libSDL-1.2.so.0.11.3")
|
||||
self.path("libSDL-1.2.so.0")
|
||||
self.path("libdirectfb-1.4.so.5.0.4")
|
||||
self.path("libuuid.so*")
|
||||
self.path("libSDL-1.2.so.*")
|
||||
self.path("libdirectfb-1.*.so.*")
|
||||
self.path("libfusion-1.*.so.*")
|
||||
self.path("libdirect-1.*.so.*")
|
||||
self.path("libopenjpeg.so*")
|
||||
self.path("libdirectfb-1.4.so.5")
|
||||
self.path("libfusion-1.4.so.5.0.4")
|
||||
self.path("libfusion-1.4.so.5")
|
||||
self.path("libdirect-1.4.so.5.0.4")
|
||||
self.path("libdirect-1.4.so.5")
|
||||
self.path("libopenjpeg.so.1.4.0")
|
||||
self.path("libopenjpeg.so.1")
|
||||
self.path("libopenjpeg.so")
|
||||
self.path("libalut.so")
|
||||
self.path("libopenal.so", "libopenal.so.1")
|
||||
self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname
|
||||
self.path("libfontconfig.so.1.4.4")
|
||||
self.path("libtcmalloc.so", "libtcmalloc.so") #formerly called google perf tools
|
||||
self.path("libtcmalloc.so.0", "libtcmalloc.so.0") #formerly called google perf tools
|
||||
self.path("libtcmalloc.so.0.1.0", "libtcmalloc.so.0.1.0") #formerly called google perf tools
|
||||
# KLUDGE: As of 2012-04-11, the 'fontconfig' package installs
|
||||
# libfontconfig.so.1.4.4, along with symlinks libfontconfig.so.1
|
||||
# and libfontconfig.so. Before we added support for library-file
|
||||
# wildcards, though, this self.path() call specifically named
|
||||
# libfontconfig.so.1.4.4 WITHOUT also copying the symlinks. When I
|
||||
# (nat) changed the call to self.path("libfontconfig.so.*"), we
|
||||
# ended up with the libfontconfig.so.1 symlink in the target
|
||||
# directory as well. But guess what! At least on Ubuntu 10.04,
|
||||
# certain viewer fonts look terrible with libfontconfig.so.1
|
||||
# present in the target directory. Removing that symlink suffices
|
||||
# to improve them. I suspect that means we actually do better when
|
||||
# the viewer fails to find our packaged libfontconfig.so*, falling
|
||||
# back on the system one instead -- but diagnosing and fixing that
|
||||
# is a bit out of scope for the present project. Meanwhile, this
|
||||
# particular wildcard specification gets us exactly what the
|
||||
# previous call did, without having to explicitly state the
|
||||
# version number.
|
||||
self.path("libfontconfig.so.*.*")
|
||||
self.path("libtcmalloc.so*") #formerly called google perf tools
|
||||
try:
|
||||
self.path("libfmod-3.75.so")
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* @file catch_and_store_what_in.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-15
|
||||
* @brief CATCH_AND_STORE_WHAT_IN() macro
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_CATCH_AND_STORE_WHAT_IN_H)
|
||||
#define LL_CATCH_AND_STORE_WHAT_IN_H
|
||||
|
||||
/**
|
||||
* Idiom useful for test programs: catch an expected exception, store its
|
||||
* what() string in a specified std::string variable. From there the caller
|
||||
* can do things like:
|
||||
* @code
|
||||
* ensure("expected exception not thrown", ! string.empty());
|
||||
* @endcode
|
||||
* or
|
||||
* @code
|
||||
* ensure_contains("exception doesn't mention blah", string, "blah");
|
||||
* @endcode
|
||||
* etc.
|
||||
*
|
||||
* The trouble is that when linking to a dynamic libllcommon.so on Linux, we
|
||||
* generally fail to catch the specific exception. Oddly, we can catch it as
|
||||
* std::runtime_error and validate its typeid().name(), so we do -- but that's
|
||||
* a lot of boilerplate per test. Encapsulate with this macro. Usage:
|
||||
*
|
||||
* @code
|
||||
* std::string threw;
|
||||
* try
|
||||
* {
|
||||
* some_call_that_should_throw_Foo();
|
||||
* }
|
||||
* CATCH_AND_STORE_WHAT_IN(threw, Foo)
|
||||
* ensure("some_call_that_should_throw_Foo() didn't throw", ! threw.empty());
|
||||
* @endcode
|
||||
*/
|
||||
#define CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION) \
|
||||
catch (const EXCEPTION& ex) \
|
||||
{ \
|
||||
(THREW) = ex.what(); \
|
||||
} \
|
||||
CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION)
|
||||
|
||||
#ifndef LL_LINUX
|
||||
#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) \
|
||||
/* only needed on Linux */
|
||||
#else // LL_LINUX
|
||||
|
||||
#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) \
|
||||
catch (const std::runtime_error& ex) \
|
||||
{ \
|
||||
/* This clause is needed on Linux, on the viewer side, because */ \
|
||||
/* the exception isn't caught by catch (const EXCEPTION&). */ \
|
||||
/* 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(EXCEPTION).name()) \
|
||||
{ \
|
||||
/* std::cerr << "Caught " << typeid(ex).name() */ \
|
||||
/* << " with Linux workaround" << std::endl; */ \
|
||||
(THREW) = ex.what(); \
|
||||
/*std::cout << ex.what() << std::endl;*/ \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* We don't even recognize this exception. Let it propagate */ \
|
||||
/* out to TUT to fail the test. */ \
|
||||
throw; \
|
||||
} \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
std::cerr << "Failed to catch expected exception " \
|
||||
<< #EXCEPTION << "!" << std::endl; \
|
||||
/* This indicates a problem in the test that should be addressed. */ \
|
||||
throw; \
|
||||
}
|
||||
|
||||
#endif // LL_LINUX
|
||||
|
||||
#endif /* ! defined(LL_CATCH_AND_STORE_WHAT_IN_H) */
|
||||
|
|
@ -49,46 +49,12 @@
|
|||
#include <boost/assign/list_of.hpp>
|
||||
// other Linden headers
|
||||
#include "lltut.h"
|
||||
#include "catch_and_store_what_in.h"
|
||||
#include "stringize.h"
|
||||
#include "tests/listener.h"
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
#ifdef LL_LINUX
|
||||
#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw) \
|
||||
catch (const std::runtime_error& ex) \
|
||||
{ \
|
||||
/* This clause is needed on Linux, on the viewer side, because the */ \
|
||||
/* 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. */ \
|
||||
/* This approach is also used in llsdmessage_test.cpp. */ \
|
||||
if (std::string(typeid(ex).name()) == typeid(exception).name()) \
|
||||
{ \
|
||||
threw = ex.what(); \
|
||||
/*std::cout << ex.what() << std::endl;*/ \
|
||||
} \
|
||||
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 " << #exception << "!" << \
|
||||
std::endl; \
|
||||
/* This indicates a problem in the test that should be addressed. */ \
|
||||
throw; \
|
||||
}
|
||||
|
||||
#else // LL_LINUX
|
||||
#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw) \
|
||||
/* Not needed on other platforms */
|
||||
#endif // LL_LINUX
|
||||
|
||||
template<typename T>
|
||||
T make(const T& value)
|
||||
{
|
||||
|
|
@ -178,11 +144,7 @@ void events_object::test<1>()
|
|||
per_frame.listen(listener0.getName(), // note bug, dup name
|
||||
boost::bind(&Listener::call, boost::ref(listener1), _1));
|
||||
}
|
||||
catch (const LLEventPump::DupListenerName& e)
|
||||
{
|
||||
threw = e.what();
|
||||
}
|
||||
CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupListenerName, threw)
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupListenerName)
|
||||
ensure_equals(threw,
|
||||
std::string("DupListenerName: "
|
||||
"Attempt to register duplicate listener name '") +
|
||||
|
|
@ -354,7 +316,6 @@ void events_object::test<7>()
|
|||
{
|
||||
set_test_name("listener dependency order");
|
||||
typedef LLEventPump::NameList NameList;
|
||||
typedef Collect::StringList StringList;
|
||||
LLEventPump& button(pumps.obtain("button"));
|
||||
Collect collector;
|
||||
button.listen("Mary",
|
||||
|
|
@ -368,7 +329,7 @@ void events_object::test<7>()
|
|||
button.listen("spot",
|
||||
boost::bind(&Collect::add, boost::ref(collector), "spot", _1));
|
||||
button.post(1);
|
||||
ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary")));
|
||||
ensure_equals(collector.result, make<StringVec>(list_of("spot")("checked")("Mary")));
|
||||
collector.clear();
|
||||
button.stopListening("Mary");
|
||||
button.listen("Mary",
|
||||
|
|
@ -377,7 +338,7 @@ void events_object::test<7>()
|
|||
// now "Mary" must come before "spot"
|
||||
make<NameList>(list_of("spot")));
|
||||
button.post(2);
|
||||
ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked")));
|
||||
ensure_equals(collector.result, make<StringVec>(list_of("Mary")("spot")("checked")));
|
||||
collector.clear();
|
||||
button.stopListening("spot");
|
||||
std::string threw;
|
||||
|
|
@ -388,12 +349,7 @@ void events_object::test<7>()
|
|||
// after "Mary" and "checked" -- whoops!
|
||||
make<NameList>(list_of("Mary")("checked")));
|
||||
}
|
||||
catch (const LLEventPump::Cycle& e)
|
||||
{
|
||||
threw = e.what();
|
||||
// std::cout << "Caught: " << e.what() << '\n';
|
||||
}
|
||||
CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::Cycle, threw)
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::Cycle)
|
||||
// Obviously the specific wording of the exception text can
|
||||
// change; go ahead and change the test to match.
|
||||
// Establish that it contains:
|
||||
|
|
@ -416,7 +372,7 @@ void events_object::test<7>()
|
|||
boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),
|
||||
make<NameList>(list_of("checked")));
|
||||
button.post(3);
|
||||
ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
|
||||
ensure_equals(collector.result, make<StringVec>(list_of("Mary")("checked")("yellow")("shoelaces")));
|
||||
collector.clear();
|
||||
threw.clear();
|
||||
try
|
||||
|
|
@ -426,12 +382,7 @@ void events_object::test<7>()
|
|||
make<NameList>(list_of("shoelaces")),
|
||||
make<NameList>(list_of("yellow")));
|
||||
}
|
||||
catch (const LLEventPump::OrderChange& e)
|
||||
{
|
||||
threw = e.what();
|
||||
// std::cout << "Caught: " << e.what() << '\n';
|
||||
}
|
||||
CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::OrderChange, threw)
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::OrderChange)
|
||||
// Same remarks about the specific wording of the exception. Just
|
||||
// ensure that it contains enough information to clarify the
|
||||
// problem and what must be done to resolve it.
|
||||
|
|
@ -443,7 +394,7 @@ void events_object::test<7>()
|
|||
ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");
|
||||
ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");
|
||||
button.post(4);
|
||||
ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces")));
|
||||
ensure_equals(collector.result, make<StringVec>(list_of("Mary")("checked")("yellow")("shoelaces")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
@ -459,12 +410,7 @@ void events_object::test<8>()
|
|||
// then another with a duplicate name.
|
||||
LLEventStream bob2("bob");
|
||||
}
|
||||
catch (const LLEventPump::DupPumpName& e)
|
||||
{
|
||||
threw = e.what();
|
||||
// std::cout << "Caught: " << e.what() << '\n';
|
||||
}
|
||||
CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupPumpName, threw)
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)
|
||||
ensure("Caught DupPumpName", !threw.empty());
|
||||
} // delete first 'bob'
|
||||
LLEventStream bob("bob"); // should work, previous one unregistered
|
||||
|
|
@ -505,11 +451,7 @@ void events_object::test<9>()
|
|||
LLListenerOrPumpName empty;
|
||||
empty(17);
|
||||
}
|
||||
catch (const LLListenerOrPumpName::Empty& e)
|
||||
{
|
||||
threw = e.what();
|
||||
}
|
||||
CATCH_MISSED_LINUX_EXCEPTION(LLListenerOrPumpName::Empty, threw)
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLListenerOrPumpName::Empty)
|
||||
|
||||
ensure("threw Empty", !threw.empty());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @file manageapr.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-13
|
||||
* @brief ManageAPR class for simple test programs
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_MANAGEAPR_H)
|
||||
#define LL_MANAGEAPR_H
|
||||
|
||||
#include "llapr.h"
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
/**
|
||||
* Declare a static instance of this class for dead-simple ll_init_apr() at
|
||||
* program startup, ll_cleanup_apr() at termination. This is recommended for
|
||||
* use only with simple test programs. Once you start introducing static
|
||||
* instances of other classes that depend on APR already being initialized,
|
||||
* the indeterminate static-constructor-order problem rears its ugly head.
|
||||
*/
|
||||
class ManageAPR: public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
ManageAPR()
|
||||
{
|
||||
ll_init_apr();
|
||||
}
|
||||
|
||||
~ManageAPR()
|
||||
{
|
||||
ll_cleanup_apr();
|
||||
}
|
||||
|
||||
static std::string strerror(apr_status_t rv)
|
||||
{
|
||||
char errbuf[256];
|
||||
apr_strerror(rv, errbuf, sizeof(errbuf));
|
||||
return errbuf;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_MANAGEAPR_H) */
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* @file namedtempfile.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-13
|
||||
* @brief NamedTempFile class for tests that need disk files as fixtures.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_NAMEDTEMPFILE_H)
|
||||
#define LL_NAMEDTEMPFILE_H
|
||||
|
||||
#include "llerror.h"
|
||||
#include "llapr.h"
|
||||
#include "apr_file_io.h"
|
||||
#include <string>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
#include <boost/lambda/bind.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
/**
|
||||
* Create a text file with specified content "somewhere in the
|
||||
* filesystem," cleaning up when it goes out of scope.
|
||||
*/
|
||||
class NamedTempFile: public boost::noncopyable
|
||||
{
|
||||
LOG_CLASS(NamedTempFile);
|
||||
public:
|
||||
NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp):
|
||||
mPool(pool)
|
||||
{
|
||||
createFile(pfx, boost::lambda::_1 << content);
|
||||
}
|
||||
|
||||
// Disambiguate when passing string literal
|
||||
NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp):
|
||||
mPool(pool)
|
||||
{
|
||||
createFile(pfx, boost::lambda::_1 << content);
|
||||
}
|
||||
|
||||
// Function that accepts an ostream ref and (presumably) writes stuff to
|
||||
// it, e.g.:
|
||||
// (boost::lambda::_1 << "the value is " << 17 << '\n')
|
||||
typedef boost::function<void(std::ostream&)> Streamer;
|
||||
|
||||
NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
|
||||
mPool(pool)
|
||||
{
|
||||
createFile(pfx, func);
|
||||
}
|
||||
|
||||
virtual ~NamedTempFile()
|
||||
{
|
||||
ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool));
|
||||
}
|
||||
|
||||
virtual std::string getName() const { return mPath; }
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
protected:
|
||||
void createFile(const std::string& pfx, const Streamer& func)
|
||||
{
|
||||
// Create file in a temporary place.
|
||||
const char* tempdir = NULL;
|
||||
ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool));
|
||||
|
||||
// Construct a temp filename template in that directory.
|
||||
char *tempname = NULL;
|
||||
ll_apr_assert_status(apr_filepath_merge(&tempname,
|
||||
tempdir,
|
||||
(pfx + "XXXXXX").c_str(),
|
||||
0,
|
||||
mPool));
|
||||
|
||||
// Create a temp file from that template.
|
||||
apr_file_t* fp = NULL;
|
||||
ll_apr_assert_status(apr_file_mktemp(&fp,
|
||||
tempname,
|
||||
APR_CREATE | APR_WRITE | APR_EXCL,
|
||||
mPool));
|
||||
// apr_file_mktemp() alters tempname with the actual name. Not until
|
||||
// now is it valid to capture as our mPath.
|
||||
mPath = tempname;
|
||||
|
||||
// Write desired content.
|
||||
std::ostringstream out;
|
||||
// Stream stuff to it.
|
||||
func(out);
|
||||
|
||||
std::string data(out.str());
|
||||
apr_size_t writelen(data.length());
|
||||
ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen));
|
||||
ll_apr_assert_status(apr_file_close(fp));
|
||||
llassert_always(writelen == data.length());
|
||||
}
|
||||
|
||||
std::string mPath;
|
||||
apr_pool_t* mPool;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a NamedTempFile with a specified filename extension. This is useful
|
||||
* when, for instance, you must be able to use the file in a Python import
|
||||
* statement.
|
||||
*
|
||||
* A NamedExtTempFile actually has two different names. We retain the original
|
||||
* no-extension name as a placeholder in the temp directory to ensure
|
||||
* uniqueness; to that we link the name plus the desired extension. Naturally,
|
||||
* both must be removed on destruction.
|
||||
*/
|
||||
class NamedExtTempFile: public NamedTempFile
|
||||
{
|
||||
LOG_CLASS(NamedExtTempFile);
|
||||
public:
|
||||
NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp):
|
||||
NamedTempFile(remove_dot(ext), content, pool),
|
||||
mLink(mPath + ensure_dot(ext))
|
||||
{
|
||||
linkto(mLink);
|
||||
}
|
||||
|
||||
// Disambiguate when passing string literal
|
||||
NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp):
|
||||
NamedTempFile(remove_dot(ext), content, pool),
|
||||
mLink(mPath + ensure_dot(ext))
|
||||
{
|
||||
linkto(mLink);
|
||||
}
|
||||
|
||||
NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
|
||||
NamedTempFile(remove_dot(ext), func, pool),
|
||||
mLink(mPath + ensure_dot(ext))
|
||||
{
|
||||
linkto(mLink);
|
||||
}
|
||||
|
||||
virtual ~NamedExtTempFile()
|
||||
{
|
||||
ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool));
|
||||
}
|
||||
|
||||
// Since the caller has gone to the trouble to create the name with the
|
||||
// extension, that should be the name we return. In this class, mPath is
|
||||
// just a placeholder to ensure that future createFile() calls won't
|
||||
// collide.
|
||||
virtual std::string getName() const { return mLink; }
|
||||
|
||||
static std::string ensure_dot(const std::string& ext)
|
||||
{
|
||||
if (ext.empty())
|
||||
{
|
||||
// What SHOULD we do when the caller makes a point of using
|
||||
// NamedExtTempFile to generate a file with a particular
|
||||
// extension, then passes an empty extension? Use just "."? That
|
||||
// sounds like a Bad Idea, especially on Windows. Treat that as a
|
||||
// coding error.
|
||||
LL_ERRS("NamedExtTempFile") << "passed empty extension" << LL_ENDL;
|
||||
}
|
||||
if (ext[0] == '.')
|
||||
{
|
||||
return ext;
|
||||
}
|
||||
return std::string(".") + ext;
|
||||
}
|
||||
|
||||
static std::string remove_dot(const std::string& ext)
|
||||
{
|
||||
std::string::size_type found = ext.find_first_not_of(".");
|
||||
if (found == std::string::npos)
|
||||
{
|
||||
return ext;
|
||||
}
|
||||
return ext.substr(found);
|
||||
}
|
||||
|
||||
private:
|
||||
void linkto(const std::string& path)
|
||||
{
|
||||
// This method assumes that since mPath (without extension) is
|
||||
// guaranteed by apr_file_mktemp() to be unique, then (mPath + any
|
||||
// extension) is also unique. This is likely, though not guaranteed:
|
||||
// files could be created in the same temp directory other than by
|
||||
// this class.
|
||||
ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str()));
|
||||
}
|
||||
|
||||
std::string mLink;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_NAMEDTEMPFILE_H) */
|
||||
|
|
@ -37,7 +37,9 @@
|
|||
#include "linden_common.h"
|
||||
#include "llerrorcontrol.h"
|
||||
#include "lltut.h"
|
||||
#include "tests/wrapllerrs.h" // RecorderProxy
|
||||
#include "stringize.h"
|
||||
#include "namedtempfile.h"
|
||||
|
||||
#include "apr_pools.h"
|
||||
#include "apr_getopt.h"
|
||||
|
|
@ -64,23 +66,87 @@
|
|||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
void wouldHaveCrashed(const std::string& message);
|
||||
|
||||
namespace tut
|
||||
{
|
||||
std::string sSourceDir;
|
||||
|
||||
test_runner_singleton runner;
|
||||
|
||||
test_runner_singleton runner;
|
||||
}
|
||||
|
||||
class LLReplayLog
|
||||
{
|
||||
public:
|
||||
LLReplayLog() {}
|
||||
virtual ~LLReplayLog() {}
|
||||
|
||||
virtual void reset() {}
|
||||
virtual void replay(std::ostream&) {}
|
||||
};
|
||||
|
||||
class LLReplayLogReal: public LLReplayLog, public LLError::Recorder, public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool):
|
||||
mOldSettings(LLError::saveAndResetSettings()),
|
||||
mProxy(new RecorderProxy(this)),
|
||||
mTempFile("log", "", pool), // create file
|
||||
mFile(mTempFile.getName().c_str()) // open it
|
||||
{
|
||||
LLError::setFatalFunction(wouldHaveCrashed);
|
||||
LLError::setDefaultLevel(level);
|
||||
LLError::addRecorder(mProxy);
|
||||
}
|
||||
|
||||
virtual ~LLReplayLogReal()
|
||||
{
|
||||
LLError::removeRecorder(mProxy);
|
||||
delete mProxy;
|
||||
LLError::restoreSettings(mOldSettings);
|
||||
}
|
||||
|
||||
virtual void recordMessage(LLError::ELevel level, const std::string& message)
|
||||
{
|
||||
mFile << message << std::endl;
|
||||
}
|
||||
|
||||
virtual void reset()
|
||||
{
|
||||
mFile.close();
|
||||
mFile.open(mTempFile.getName().c_str());
|
||||
}
|
||||
|
||||
virtual void replay(std::ostream& out)
|
||||
{
|
||||
mFile.close();
|
||||
std::ifstream inf(mTempFile.getName().c_str());
|
||||
std::string line;
|
||||
while (std::getline(inf, line))
|
||||
{
|
||||
out << line << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
LLError::Settings* mOldSettings;
|
||||
LLError::Recorder* mProxy;
|
||||
NamedTempFile mTempFile;
|
||||
std::ofstream mFile;
|
||||
};
|
||||
|
||||
class LLTestCallback : public tut::callback
|
||||
{
|
||||
public:
|
||||
LLTestCallback(bool verbose_mode, std::ostream *stream) :
|
||||
LLTestCallback(bool verbose_mode, std::ostream *stream,
|
||||
boost::shared_ptr<LLReplayLog> replayer) :
|
||||
mVerboseMode(verbose_mode),
|
||||
mTotalTests(0),
|
||||
mPassedTests(0),
|
||||
|
|
@ -88,8 +154,10 @@ public:
|
|||
mSkippedTests(0),
|
||||
// By default, capture a shared_ptr to std::cout, with a no-op "deleter"
|
||||
// so that destroying the shared_ptr makes no attempt to delete std::cout.
|
||||
mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1))
|
||||
{
|
||||
mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1)),
|
||||
mReplayer(replayer)
|
||||
if (stream)
|
||||
{
|
||||
if (stream)
|
||||
{
|
||||
// We want a boost::iostreams::tee_device that will stream to two
|
||||
|
|
@ -126,6 +194,16 @@ public:
|
|||
virtual void test_completed(const tut::test_result& tr)
|
||||
{
|
||||
++mTotalTests;
|
||||
|
||||
// If this test failed, dump requested log messages BEFORE stating the
|
||||
// test result.
|
||||
if (tr.result != tut::test_result::ok && tr.result != tut::test_result::skip)
|
||||
{
|
||||
mReplayer->replay(*mStream);
|
||||
}
|
||||
// Either way, clear stored messages in preparation for next test.
|
||||
mReplayer->reset();
|
||||
|
||||
std::ostringstream out;
|
||||
out << "[" << tr.group << ", " << tr.test;
|
||||
if (! tr.name.empty())
|
||||
|
|
@ -206,6 +284,7 @@ protected:
|
|||
int mFailedTests;
|
||||
int mSkippedTests;
|
||||
boost::shared_ptr<std::ostream> mStream;
|
||||
boost::shared_ptr<LLReplayLog> mReplayer;
|
||||
};
|
||||
|
||||
// TeamCity specific class which emits service messages
|
||||
|
|
@ -214,8 +293,9 @@ protected:
|
|||
class LLTCTestCallback : public LLTestCallback
|
||||
{
|
||||
public:
|
||||
LLTCTestCallback(bool verbose_mode, std::ostream *stream) :
|
||||
LLTestCallback(verbose_mode, stream)
|
||||
LLTCTestCallback(bool verbose_mode, std::ostream *stream,
|
||||
boost::shared_ptr<LLReplayLog> replayer) :
|
||||
LLTestCallback(verbose_mode, stream, replayer)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -356,6 +436,14 @@ void stream_usage(std::ostream& s, const char* app)
|
|||
++option;
|
||||
}
|
||||
|
||||
s << app << " is also sensitive to environment variables:\n"
|
||||
<< "LOGTEST=level : for all tests, emit log messages at level 'level'\n"
|
||||
<< "LOGFAIL=level : only for failed tests, emit log messages at level 'level'\n"
|
||||
<< "where 'level' is one of ALL, DEBUG, INFO, WARN, ERROR, NONE.\n"
|
||||
<< "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST.\n"
|
||||
<< "Setting LOGFAIL overrides both LOGTEST and --debug: the only log\n"
|
||||
<< "messages you will see will be for failed tests.\n\n";
|
||||
|
||||
s << "Examples:" << std::endl;
|
||||
s << " " << app << " --verbose" << std::endl;
|
||||
s << "\tRun all the tests and report all results." << std::endl;
|
||||
|
|
@ -392,8 +480,14 @@ int main(int argc, char **argv)
|
|||
LLError::initForApplication(".");
|
||||
LLError::setFatalFunction(wouldHaveCrashed);
|
||||
LLError::setDefaultLevel(LLError::LEVEL_ERROR);
|
||||
//< *TODO: should come from error config file. Note that we
|
||||
// have a command line option that sets this to debug.
|
||||
// ^ possibly overridden by --debug, LOGTEST or LOGFAIL
|
||||
|
||||
// LOGTEST overrides default, but can be overridden by --debug or LOGFAIL.
|
||||
const char* LOGTEST = getenv("LOGTEST");
|
||||
if (LOGTEST)
|
||||
{
|
||||
LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST));
|
||||
}
|
||||
|
||||
#ifdef CTYPE_WORKAROUND
|
||||
ctype_workaround();
|
||||
|
|
@ -468,8 +562,6 @@ int main(int argc, char **argv)
|
|||
wait_at_exit = true;
|
||||
break;
|
||||
case 'd':
|
||||
// *TODO: should come from error config file. We set it to
|
||||
// ERROR by default, so this allows full debug levels.
|
||||
LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
|
||||
break;
|
||||
case 'x':
|
||||
|
|
@ -484,14 +576,28 @@ int main(int argc, char **argv)
|
|||
|
||||
// run the tests
|
||||
|
||||
LLTestCallback* mycallback;
|
||||
if (getenv("TEAMCITY_PROJECT_NAME"))
|
||||
const char* LOGFAIL = getenv("LOGFAIL");
|
||||
boost::shared_ptr<LLReplayLog> replayer;
|
||||
// As described in stream_usage(), LOGFAIL overrides both --debug and
|
||||
// LOGTEST.
|
||||
if (LOGFAIL)
|
||||
{
|
||||
mycallback = new LLTCTestCallback(verbose_mode, output.get());
|
||||
LLError::ELevel level = LLError::decodeLevel(LOGFAIL);
|
||||
replayer.reset(new LLReplayLogReal(level, pool));
|
||||
}
|
||||
else
|
||||
{
|
||||
mycallback = new LLTestCallback(verbose_mode, output.get());
|
||||
replayer.reset(new LLReplayLog());
|
||||
}
|
||||
|
||||
LLTestCallback* mycallback;
|
||||
if (getenv("TEAMCITY_PROJECT_NAME"))
|
||||
{
|
||||
mycallback = new LLTCTestCallback(verbose_mode, output, replayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
mycallback = new LLTestCallback(verbose_mode, output, replayer);
|
||||
}
|
||||
|
||||
tut::runner.get().set_callback(mycallback);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
/**
|
||||
/**
|
||||
* @file llupdatedownloader.cpp
|
||||
*
|
||||
* $LicenseInfo:firstyear=2010&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$
|
||||
*/
|
||||
|
|
@ -58,7 +58,7 @@ public:
|
|||
int onProgress(double downloadSize, double bytesDownloaded);
|
||||
void resume(void);
|
||||
void setBandwidthLimit(U64 bytesPerSecond);
|
||||
|
||||
|
||||
private:
|
||||
curl_off_t mBandwidthLimit;
|
||||
bool mCancelled;
|
||||
|
|
@ -69,7 +69,7 @@ private:
|
|||
unsigned char mDownloadPercent;
|
||||
std::string mDownloadRecordPath;
|
||||
curl_slist * mHeaderList;
|
||||
|
||||
|
||||
void initializeCurlGet(std::string const & url, bool processHeader);
|
||||
void resumeDownloading(size_t startByte);
|
||||
void run(void);
|
||||
|
|
@ -93,7 +93,7 @@ namespace {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
|
||||
};
|
||||
|
||||
|
|
@ -192,18 +192,18 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client &
|
|||
mHeaderList(0)
|
||||
{
|
||||
CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
|
||||
llverify(code == CURLE_OK); // TODO: real error handling here.
|
||||
llverify(code == CURLE_OK); // TODO: real error handling here.
|
||||
}
|
||||
|
||||
|
||||
LLUpdateDownloader::Implementation::~Implementation()
|
||||
{
|
||||
if(isDownloading())
|
||||
if(isDownloading())
|
||||
{
|
||||
cancel();
|
||||
shutdown();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
; // No op.
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ void LLUpdateDownloader::Implementation::cancel(void)
|
|||
{
|
||||
mCancelled = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LLUpdateDownloader::Implementation::download(LLURI const & uri,
|
||||
std::string const & hash,
|
||||
|
|
@ -259,24 +259,24 @@ void LLUpdateDownloader::Implementation::resume(void)
|
|||
mClient.downloadError("no download marker");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
|
||||
|
||||
|
||||
if(!mDownloadData.asBoolean()) {
|
||||
mClient.downloadError("no download information in marker");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::string filePath = mDownloadData["path"].asString();
|
||||
try {
|
||||
if(LLFile::isfile(filePath)) {
|
||||
if(LLFile::isfile(filePath)) {
|
||||
llstat fileStatus;
|
||||
LLFile::stat(filePath, &fileStatus);
|
||||
if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
|
||||
resumeDownloading(fileStatus.st_size);
|
||||
} else if(!validateDownload()) {
|
||||
LLFile::remove(filePath);
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
mDownloadData["hash"].asString(),
|
||||
mDownloadData["update_version"].asString(),
|
||||
mDownloadData["required"].asBoolean());
|
||||
|
|
@ -284,7 +284,7 @@ void LLUpdateDownloader::Implementation::resume(void)
|
|||
mClient.downloadComplete(mDownloadData);
|
||||
}
|
||||
} else {
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
mDownloadData["hash"].asString(),
|
||||
mDownloadData["update_version"].asString(),
|
||||
mDownloadData["required"].asBoolean());
|
||||
|
|
@ -301,7 +301,7 @@ void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
|
|||
llassert(mCurl != 0);
|
||||
mBandwidthLimit = bytesPerSecond;
|
||||
CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
|
||||
if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
|
||||
if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
|
||||
"unable to change dowload bandwidth" << LL_ENDL;
|
||||
} else {
|
||||
mBandwidthLimit = bytesPerSecond;
|
||||
|
|
@ -315,7 +315,7 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
std::string header(headerPtr, headerPtr + size);
|
||||
size_t colonPosition = header.find(':');
|
||||
if(colonPosition == std::string::npos) return size; // HTML response; ignore.
|
||||
|
||||
|
||||
if(header.substr(0, colonPosition) == "Content-Length") {
|
||||
try {
|
||||
size_t firstDigitPos = header.find_first_of("0123456789", colonPosition);
|
||||
|
|
@ -323,18 +323,18 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);
|
||||
size_t size = boost::lexical_cast<size_t>(contentLength);
|
||||
LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
|
||||
|
||||
|
||||
mDownloadData["size"] = LLSD(LLSD::Integer(size));
|
||||
llofstream odataStream(mDownloadRecordPath);
|
||||
LLSDSerialize::toPrettyXML(mDownloadData, odataStream);
|
||||
} catch (std::exception const & e) {
|
||||
LL_WARNS("UpdateDownload") << "unable to read content length ("
|
||||
LL_WARNS("UpdateDownload") << "unable to read content length ("
|
||||
<< e.what() << ")" << LL_ENDL;
|
||||
}
|
||||
} else {
|
||||
; // No op.
|
||||
}
|
||||
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
|
@ -342,9 +342,9 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
|
||||
{
|
||||
if(mCancelled) return 0; // Forces a write error which will halt curl thread.
|
||||
if((size == 0) || (buffer == 0)) return 0;
|
||||
|
||||
mDownloadStream.write(reinterpret_cast<const char *>(buffer), size);
|
||||
if((size == 0) || (buffer == 0)) return 0;
|
||||
|
||||
mDownloadStream.write(static_cast<const char *>(buffer), size);
|
||||
if(mDownloadStream.bad()) {
|
||||
return 0;
|
||||
} else {
|
||||
|
|
@ -358,7 +358,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
|
|||
int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
|
||||
if(downloadPercent > mDownloadPercent) {
|
||||
mDownloadPercent = downloadPercent;
|
||||
|
||||
|
||||
LLSD event;
|
||||
event["pump"] = LLUpdaterService::pumpName();
|
||||
LLSD payload;
|
||||
|
|
@ -367,12 +367,12 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
|
|||
payload["bytes_downloaded"] = bytesDownloaded;
|
||||
event["payload"] = payload;
|
||||
LLEventPumps::instance().obtain("mainlooprepeater").post(event);
|
||||
|
||||
|
||||
LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
|
||||
} else {
|
||||
; // Keep events to a reasonalbe number.
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -396,13 +396,13 @@ void LLUpdateDownloader::Implementation::run(void)
|
|||
LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL;
|
||||
// Do not call back client.
|
||||
} else {
|
||||
LL_WARNS("UpdateDownload") << "download failed with error '" <<
|
||||
LL_WARNS("UpdateDownload") << "download failed with error '" <<
|
||||
curl_easy_strerror(code) << "'" << LL_ENDL;
|
||||
LLFile::remove(mDownloadRecordPath);
|
||||
if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
|
||||
mClient.downloadError("curl error");
|
||||
}
|
||||
|
||||
|
||||
if(mHeaderList) {
|
||||
curl_slist_free_all(mHeaderList);
|
||||
mHeaderList = 0;
|
||||
|
|
@ -412,17 +412,17 @@ void LLUpdateDownloader::Implementation::run(void)
|
|||
|
||||
void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
|
||||
{
|
||||
if(mCurl == 0)
|
||||
if(mCurl == 0)
|
||||
{
|
||||
mCurl = LLCurl::newEasyHandle();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_reset(mCurl);
|
||||
}
|
||||
|
||||
|
||||
if(mCurl == 0) throw DownloadError("failed to initialize curl");
|
||||
|
||||
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
|
||||
|
|
@ -439,7 +439,7 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u
|
|||
// if it's a required update set the bandwidth limit to 0 (unlimited)
|
||||
curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit;
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit));
|
||||
|
||||
|
||||
mDownloadPercent = 0;
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +450,7 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
|
|||
<< " at byte " << startByte << LL_ENDL;
|
||||
|
||||
initializeCurlGet(mDownloadData["url"].asString(), false);
|
||||
|
||||
|
||||
// The header 'Range: bytes n-' will request the bytes remaining in the
|
||||
// source begining with byte n and ending with the last byte.
|
||||
boost::format rangeHeaderFormat("Range: bytes=%u-");
|
||||
|
|
@ -458,7 +458,7 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
|
|||
mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
|
||||
if(mHeaderList == 0) throw DownloadError("cannot add Range header");
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
|
||||
|
||||
|
||||
mDownloadStream.open(mDownloadData["path"].asString(),
|
||||
std::ios_base::out | std::ios_base::binary | std::ios_base::app);
|
||||
start();
|
||||
|
|
@ -479,10 +479,10 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std
|
|||
LL_INFOS("UpdateDownload") << "downloading " << filePath
|
||||
<< " from " << uri.asString() << LL_ENDL;
|
||||
LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
|
||||
|
||||
|
||||
llofstream dataStream(mDownloadRecordPath);
|
||||
LLSDSerialize::toPrettyXML(mDownloadData, dataStream);
|
||||
|
||||
|
||||
mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
|
||||
initializeCurlGet(uri.asString(), true);
|
||||
start();
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@
|
|||
#include "linden_common.h"
|
||||
#include <apr_file_io.h>
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#include "llupdateinstaller.h"
|
||||
#include "lldir.h"
|
||||
|
||||
#include "llsd.h"
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!).
|
||||
|
|
@ -78,15 +78,13 @@ int ll_install_update(std::string const & script,
|
|||
llinfos << "UpdateInstaller: installing " << updatePath << " using " <<
|
||||
actualScriptPath << LL_ENDL;
|
||||
|
||||
LLProcessLauncher launcher;
|
||||
launcher.setExecutable(actualScriptPath);
|
||||
launcher.addArgument(updatePath);
|
||||
launcher.addArgument(ll_install_failed_marker_path().c_str());
|
||||
launcher.addArgument(boost::lexical_cast<std::string>(required));
|
||||
int result = launcher.launch();
|
||||
launcher.orphan();
|
||||
|
||||
return result;
|
||||
LLProcess::Params params;
|
||||
params.executable = actualScriptPath;
|
||||
params.args.add(updatePath);
|
||||
params.args.add(ll_install_failed_marker_path());
|
||||
params.args.add(boost::lexical_cast<std::string>(required));
|
||||
params.autokill = false;
|
||||
return LLProcess::create(params)? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue