From ee9fb80dd49ff4d4029a7d2b7f02fefeffd15bd3 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:18:13 +0200 Subject: [PATCH 001/125] Only link against llcommon, not against ${LLCOMMON_LIBRARIES} (which does include a lot more and those cannot be added as dependencies) --- indra/media_plugins/gstreamer010/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt index 6d18814b1e..8ed92f0e19 100644 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ b/indra/media_plugins/gstreamer010/CMakeLists.txt @@ -69,7 +69,7 @@ target_link_libraries(media_plugin_gstreamer010 add_dependencies(media_plugin_gstreamer010 ${LLPLUGIN_LIBRARIES} ${MEDIA_PLUGIN_BASE_LIBRARIES} - ${LLCOMMON_LIBRARIES} + llcommon ) From 83aa2854b464fe1f6c1c00583ce006ef4425f989 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:19:25 +0200 Subject: [PATCH 002/125] Do not link llwindow against itself. --- indra/llwindow/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 0743fd899f..edc6d69365 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -198,5 +198,6 @@ if (SDL_FOUND) ) endif (SDL_FOUND) - target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) +# ND: What kind of sorcery is this supposed to be, link llwindow against itself? cmake does not like this a bit. +# target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) From 3cc36b5376ebc3b52c7d53d71ae3385fcfc16676 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:22:46 +0200 Subject: [PATCH 003/125] Do not link against pthread like this, again newer cmake versions do not like it. --- indra/cmake/LLPlugin.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/LLPlugin.cmake b/indra/cmake/LLPlugin.cmake index 399cb332dd..a946d1e0ef 100644 --- a/indra/cmake/LLPlugin.cmake +++ b/indra/cmake/LLPlugin.cmake @@ -8,7 +8,7 @@ set(LLPLUGIN_INCLUDE_DIRS if (LINUX) # In order to support using ld.gold on linux, we need to explicitely # specify all libraries that llplugin uses. - set(LLPLUGIN_LIBRARIES llplugin pthread) + set(LLPLUGIN_LIBRARIES llplugin) else (LINUX) set(LLPLUGIN_LIBRARIES llplugin) endif (LINUX) From 5634a918428deb332e4676a190f6241ea3d2e89b Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:23:11 +0200 Subject: [PATCH 004/125] cmake findfile for nghttp2. --- indra/cmake/FindNGHTTP2.cmake | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 indra/cmake/FindNGHTTP2.cmake diff --git a/indra/cmake/FindNGHTTP2.cmake b/indra/cmake/FindNGHTTP2.cmake new file mode 100644 index 0000000000..e9c126660b --- /dev/null +++ b/indra/cmake/FindNGHTTP2.cmake @@ -0,0 +1,41 @@ +# -*- cmake -*- + +include(FindPkgConfig) +pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) + +if (FALSE) +FIND_PATH(NGHTTP2_INCLUDE_DIR nghttp2/nghttp2.h) + + +FIND_LIBRARY(NGHTTP2_LIBRARIES + NAMES libnghttp2 + PATHS /usr/lib /usr/local/lib + ) + +IF (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) + SET(NGHTTP2_LIBRARIES ${NGHTTP2_LIBRARY}) + SET(NGHTTP2_FOUND "YES") +ELSE (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) + SET(NGHTTP2_FOUND "NO") +ENDIF (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) + + +IF (NGHTTP2_FOUND) + IF (NOT NGHTTP2_FIND_QUIETLY) + MESSAGE(STATUS "Found JSONCpp: ${NGHTTP2_LIBRARIES}") + ENDIF (NOT NGHTTP2_FIND_QUIETLY) +ELSE (NGHTTP2_FOUND) + IF (NGHTTP2_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find JSONCpp library") + ENDIF (NGHTTP2_FIND_REQUIRED) +ENDIF (NGHTTP2_FOUND) + +# Deprecated declarations. +SET (NATIVE_NGHTTP2_INCLUDE_PATH ${NGHTTP2_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_NGHTTP2_LIB_PATH ${NGHTTP2_LIBRARY} PATH) + +MARK_AS_ADVANCED( + NGHTTP2_LIBRARY + NGHTTP2_INCLUDE_DIR + ) +endif() \ No newline at end of file From 0882225f3465310bca747fc3a15911127d42c250 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:23:38 +0200 Subject: [PATCH 005/125] Do not use berkelydb. --- indra/cmake/APR.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/APR.cmake b/indra/cmake/APR.cmake index 1a01671002..47567ee009 100644 --- a/indra/cmake/APR.cmake +++ b/indra/cmake/APR.cmake @@ -1,4 +1,4 @@ -include(BerkeleyDB) +#include(BerkeleyDB) include(Linking) include(Prebuilt) From bf3a76a2bb6bda13194d8e45dd21aa77f67b0658 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:24:23 +0200 Subject: [PATCH 006/125] Do not define LL_OS_DRAGDROP_ENABLED via cmake. From my understanding does not come via viewer variables now. --- indra/cmake/DragDrop.cmake | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/indra/cmake/DragDrop.cmake b/indra/cmake/DragDrop.cmake index 73ef59b18f..342423d0b5 100644 --- a/indra/cmake/DragDrop.cmake +++ b/indra/cmake/DragDrop.cmake @@ -1,20 +1 @@ # -*- cmake -*- - - set(OS_DRAG_DROP ON CACHE BOOL "Build the viewer with OS level drag and drop turned on or off") - - if (OS_DRAG_DROP) - - if (WINDOWS) - add_definitions(-DLL_OS_DRAGDROP_ENABLED=1) - endif (WINDOWS) - - if (DARWIN) - add_definitions(-DLL_OS_DRAGDROP_ENABLED=1) - endif (DARWIN) - - if (LINUX) - add_definitions(-DLL_OS_DRAGDROP_ENABLED=0) - endif (LINUX) - - endif (OS_DRAG_DROP) - From 9a4c1b5cf4de64d73748195a87beb80fba3d974d Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:27:14 +0200 Subject: [PATCH 007/125] Changes to find system libraries when using standalone on Linux (Debian Jessie). Todo: Use pkg-config for Find*.cmake? --- indra/cmake/Boost.cmake | 19 +++++++++++-------- indra/cmake/FindGLH.cmake | 4 +--- indra/cmake/FindGoogleBreakpad.cmake | 4 +++- indra/cmake/FindHUNSPELL.cmake | 4 ++-- indra/cmake/FindJsonCpp.cmake | 5 +++-- indra/cmake/FindURIPARSER.cmake | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 180a84dbcf..992de12815 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -5,16 +5,19 @@ set(Boost_FIND_QUIETLY ON) set(Boost_FIND_REQUIRED ON) if (USESYSTEMLIBS) + SET(BOOST_INCLUDEDIR ${LIBS_PREBUILT_DIR}/include ) + SET(BOOST_LIBRARYDIR ${LIBS_PREBUILT_DIR}/lib/release ) include(FindBoost) - set(BOOST_CONTEXT_LIBRARY boost_context-mt) - set(BOOST_COROUTINE_LIBRARY boost_coroutine-mt) - set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt) - set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt) - set(BOOST_REGEX_LIBRARY boost_regex-mt) - set(BOOST_SIGNALS_LIBRARY boost_signals-mt) - set(BOOST_SYSTEM_LIBRARY boost_system-mt) - set(BOOST_THREAD_LIBRARY boost_thread-mt) + include_directories( ${BOOST_INCLUDEDIR} ) + FIND_LIBRARY(BOOST_CONTEXT_LIBRARY boost_context-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_COROUTINE_LIBRARY boost_coroutine-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_REGEX_LIBRARY boost_regex-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_SIGNALS_LIBRARY boost_signals-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_SYSTEM_LIBRARY boost_system-mt PATHS ${BOOST_LIBRARYDIR} ) + FIND_LIBRARY(BOOST_THREAD_LIBRARY boost_thread-mt PATHS ${BOOST_LIBRARYDIR} ) else (USESYSTEMLIBS) use_prebuilt_binary(boost) set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include) diff --git a/indra/cmake/FindGLH.cmake b/indra/cmake/FindGLH.cmake index 3d16adaf03..7288eb1d12 100644 --- a/indra/cmake/FindGLH.cmake +++ b/indra/cmake/FindGLH.cmake @@ -6,9 +6,7 @@ # GLH_INCLUDE_DIR, where to find glh/glh_linear.h. # GLH_FOUND, If false, do not try to use GLH. -find_path(GLH_INCLUDE_DIR glh/glh_linear.h - NO_SYSTEM_ENVIRONMENT_PATH - ) +find_path(GLH_INCLUDE_DIR glh/glh_linear.h ) if (GLH_INCLUDE_DIR) set(GLH_FOUND "YES") diff --git a/indra/cmake/FindGoogleBreakpad.cmake b/indra/cmake/FindGoogleBreakpad.cmake index 1a0493be5e..1f05a988b0 100644 --- a/indra/cmake/FindGoogleBreakpad.cmake +++ b/indra/cmake/FindGoogleBreakpad.cmake @@ -9,13 +9,15 @@ # also defined, but not for general use are # BREAKPAD_EXCEPTION_HANDLER_LIBRARY, where to find the Google BreakPad library. -FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR google_breakpad/exception_handler.h) +FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR google_breakpad/client/linux/handler/exception_handler.h) SET(BREAKPAD_EXCEPTION_HANDLER_NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} breakpad_client) FIND_LIBRARY(BREAKPAD_EXCEPTION_HANDLER_LIBRARY NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} ) +include_directories( ${BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR}/google_breakpad/ ) + IF (BREAKPAD_EXCEPTION_HANDLER_LIBRARY AND BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR) SET(BREAKPAD_EXCEPTION_HANDLER_LIBRARIES ${BREAKPAD_EXCEPTION_HANDLER_LIBRARY}) SET(BREAKPAD_EXCEPTION_HANDLER_FOUND "YES") diff --git a/indra/cmake/FindHUNSPELL.cmake b/indra/cmake/FindHUNSPELL.cmake index d411bdb9e5..9097537a14 100644 --- a/indra/cmake/FindHUNSPELL.cmake +++ b/indra/cmake/FindHUNSPELL.cmake @@ -6,11 +6,11 @@ # HUNSPELL_LIBRARY, the library needed to use HUNSPELL. # HUNSPELL_FOUND, If false, do not try to use HUNSPELL. -find_path(HUNSPELL_INCLUDE_DIR hunspell.h +find_path(HUNSPELL_INCLUDE_DIR hunspell.hxx PATH_SUFFIXES hunspell ) -set(HUNSPELL_NAMES ${HUNSPELL_NAMES} libhunspell-1.3 libhunspell) +set(HUNSPELL_NAMES ${HUNSPELL_NAMES} libhunspell-1.3.a libhunspell.a) find_library(HUNSPELL_LIBRARY NAMES ${HUNSPELL_NAMES} ) diff --git a/indra/cmake/FindJsonCpp.cmake b/indra/cmake/FindJsonCpp.cmake index 9398779cff..58887aac1e 100644 --- a/indra/cmake/FindJsonCpp.cmake +++ b/indra/cmake/FindJsonCpp.cmake @@ -9,9 +9,10 @@ # also defined, but not for general use are # JSONCPP_LIBRARY, where to find the jsoncpp library. -FIND_PATH(JSONCPP_INCLUDE_DIR jsoncpp/json.h +FIND_PATH(JSONCPP_INCLUDE_DIR json/json.h /usr/local/include /usr/include +/usr/include/jsoncpp ) # Get the GCC compiler version @@ -25,7 +26,7 @@ EXEC_PROGRAM(${CMAKE_CXX_COMPILER} SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson_linux-gcc-${_gcc_COMPILER_VERSION}_libmt.so) IF (USESYSTEMLIBS) # On standalone, assume that the system installed library was compiled with the used compiler. - SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson.so) + SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson.so libjsoncpp.so ) ENDIF (USESYSTEMLIBS) FIND_LIBRARY(JSONCPP_LIBRARY NAMES ${JSONCPP_NAMES} diff --git a/indra/cmake/FindURIPARSER.cmake b/indra/cmake/FindURIPARSER.cmake index 8ab9f0f4ed..ab5c8c16ee 100644 --- a/indra/cmake/FindURIPARSER.cmake +++ b/indra/cmake/FindURIPARSER.cmake @@ -14,7 +14,7 @@ # Note: Since this file is only used for standalone, the windows # specific parts were left out. -FIND_PATH(URIPARSER_INCLUDE_DIR uriparser/uri.h +FIND_PATH(URIPARSER_INCLUDE_DIR uriparser/Uri.h NO_SYSTEM_ENVIRONMENT_PATH ) From 4af80ab7cb2f6a766a920b07be378ed0d08bff22 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:29:04 +0200 Subject: [PATCH 008/125] g_thread_init is deprecated and should not be used anymore. Away with it. --- indra/llwindow/llwindowsdl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index c20e639fc7..4974574df0 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -119,7 +119,6 @@ bool LLWindowSDL::ll_try_gtk_init(void) if (!tried_gtk_init) { tried_gtk_init = TRUE; - if (!g_thread_supported ()) g_thread_init (NULL); maybe_lock_display(); gtk_is_good = gtk_init_check(NULL, NULL); maybe_unlock_display(); From 1531775be7d120d4d04fa433bf7250168608bf3b Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:37:21 +0200 Subject: [PATCH 009/125] If building standlone do not blindly add ../json to the include search path. IMO that's horrible as generic includes like value.h/write.h/config.h/features.h should not be reachable with just #include . This will easily clash with system includes. IMO normal builds should use #include "json/" too. --- indra/llcommon/llsdjson.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h index 2be7112404..000369d39f 100644 --- a/indra/llcommon/llsdjson.h +++ b/indra/llcommon/llsdjson.h @@ -34,7 +34,12 @@ #include "stdtypes.h" #include "llsd.h" + +#ifndef LL_USESYSTEMLIBS #include "value.h" +#else +#include "json/value.h" +#endif /// Convert a parsed JSON structure into LLSD maintaining member names and /// array indexes. From f955e9c88a8182055bc2a3a047e44faa22e4edb3 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:44:37 +0200 Subject: [PATCH 010/125] More of that json/ include path madness. --- indra/llmessage/llcorehttputil.cpp | 7 +++++++ indra/newview/llmarketplacefunctions.cpp | 7 +++++++ indra/newview/lltranslate.cpp | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/indra/llmessage/llcorehttputil.cpp b/indra/llmessage/llcorehttputil.cpp index 24387fbffd..26c98b7bae 100644 --- a/indra/llmessage/llcorehttputil.cpp +++ b/indra/llmessage/llcorehttputil.cpp @@ -35,8 +35,15 @@ #include "llsd.h" #include "llsdjson.h" #include "llsdserialize.h" + +#ifndef LL_USESYSTEMLIBS #include "reader.h" // JSON #include "writer.h" // JSON +#else +#include "json/reader.h" // JSON +#include "json/writer.h" // JSON +#endif + #include "llvfile.h" #include "message.h" // for getting the port diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index a0e19f2d19..816e91cdc6 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -41,8 +41,15 @@ #include "llviewermedia.h" #include "llviewernetwork.h" #include "llviewerregion.h" + +#ifndef LL_USESYSTEMLIBS #include "reader.h" // JSON #include "writer.h" // JSON +#else +#include "json/reader.h" // JSON +#include "json/writer.h" // JSON +#endif + #include "lleventcoro.h" #include "llcoros.h" #include "llcorehttputil.h" diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 1936e24761..7f42d79e41 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -36,7 +36,13 @@ #include "llversioninfo.h" #include "llviewercontrol.h" #include "llcoros.h" + +#ifndef LL_USESYSTEMLIBS #include "reader.h" +#else +#include "json/reader.h" +#endif + #include "llcorehttputil.h" From 76e75fd2207cd7dba159b3a4f54463b168e09edc Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:52:51 +0200 Subject: [PATCH 011/125] gstreamer010, remove some deprecated glib calls and make the compiler in C++11 mode happy. --- indra/media_plugins/gstreamer010/CMakeLists.txt | 2 +- .../gstreamer010/media_plugin_gstreamer010.cpp | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt index 8ed92f0e19..ea9354cb93 100644 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ b/indra/media_plugins/gstreamer010/CMakeLists.txt @@ -37,7 +37,7 @@ if(NOT ADDRESS_SIZE EQUAL 32) if(WINDOWS) ##add_definitions(/FIXED:NO) else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) + add_definitions(-fPIC -Wno-literal-suffix) endif(WINDOWS) endif(NOT ADDRESS_SIZE EQUAL 32) diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp index 352b63583e..969dbdfb61 100644 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp @@ -76,7 +76,7 @@ private: bool play(double rate); bool getTimePos(double &sec_out); - static const double MIN_LOOP_SEC = 1.0F; + static constexpr double MIN_LOOP_SEC = 1.0F; bool mIsLooping; @@ -798,10 +798,6 @@ MediaPluginGStreamer010::startup() // only do global GStreamer initialization once. if (!mDoneInit) { - g_thread_init(NULL); - - // Init the glib type system - we need it. - g_type_init(); // Get symbols! #if LL_DARWIN From f11edff29a535321b3f89b64a6781e9a52fab922 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 12:54:09 +0200 Subject: [PATCH 012/125] Linux: Do not try to compile the VLC plugin for now. --- indra/media_plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 1a5cc8ec9a..75edd58a44 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(base) if (LINUX) add_subdirectory(gstreamer010) - add_subdirectory(libvlc) + #add_subdirectory(libvlc) add_subdirectory(example) endif (LINUX) From 3764f78811fdd5c6205c5435291db5feabd8dfc6 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 13:09:01 +0200 Subject: [PATCH 013/125] Correctly link against pthreads, solution from https://stackoverflow.com/questions/5395309/how-do-i-force-cmake-to-include-pthread-option-during-compilation --- indra/cmake/LLPlugin.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/cmake/LLPlugin.cmake b/indra/cmake/LLPlugin.cmake index a946d1e0ef..74470d3974 100644 --- a/indra/cmake/LLPlugin.cmake +++ b/indra/cmake/LLPlugin.cmake @@ -8,7 +8,10 @@ set(LLPLUGIN_INCLUDE_DIRS if (LINUX) # In order to support using ld.gold on linux, we need to explicitely # specify all libraries that llplugin uses. - set(LLPLUGIN_LIBRARIES llplugin) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package( Threads ) + set(LLPLUGIN_LIBRARIES llplugin Threads::Threads) else (LINUX) set(LLPLUGIN_LIBRARIES llplugin) endif (LINUX) From baffa915c3af0f4cb25390a54c89d7a25630178d Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 13:16:28 +0200 Subject: [PATCH 014/125] Use correct types and casts for OpenGL --- indra/llrender/llglslshader.cpp | 4 ++-- indra/llrender/llvertexbuffer.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 970502f2d6..0f30446528 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -273,10 +273,10 @@ void LLGLSLShader::readProfileQuery(U32 count, U32 mode) glEndQueryARB(GL_TIME_ELAPSED); glEndQueryARB(GL_SAMPLES_PASSED); - U64 time_elapsed = 0; + GLuint64 time_elapsed = 0; glGetQueryObjectui64v(mTimerQuery, GL_QUERY_RESULT, &time_elapsed); - U64 samples_passed = 0; + GLuint64 samples_passed = 0; glGetQueryObjectui64v(mSamplesQuery, GL_QUERY_RESULT, &samples_passed); sTotalTimeElapsed += time_elapsed; diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index f10301b42d..388481caa8 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -1473,7 +1473,7 @@ void LLVertexBuffer::setupVertexArray() //glVertexattribIPointer requires GLSL 1.30 or later if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) { - glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i], (const GLvoid*) mOffsets[i]); + glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i], reinterpret_cast( mOffsets[i] )); } #endif } From 9af28d7b59d6864500647eadd7b94df36d139b21 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 13:16:47 +0200 Subject: [PATCH 015/125] Link against pthreads. --- indra/linux_crash_logger/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index 029096df37..468d0d1bb6 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -59,6 +59,9 @@ add_executable(linux-crash-logger ${linux_crash_logger_SOURCE_FILES}) # llcommon uses `clock_gettime' which is provided by librt on linux. set(LIBRT_LIBRARY rt) +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) target_link_libraries(linux-crash-logger ${LLCRASHLOGGER_LIBRARIES} @@ -75,6 +78,7 @@ target_link_libraries(linux-crash-logger ${DB_LIBRARIES} ${FREETYPE_LIBRARIES} ${LIBRT_LIBRARY} + Threads::Threads ) add_custom_target(linux-crash-logger-target ALL From 4ed702747da6b585049a46446398eae814826f4d Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 14:56:09 +0200 Subject: [PATCH 016/125] Change return value of getFinalAStart/getFinalAEnd/getFinalBStart/getFinalBEnd as they would return a reference to a temporary object. This might have to do with the fact that I cannot use the official Havok stub right now. But the change will not be ill formed or have a huge impact either way. --- indra/newview/llpathfindingpathtool.cpp | 8 ++++---- indra/newview/llpathfindingpathtool.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/indra/newview/llpathfindingpathtool.cpp b/indra/newview/llpathfindingpathtool.cpp index 3187325101..e846008cb8 100644 --- a/indra/newview/llpathfindingpathtool.cpp +++ b/indra/newview/llpathfindingpathtool.cpp @@ -377,12 +377,12 @@ bool LLPathfindingPathTool::hasFinalA() const return mFinalPathData.mHasPointA; } -const LLVector3 &LLPathfindingPathTool::getFinalAStart() const +const LLVector3 LLPathfindingPathTool::getFinalAStart() const { return mFinalPathData.mStartPointA; } -const LLVector3 &LLPathfindingPathTool::getFinalAEnd() const +const LLVector3 LLPathfindingPathTool::getFinalAEnd() const { return mFinalPathData.mEndPointA; } @@ -411,12 +411,12 @@ bool LLPathfindingPathTool::hasFinalB() const return mFinalPathData.mHasPointB; } -const LLVector3 &LLPathfindingPathTool::getFinalBStart() const +const LLVector3 LLPathfindingPathTool::getFinalBStart() const { return mFinalPathData.mStartPointB; } -const LLVector3 &LLPathfindingPathTool::getFinalBEnd() const +const LLVector3 LLPathfindingPathTool::getFinalBEnd() const { return mFinalPathData.mEndPointB; } diff --git a/indra/newview/llpathfindingpathtool.h b/indra/newview/llpathfindingpathtool.h index 88cb3a15f8..f762ed476b 100644 --- a/indra/newview/llpathfindingpathtool.h +++ b/indra/newview/llpathfindingpathtool.h @@ -105,16 +105,16 @@ private: void setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); bool hasFinalA() const; - const LLVector3 &getFinalAStart() const; - const LLVector3 &getFinalAEnd() const; + const LLVector3 getFinalAStart() const; + const LLVector3 getFinalAEnd() const; void setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); bool hasTempA() const; void setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); bool hasFinalB() const; - const LLVector3 &getFinalBStart() const; - const LLVector3 &getFinalBEnd() const; + const LLVector3 getFinalBStart() const; + const LLVector3 getFinalBEnd() const; void setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); bool hasTempB() const; From 57f8eacf80117d9f619724bd1b5159480deb8efa Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 14:58:11 +0200 Subject: [PATCH 017/125] GCC was a bit cranky that inside boost there might have been unitialized variables. --- indra/newview/llgroupmgr.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp index 152d0eddcd..097c06d098 100644 --- a/indra/newview/llgroupmgr.cpp +++ b/indra/newview/llgroupmgr.cpp @@ -62,6 +62,15 @@ #pragma warning (disable:4702) #endif +// ND: Disable some warnings on newer GCC versions. +#if LL_LINUX +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ ) >= 40800 + #pragma GCC diagnostic ignored "-Wuninitialized" + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + #endif +#endif +// + #include #if LL_MSVC From f27b0867d6b4584a428ed7fed6b1ad4769c57d16 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 14:59:03 +0200 Subject: [PATCH 018/125] GCC was complaning that in some edge cases variables might have used when not correctly initialized. So they all get initialized now. --- indra/newview/llpanelface.cpp | 29 +++++++++++++++-------------- indra/newview/llpanelface.h | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 7e75dca908..b72f0ff694 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -1183,9 +1183,10 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) LLSelectedTE::getFullbright(fullbright_flag,identical_fullbright); - getChild("checkbox fullbright")->setValue((S32)(fullbright_flag != 0)); - getChildView("checkbox fullbright")->setEnabled(editable); - getChild("checkbox fullbright")->setTentative(!identical_fullbright); + LLUICtrl* check_fullbright = getChild("checkbox fullbright"); + check_fullbright->setValue((S32)(fullbright_flag != 0)); + check_fullbright->setEnabled(editable); + check_fullbright->setTentative(!identical_fullbright); } // Repeats per meter @@ -2448,17 +2449,17 @@ void LLPanelFace::LLSelectedTE::getFace(LLFace*& face_to_return, bool& identical void LLPanelFace::LLSelectedTE::getImageFormat(LLGLenum& image_format_to_return, bool& identical_face) { - LLGLenum image_format; - struct LLSelectedTEGetImageFormat : public LLSelectedTEGetFunctor - { - LLGLenum get(LLViewerObject* object, S32 te_index) - { - LLViewerTexture* image = object->getTEImage(te_index); - return image ? image->getPrimaryFormat() : GL_RGB; - } - } get_glenum; - identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_glenum, image_format); - image_format_to_return = image_format; + LLGLenum image_format(0); + struct LLSelectedTEGetImageFormat : public LLSelectedTEGetFunctor + { + LLGLenum get(LLViewerObject* object, S32 te_index) + { + LLViewerTexture* image = object->getTEImage(te_index); + return image ? image->getPrimaryFormat() : GL_RGB; + } + } get_glenum; + identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_glenum, image_format); + image_format_to_return = image_format; } void LLPanelFace::LLSelectedTE::getTexId(LLUUID& id, bool& identical) diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index 7c084cb0ab..0dfbd063fc 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -336,7 +336,7 @@ private: ReturnType (LLMaterial::* const MaterialGetFunc)() const > static void getTEMaterialValue(DataType& data_to_return, bool& identical,DataType default_value) { - DataType data_value; + DataType data_value = default_value; struct GetTEMaterialVal : public LLSelectedTEGetFunctor { GetTEMaterialVal(DataType default_value) : _default(default_value) {} @@ -369,7 +369,7 @@ private: ReturnType (LLTextureEntry::* const TEGetFunc)() const > static void getTEValue(DataType& data_to_return, bool& identical, DataType default_value) { - DataType data_value; + DataType data_value = default_value; struct GetTEVal : public LLSelectedTEGetFunctor { GetTEVal(DataType default_value) : _default(default_value) {} From 266baf334d2c8655bfe5117b89d02693a0fa9b7b Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 14:59:52 +0200 Subject: [PATCH 019/125] Fix wrong preprocessor statement. Interesting no other compiler did complain about this so far. --- indra/newview/llappviewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 464e216cf0..4cd30af6e0 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -292,7 +292,7 @@ S32 gLastExecDuration = -1; // (<0 indicates unknown) # define LL_PLATFORM_KEY "mac" #elif LL_LINUX # define LL_PLATFORM_KEY "lnx" -else +#else # error "Unknown Platform" #endif const char* gPlatform = LL_PLATFORM_KEY; From 4bacf2ad7b36ad0c73c709d6cce22c35480d2470 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 15:49:10 +0200 Subject: [PATCH 020/125] Another one of those json includes .. --- indra/newview/llwebprofile.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/newview/llwebprofile.cpp b/indra/newview/llwebprofile.cpp index 06ce497510..09bc2d83bb 100644 --- a/indra/newview/llwebprofile.cpp +++ b/indra/newview/llwebprofile.cpp @@ -42,7 +42,11 @@ #include "llcorehttputil.h" // third-party +#ifndef LL_USESYSTEMLIBS #include "reader.h" // JSON +#else +#include "json/reader.h" // JSON +#endif /* * Workflow: From b80a17cf244189bdd8b2ca104f2dfea329a4d6b3 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 15:49:34 +0200 Subject: [PATCH 021/125] Remove deprecated glib calls. --- indra/newview/llappviewerlinux.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 6f32aab851..6e821c8b74 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -122,12 +122,6 @@ LLAppViewerLinux::~LLAppViewerLinux() bool LLAppViewerLinux::init() { - // g_thread_init() must be called before *any* use of glib, *and* - // before any mutexes are held, *and* some of our third-party - // libraries likes to use glib functions; in short, do this here - // really early in app startup! - if (!g_thread_supported ()) g_thread_init (NULL); - bool success = LLAppViewer::init(); #if LL_SEND_CRASH_REPORTS @@ -266,8 +260,6 @@ bool LLAppViewerLinux::initSLURLHandler() return false; // failed } - g_type_init(); - //ViewerAppAPI *api_server = (ViewerAppAPI*) g_object_new(viewerappapi_get_type(), NULL); @@ -286,8 +278,6 @@ bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url) DBusGConnection *bus; GError *error = NULL; - g_type_init(); - bus = lldbus_g_bus_get (DBUS_BUS_SESSION, &error); if (bus) { From 7403a693992d2736129f94fdcbccd4fa82e92a2b Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 15:52:59 +0200 Subject: [PATCH 022/125] linux-crash-logger-strip-target is a nonsensical dependency for the target "viewer". Strip is done in viewer-manifest.py and does not need an extra target. --- indra/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index a40b2c0846..049c939b34 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -63,7 +63,6 @@ if (LINUX) include(LLAppearanceUtility) add_subdirectory(${LLAPPEARANCEUTILITY_SRC_DIR} ${LLAPPEARANCEUTILITY_BIN_DIR}) endif (INSTALL_PROPRIETARY) - add_dependencies(viewer linux-crash-logger-strip-target) elseif (DARWIN) add_subdirectory(${VIEWER_PREFIX}mac_crash_logger) add_dependencies(viewer mac-crash-logger) From 8bd33e499f8e6ee85c30c71fd4953b4b8c41f572 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 15:54:03 +0200 Subject: [PATCH 023/125] stage_third_party_libs in it's current form makes no sense when building standalone. --- indra/llcommon/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d9eb13d65a..246475f9a0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -299,7 +299,9 @@ if (DARWIN) target_link_libraries(llcommon ${CARBON_LIBRARY}) endif (DARWIN) +if( NOT USESYSTEMLIBS ) add_dependencies(llcommon stage_third_party_libs) +endif() if (LL_TESTS) include(LLAddBuildTest) From 991a621658af469833f47aac98d8bbac82cfa99c Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 17:12:00 +0200 Subject: [PATCH 024/125] Linux: Link SLPlugin with -Wl,--as-needed --- indra/llplugin/slplugin/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt index 0e5e835777..5645d15185 100644 --- a/indra/llplugin/slplugin/CMakeLists.txt +++ b/indra/llplugin/slplugin/CMakeLists.txt @@ -82,6 +82,10 @@ if (DARWIN) ) endif (DARWIN) +if( LINUX ) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") +endif() + if (LL_TESTS) ll_deploy_sharedlibs_command(SLPlugin) endif (LL_TESTS) From 168da04e713f21e717b647a1927d60d083153911 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 6 Apr 2018 17:29:41 +0200 Subject: [PATCH 025/125] As I have no access to the havok sources or the stub or the stub source: Use https://bitbucket.org/NickyD/p64_3p-ndphysicsstub. For Linux also disable media_plugin_libvlc as a dependency (at least for now). --- indra/cmake/LLPhysicsExtensions.cmake | 4 ++-- indra/newview/CMakeLists.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indra/cmake/LLPhysicsExtensions.cmake b/indra/cmake/LLPhysicsExtensions.cmake index e6afee762e..e18722c752 100644 --- a/indra/cmake/LLPhysicsExtensions.cmake +++ b/indra/cmake/LLPhysicsExtensions.cmake @@ -27,8 +27,8 @@ elseif (HAVOK_TPV) else (HAVOK) use_prebuilt_binary(llphysicsextensions_stub) - set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/stub) - set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensionsstub) + #set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/stub) + set(LLPHYSICSEXTENSIONS_LIBRARIES libhacd.a libnd_hacdConvexDecomposition.a libnd_Pathing.a ) endif (HAVOK) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 6b16713add..b1dbe7c9d5 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -54,10 +54,10 @@ include(VisualLeakDetector) include(ZLIB) include(URIPARSER) -if (NOT HAVOK_TPV) +if ( HAVOK ) # When using HAVOK_TPV, the library is precompiled, so no need for this add_subdirectory(${LLPHYSICSEXTENSIONS_SRC_DIR} llphysicsextensions) -endif (NOT HAVOK_TPV) +endif ( HAVOK ) if(FMODEX) include_directories(${FMODEX_INCLUDE_DIR}) @@ -1990,7 +1990,7 @@ if (LINUX) linux-crash-logger SLPlugin media_plugin_gstreamer010 - media_plugin_libvlc + #media_plugin_libvlc llcommon ) From 76ac9481266099acfcecaa61ff8a85864a73968c Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 11 Apr 2018 13:51:54 +0200 Subject: [PATCH 026/125] Make cmake search for collada and dependencies when building standalone. --- indra/cmake/LLPrimitive.cmake | 107 ++++++++++++++++++++----------- indra/llprimitive/CMakeLists.txt | 4 +- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index 93626f689f..3888208fa6 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -4,46 +4,75 @@ include(Prebuilt) include(Boost) -use_prebuilt_binary(colladadom) -use_prebuilt_binary(pcre) -use_prebuilt_binary(libxml2) - set(LLPRIMITIVE_INCLUDE_DIRS ${LIBS_OPEN_DIR}/llprimitive ) -if (WINDOWS) - set(LLPRIMITIVE_LIBRARIES - debug llprimitive - optimized llprimitive - debug libcollada14dom23-sd - optimized libcollada14dom23-s - libxml2_a - debug pcrecppd - optimized pcrecpp - debug pcred - optimized pcre - ${BOOST_SYSTEM_LIBRARIES} - ) -elseif (DARWIN) - set(LLPRIMITIVE_LIBRARIES - llprimitive - debug collada14dom-d - optimized collada14dom - minizip - xml2 - pcrecpp - pcre - iconv # Required by libxml2 - ) -elseif (LINUX) - set(LLPRIMITIVE_LIBRARIES - llprimitive - debug collada14dom-d - optimized collada14dom - minizip - xml2 - pcrecpp - pcre - ) -endif (WINDOWS) +if( NOT USESYSTEMLIBS ) + use_prebuilt_binary(colladadom) + use_prebuilt_binary(pcre) + use_prebuilt_binary(libxml2) + + set( COLLADADOM_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/collada ${LIBS_PREBUILT_DIR}/include/collada/1.4 ) + + if (WINDOWS) + set(LLPRIMITIVE_LIBRARIES + debug llprimitive + optimized llprimitive + debug libcollada14dom23-sd + optimized libcollada14dom23-s + libxml2_a + debug pcrecppd + optimized pcrecpp + debug pcred + optimized pcre + ${BOOST_SYSTEM_LIBRARIES} + ) + elseif (DARWIN) + set(LLPRIMITIVE_LIBRARIES + llprimitive + debug collada14dom-d + optimized collada14dom + minizip + xml2 + pcrecpp + pcre + iconv # Required by libxml2 + ) + elseif (LINUX) + set(LLPRIMITIVE_LIBRARIES + llprimitive + debug collada14dom-d + optimized collada14dom + minizip + xml2 + pcrecpp + pcre + ) + endif (WINDOWS) + +else() + + include(FindPkgConfig) + pkg_check_modules( MINIZIP REQUIRED minizip ) + pkg_check_modules( LIBXML2 REQUIRED libxml-2.0 ) + pkg_check_modules( LIBPCRECPP REQUIRED libpcrecpp ) + + find_library( COLLADADOM_LIBRARY collada14dom ) + find_path( COLLADADOM_INCLUDE_DIR collada/dae.h ) + + if( COLLADADOM_INCLUDE_DIR STREQUAL "COLLADADOM_INCLUDE_DIR-NOTFOUND" ) + message( FATAL_ERROR "Cannot find colladadom include dir" ) + endif() + + set( COLLADADOM_INCLUDE_DIRS ${COLLADADOM_INCLUDE_DIR}/collada ${COLLADADOM_INCLUDE_DIR}/collada/1.4 ) + + set(LLPRIMITIVE_LIBRARIES + llprimitive + ${COLLADADOM_LIBRARY} + ${MINIZIP_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${LIBPRCECCPP_LIBRARIES} + ) + +endif() diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt index dd2e806dda..95cc13e646 100644 --- a/indra/llprimitive/CMakeLists.txt +++ b/indra/llprimitive/CMakeLists.txt @@ -10,14 +10,14 @@ include(LLCoreHttp) include(LLXML) include(LLPhysicsExtensions) include(LLCharacter) +include (LLPrimitive ) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LIBS_PREBUILT_DIR}/include/collada - ${LIBS_PREBUILT_DIR}/include/collada/1.4 + ${COLLADADOM_INCLUDE_DIRS} ${LLCHARACTER_INCLUDE_DIRS} ) include_directories(SYSTEM From f2ffb01d9783b83c0764156dd0714416f44a26d7 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 11 Apr 2018 13:52:59 +0200 Subject: [PATCH 027/125] Use pkg-config where possible; add more cmake machinery to find all needed libraries when building standalone. --- indra/cmake/Boost.cmake | 5 +- indra/cmake/FindAPR.cmake | 96 ++------------------------- indra/cmake/FindGoogleBreakpad.cmake | 1 + indra/cmake/FindHUNSPELL.cmake | 40 ++--------- indra/cmake/FindJsonCpp.cmake | 61 +---------------- indra/cmake/FindNGHTTP2.cmake | 36 ---------- indra/cmake/FindOpenJPEG.cmake | 47 +------------ indra/cmake/FindURIPARSER.cmake | 47 +------------ indra/cmake/FindZLIB.cmake | 47 +------------ indra/cmake/GLOD.cmake | 21 +++++- indra/cmake/GoogleMock.cmake | 57 ++++++++++------ indra/cmake/LLPhysicsExtensions.cmake | 7 +- 12 files changed, 84 insertions(+), 381 deletions(-) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 992de12815..b31b1480af 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -5,11 +5,12 @@ set(Boost_FIND_QUIETLY ON) set(Boost_FIND_REQUIRED ON) if (USESYSTEMLIBS) - SET(BOOST_INCLUDEDIR ${LIBS_PREBUILT_DIR}/include ) - SET(BOOST_LIBRARYDIR ${LIBS_PREBUILT_DIR}/lib/release ) + SET(BOOST_INCLUDEDIR ${CMAKE_INCLUDE_PATH} ) + SET(BOOST_LIBRARYDIR ${CMAKE_LIBRARY_PATH} ) include(FindBoost) include_directories( ${BOOST_INCLUDEDIR} ) + FIND_LIBRARY(BOOST_CONTEXT_LIBRARY boost_context-mt PATHS ${BOOST_LIBRARYDIR} ) FIND_LIBRARY(BOOST_COROUTINE_LIBRARY boost_coroutine-mt PATHS ${BOOST_LIBRARYDIR} ) FIND_LIBRARY(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt PATHS ${BOOST_LIBRARYDIR} ) diff --git a/indra/cmake/FindAPR.cmake b/indra/cmake/FindAPR.cmake index 906b6c9452..993748597a 100644 --- a/indra/cmake/FindAPR.cmake +++ b/indra/cmake/FindAPR.cmake @@ -1,94 +1,8 @@ # -*- cmake -*- -# - Find Apache Portable Runtime -# Find the APR includes and libraries -# This module defines -# APR_INCLUDE_DIR and APRUTIL_INCLUDE_DIR, where to find apr.h, etc. -# APR_LIBRARIES and APRUTIL_LIBRARIES, the libraries needed to use APR. -# APR_FOUND and APRUTIL_FOUND, If false, do not try to use APR. -# also defined, but not for general use are -# APR_LIBRARY and APRUTIL_LIBRARY, where to find the APR library. +include(FindPkgConfig) +pkg_check_modules( APR REQUIRED apr-1 ) +set( APR_INCLUDE_DIR ${APR_INCLUDE_DIRS} ) -# APR first. - -FIND_PATH(APR_INCLUDE_DIR apr.h -/usr/local/include/apr-1 -/usr/local/include/apr-1.0 -/usr/include/apr-1 -/usr/include/apr-1.0 -) - -SET(APR_NAMES ${APR_NAMES} apr-1) -FIND_LIBRARY(APR_LIBRARY - NAMES ${APR_NAMES} - PATHS /usr/lib /usr/local/lib - ) - -IF (APR_LIBRARY AND APR_INCLUDE_DIR) - SET(APR_LIBRARIES ${APR_LIBRARY}) - SET(APR_FOUND "YES") -ELSE (APR_LIBRARY AND APR_INCLUDE_DIR) - SET(APR_FOUND "NO") -ENDIF (APR_LIBRARY AND APR_INCLUDE_DIR) - - -IF (APR_FOUND) - IF (NOT APR_FIND_QUIETLY) - MESSAGE(STATUS "Found APR: ${APR_LIBRARIES}") - ENDIF (NOT APR_FIND_QUIETLY) -ELSE (APR_FOUND) - IF (APR_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find APR library") - ENDIF (APR_FIND_REQUIRED) -ENDIF (APR_FOUND) - -# Deprecated declarations. -SET (NATIVE_APR_INCLUDE_PATH ${APR_INCLUDE_DIR} ) -GET_FILENAME_COMPONENT (NATIVE_APR_LIB_PATH ${APR_LIBRARY} PATH) - -MARK_AS_ADVANCED( - APR_LIBRARY - APR_INCLUDE_DIR - ) - -# Next, APRUTIL. - -FIND_PATH(APRUTIL_INCLUDE_DIR apu.h -/usr/local/include/apr-1 -/usr/local/include/apr-1.0 -/usr/include/apr-1 -/usr/include/apr-1.0 -) - -SET(APRUTIL_NAMES ${APRUTIL_NAMES} aprutil-1) -FIND_LIBRARY(APRUTIL_LIBRARY - NAMES ${APRUTIL_NAMES} - PATHS /usr/lib /usr/local/lib - ) - -IF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) - SET(APRUTIL_LIBRARIES ${APRUTIL_LIBRARY}) - SET(APRUTIL_FOUND "YES") -ELSE (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) - SET(APRUTIL_FOUND "NO") -ENDIF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) - - -IF (APRUTIL_FOUND) - IF (NOT APRUTIL_FIND_QUIETLY) - MESSAGE(STATUS "Found APRUTIL: ${APRUTIL_LIBRARIES}") - ENDIF (NOT APRUTIL_FIND_QUIETLY) -ELSE (APRUTIL_FOUND) - IF (APRUTIL_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find APRUTIL library") - ENDIF (APRUTIL_FIND_REQUIRED) -ENDIF (APRUTIL_FOUND) - -# Deprecated declarations. -SET (NATIVE_APRUTIL_INCLUDE_PATH ${APRUTIL_INCLUDE_DIR} ) -GET_FILENAME_COMPONENT (NATIVE_APRUTIL_LIB_PATH ${APRUTIL_LIBRARY} PATH) - -MARK_AS_ADVANCED( - APRUTIL_LIBRARY - APRUTIL_INCLUDE_DIR - ) +pkg_check_modules( APRUTIL REQUIRED apr-util-1 ) +set( APRUTIL_INCLUDE_DIR ${APRUTIL_INCLUDE_DIRS} ) diff --git a/indra/cmake/FindGoogleBreakpad.cmake b/indra/cmake/FindGoogleBreakpad.cmake index 1f05a988b0..4cda561864 100644 --- a/indra/cmake/FindGoogleBreakpad.cmake +++ b/indra/cmake/FindGoogleBreakpad.cmake @@ -15,6 +15,7 @@ SET(BREAKPAD_EXCEPTION_HANDLER_NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} breakpa FIND_LIBRARY(BREAKPAD_EXCEPTION_HANDLER_LIBRARY NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} ) +message ( ${BREAKPAD_EXCEPTION_HANDLER_LIBRARY} ) include_directories( ${BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR}/google_breakpad/ ) diff --git a/indra/cmake/FindHUNSPELL.cmake b/indra/cmake/FindHUNSPELL.cmake index 9097537a14..c2b7c589f6 100644 --- a/indra/cmake/FindHUNSPELL.cmake +++ b/indra/cmake/FindHUNSPELL.cmake @@ -1,38 +1,6 @@ # -*- cmake -*- -# - Find HUNSPELL -# This module defines -# HUNSPELL_INCLUDE_DIR, where to find libhunspell.h, etc. -# HUNSPELL_LIBRARY, the library needed to use HUNSPELL. -# HUNSPELL_FOUND, If false, do not try to use HUNSPELL. - -find_path(HUNSPELL_INCLUDE_DIR hunspell.hxx - PATH_SUFFIXES hunspell - ) - -set(HUNSPELL_NAMES ${HUNSPELL_NAMES} libhunspell-1.3.a libhunspell.a) -find_library(HUNSPELL_LIBRARY - NAMES ${HUNSPELL_NAMES} - ) - -if (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) - set(HUNSPELL_FOUND "YES") -else (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) - set(HUNSPELL_FOUND "NO") -endif (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) - - -if (HUNSPELL_FOUND) - if (NOT HUNSPELL_FIND_QUIETLY) - message(STATUS "Found Hunspell: Library in '${HUNSPELL_LIBRARY}' and header in '${HUNSPELL_INCLUDE_DIR}' ") - endif (NOT HUNSPELL_FIND_QUIETLY) -else (HUNSPELL_FOUND) - if (HUNSPELL_FIND_REQUIRED) - message(FATAL_ERROR " * * *\nCould not find HUNSPELL library! * * *") - endif (HUNSPELL_FIND_REQUIRED) -endif (HUNSPELL_FOUND) - -mark_as_advanced( - HUNSPELL_LIBRARY - HUNSPELL_INCLUDE_DIR - ) +include(FindPkgConfig) +pkg_check_modules( HUNSPELL REQUIRED hunspell ) +set( HUNSPELL_INCLUDE_DIR ${HUNSPELL_INCLUDE_DIRS} ) +set( HUNSPELL_LIBRARY ${HUNSPELL_LIBRARIES} ) \ No newline at end of file diff --git a/indra/cmake/FindJsonCpp.cmake b/indra/cmake/FindJsonCpp.cmake index 58887aac1e..c02794ed67 100644 --- a/indra/cmake/FindJsonCpp.cmake +++ b/indra/cmake/FindJsonCpp.cmake @@ -1,61 +1,6 @@ # -*- cmake -*- -# - Find JSONCpp -# Find the JSONCpp includes and library -# This module defines -# JSONCPP_INCLUDE_DIR, where to find json.h, etc. -# JSONCPP_LIBRARIES, the libraries needed to use jsoncpp. -# JSONCPP_FOUND, If false, do not try to use jsoncpp. -# also defined, but not for general use are -# JSONCPP_LIBRARY, where to find the jsoncpp library. +include(FindPkgConfig) +pkg_check_modules( JSONCPP REQUIRED jsoncpp ) +set( JSONCPP_INCLUDE_DIR ${JSONCPP_INCLUDE_DIRS} ) -FIND_PATH(JSONCPP_INCLUDE_DIR json/json.h -/usr/local/include -/usr/include -/usr/include/jsoncpp -) - -# Get the GCC compiler version -EXEC_PROGRAM(${CMAKE_CXX_COMPILER} - ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion - OUTPUT_VARIABLE _gcc_COMPILER_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - -# Try to find a library that was compiled with the same compiler version as we currently use. -SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson_linux-gcc-${_gcc_COMPILER_VERSION}_libmt.so) -IF (USESYSTEMLIBS) - # On standalone, assume that the system installed library was compiled with the used compiler. - SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson.so libjsoncpp.so ) -ENDIF (USESYSTEMLIBS) -FIND_LIBRARY(JSONCPP_LIBRARY - NAMES ${JSONCPP_NAMES} - PATHS /usr/lib /usr/local/lib - ) - -IF (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) - SET(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY}) - SET(JSONCPP_FOUND "YES") -ELSE (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) - SET(JSONCPP_FOUND "NO") -ENDIF (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) - - -IF (JSONCPP_FOUND) - IF (NOT JSONCPP_FIND_QUIETLY) - MESSAGE(STATUS "Found JSONCpp: ${JSONCPP_LIBRARIES}") - ENDIF (NOT JSONCPP_FIND_QUIETLY) -ELSE (JSONCPP_FOUND) - IF (JSONCPP_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find JSONCpp library") - ENDIF (JSONCPP_FIND_REQUIRED) -ENDIF (JSONCPP_FOUND) - -# Deprecated declarations. -SET (NATIVE_JSONCPP_INCLUDE_PATH ${JSONCPP_INCLUDE_DIR} ) -GET_FILENAME_COMPONENT (NATIVE_JSONCPP_LIB_PATH ${JSONCPP_LIBRARY} PATH) - -MARK_AS_ADVANCED( - JSONCPP_LIBRARY - JSONCPP_INCLUDE_DIR - ) diff --git a/indra/cmake/FindNGHTTP2.cmake b/indra/cmake/FindNGHTTP2.cmake index e9c126660b..0ac3c812f9 100644 --- a/indra/cmake/FindNGHTTP2.cmake +++ b/indra/cmake/FindNGHTTP2.cmake @@ -3,39 +3,3 @@ include(FindPkgConfig) pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) -if (FALSE) -FIND_PATH(NGHTTP2_INCLUDE_DIR nghttp2/nghttp2.h) - - -FIND_LIBRARY(NGHTTP2_LIBRARIES - NAMES libnghttp2 - PATHS /usr/lib /usr/local/lib - ) - -IF (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) - SET(NGHTTP2_LIBRARIES ${NGHTTP2_LIBRARY}) - SET(NGHTTP2_FOUND "YES") -ELSE (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) - SET(NGHTTP2_FOUND "NO") -ENDIF (NGHTTP2_LIBRARY AND NGHTTP2_INCLUDE_DIR) - - -IF (NGHTTP2_FOUND) - IF (NOT NGHTTP2_FIND_QUIETLY) - MESSAGE(STATUS "Found JSONCpp: ${NGHTTP2_LIBRARIES}") - ENDIF (NOT NGHTTP2_FIND_QUIETLY) -ELSE (NGHTTP2_FOUND) - IF (NGHTTP2_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find JSONCpp library") - ENDIF (NGHTTP2_FIND_REQUIRED) -ENDIF (NGHTTP2_FOUND) - -# Deprecated declarations. -SET (NATIVE_NGHTTP2_INCLUDE_PATH ${NGHTTP2_INCLUDE_DIR} ) -GET_FILENAME_COMPONENT (NATIVE_NGHTTP2_LIB_PATH ${NGHTTP2_LIBRARY} PATH) - -MARK_AS_ADVANCED( - NGHTTP2_LIBRARY - NGHTTP2_INCLUDE_DIR - ) -endif() \ No newline at end of file diff --git a/indra/cmake/FindOpenJPEG.cmake b/indra/cmake/FindOpenJPEG.cmake index 949384eec4..ce85e1f4bd 100644 --- a/indra/cmake/FindOpenJPEG.cmake +++ b/indra/cmake/FindOpenJPEG.cmake @@ -1,50 +1,7 @@ # -*- cmake -*- -# - Find OpenJPEG -# Find the OpenJPEG includes and library -# This module defines -# OPENJPEG_INCLUDE_DIR, where to find openjpeg.h, etc. -# OPENJPEG_LIBRARIES, the libraries needed to use OpenJPEG. -# OPENJPEG_FOUND, If false, do not try to use OpenJPEG. -# also defined, but not for general use are -# OPENJPEG_LIBRARY, where to find the OpenJPEG library. +include(FindPkgConfig) -FIND_PATH(OPENJPEG_INCLUDE_DIR openjpeg.h -/usr/local/include/openjpeg -/usr/local/include -/usr/include/openjpeg -/usr/include -) - -SET(OPENJPEG_NAMES ${OPENJPEG_NAMES} openjpeg) -FIND_LIBRARY(OPENJPEG_LIBRARY - NAMES ${OPENJPEG_NAMES} - PATHS /usr/lib /usr/local/lib - ) - -IF (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) - SET(OPENJPEG_LIBRARIES ${OPENJPEG_LIBRARY}) - SET(OPENJPEG_FOUND "YES") -ELSE (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) - SET(OPENJPEG_FOUND "NO") -ENDIF (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) +pkg_check_modules( OPENJPEG REQUIRED libopenjpeg1 ) -IF (OPENJPEG_FOUND) - IF (NOT OPENJPEG_FIND_QUIETLY) - MESSAGE(STATUS "Found OpenJPEG: ${OPENJPEG_LIBRARIES}") - ENDIF (NOT OPENJPEG_FIND_QUIETLY) -ELSE (OPENJPEG_FOUND) - IF (OPENJPEG_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find OpenJPEG library") - ENDIF (OPENJPEG_FIND_REQUIRED) -ENDIF (OPENJPEG_FOUND) - -# Deprecated declarations. -SET (NATIVE_OPENJPEG_INCLUDE_PATH ${OPENJPEG_INCLUDE_DIR} ) -GET_FILENAME_COMPONENT (NATIVE_OPENJPEG_LIB_PATH ${OPENJPEG_LIBRARY} PATH) - -MARK_AS_ADVANCED( - OPENJPEG_LIBRARY - OPENJPEG_INCLUDE_DIR - ) diff --git a/indra/cmake/FindURIPARSER.cmake b/indra/cmake/FindURIPARSER.cmake index ab5c8c16ee..213e71e2bf 100644 --- a/indra/cmake/FindURIPARSER.cmake +++ b/indra/cmake/FindURIPARSER.cmake @@ -1,46 +1,5 @@ # -*- cmake -*- -# - Find uriparser -# Find the URIPARSER includes and library -# This module defines -# URIPARSER_INCLUDE_DIRS, where to find uriparser.h, etc. -# URIPARSER_LIBRARIES, the libraries needed to use uriparser. -# URIPARSER_FOUND, If false, do not try to use uriparser. -# -# This FindURIPARSER is about 43 times as fast the one provided with cmake (2.8.x), -# because it doesn't look up the version of uriparser, resulting in a dramatic -# speed up for configure (from 4 minutes 22 seconds to 6 seconds). -# -# Note: Since this file is only used for standalone, the windows -# specific parts were left out. - -FIND_PATH(URIPARSER_INCLUDE_DIR uriparser/Uri.h - NO_SYSTEM_ENVIRONMENT_PATH - ) - -FIND_LIBRARY(URIPARSER_LIBRARY uriparser) - -if (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) - SET(URIPARSER_INCLUDE_DIRS ${URIPARSER_INCLUDE_DIR}) - SET(URIPARSER_LIBRARIES ${URIPARSER_LIBRARY}) - SET(URIPARSER_FOUND "YES") -else (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) - SET(URIPARSER_FOUND "NO") -endif (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) - -if (URIPARSER_FOUND) - if (NOT URIPARSER_FIND_QUIETLY) - message(STATUS "Found URIPARSER: ${URIPARSER_LIBRARIES}") - SET(URIPARSER_FIND_QUIETLY TRUE) - endif (NOT URIPARSER_FIND_QUIETLY) -else (URIPARSER_FOUND) - if (URIPARSER_FIND_REQUIRED) - message(FATAL_ERROR "Could not find URIPARSER library") - endif (URIPARSER_FIND_REQUIRED) -endif (URIPARSER_FOUND) - -mark_as_advanced( - URIPARSER_LIBRARY - URIPARSER_INCLUDE_DIR - ) - +include(FindPkgConfig) +pkg_check_modules( URIPARSER REQUIRED liburiparser ) +set( URIPARSER_INCLUDE_DIR ${URIPARSER_INCLUDE_DIRS} ) \ No newline at end of file diff --git a/indra/cmake/FindZLIB.cmake b/indra/cmake/FindZLIB.cmake index 03a7db9d6f..7701aca285 100644 --- a/indra/cmake/FindZLIB.cmake +++ b/indra/cmake/FindZLIB.cmake @@ -1,46 +1,5 @@ # -*- cmake -*- -# - Find zlib -# Find the ZLIB includes and library -# This module defines -# ZLIB_INCLUDE_DIRS, where to find zlib.h, etc. -# ZLIB_LIBRARIES, the libraries needed to use zlib. -# ZLIB_FOUND, If false, do not try to use zlib. -# -# This FindZLIB is about 43 times as fast the one provided with cmake (2.8.x), -# because it doesn't look up the version of zlib, resulting in a dramatic -# speed up for configure (from 4 minutes 22 seconds to 6 seconds). -# -# Note: Since this file is only used for standalone, the windows -# specific parts were left out. - -FIND_PATH(ZLIB_INCLUDE_DIR zlib.h - NO_SYSTEM_ENVIRONMENT_PATH - ) - -FIND_LIBRARY(ZLIB_LIBRARY z) - -if (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) - SET(ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR}) - SET(ZLIB_LIBRARIES ${ZLIB_LIBRARY}) - SET(ZLIB_FOUND "YES") -else (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) - SET(ZLIB_FOUND "NO") -endif (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) - -if (ZLIB_FOUND) - if (NOT ZLIB_FIND_QUIETLY) - message(STATUS "Found ZLIB: ${ZLIB_LIBRARIES}") - SET(ZLIB_FIND_QUIETLY TRUE) - endif (NOT ZLIB_FIND_QUIETLY) -else (ZLIB_FOUND) - if (ZLIB_FIND_REQUIRED) - message(FATAL_ERROR "Could not find ZLIB library") - endif (ZLIB_FIND_REQUIRED) -endif (ZLIB_FOUND) - -mark_as_advanced( - ZLIB_LIBRARY - ZLIB_INCLUDE_DIR - ) - +include(FindPkgConfig) +pkg_check_modules( ZLIB REQUIRED zlib ) +set( ZLIB_INCLUDE_DIR ${ZLIB_INCLUDE_DIRS} ) diff --git a/indra/cmake/GLOD.cmake b/indra/cmake/GLOD.cmake index a347eb6fee..246e2749ba 100644 --- a/indra/cmake/GLOD.cmake +++ b/indra/cmake/GLOD.cmake @@ -3,7 +3,24 @@ include(Prebuilt) if (NOT USESYSTEMLIBS) use_prebuilt_binary(glod) + set(GLOD_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include) + set(GLOD_LIBRARIES GLOD vds) +else() + + find_library( GLOD_LIBRARY GLOD ) + find_library( VDS_LIBRARY vds ) + find_path( GLOD_INCLUDE_DIR glod/glod.h ) + + if( GLOD_INCLUDE_DIR STREQUAL "GLOD_INCLUDE_DIR-NOTFOUND" ) + message( FATAL_ERROR "Cannot find glod include dir" ) + endif() + if( GLOD_LIBRARY STREQUAL "GLOD_LIBRARY-NOTFOUND" ) + message( FATAL_ERROR "Cannot find library GLOD.a" ) + endif() + if( VDS_LIBRARY STREQUAL "VDS_LIBRARY-NOTFOUND" ) + message( FATAL_ERROR "Cannot find library vds.a" ) + endif() + set(GLOD_LIBRARIES ${GLOD_LIBRARY} ${VDS_LIBRARY} ) + endif (NOT USESYSTEMLIBS) -set(GLOD_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include) -set(GLOD_LIBRARIES GLOD) diff --git a/indra/cmake/GoogleMock.cmake b/indra/cmake/GoogleMock.cmake index 5a00546927..6ea2878d2e 100644 --- a/indra/cmake/GoogleMock.cmake +++ b/indra/cmake/GoogleMock.cmake @@ -2,27 +2,44 @@ include(Prebuilt) include(Linking) -use_prebuilt_binary(googlemock) +if( NOT USESYSTEMLIBS ) + + use_prebuilt_binary(googlemock) -set(GOOGLEMOCK_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include) + set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include) -if (LINUX) - # VWR-24366: gmock is underlinked, it needs gtest. - set(GOOGLEMOCK_LIBRARIES - gmock -Wl,--no-as-needed - gtest -Wl,--as-needed) -elseif(WINDOWS) - set(GOOGLEMOCK_LIBRARIES - gmock) - set(GOOGLEMOCK_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include - ${LIBS_PREBUILT_DIR}/include/gmock - ${LIBS_PREBUILT_DIR}/include/gmock/boost/tr1/tr1) -elseif(DARWIN) - set(GOOGLEMOCK_LIBRARIES - gmock - gtest) -endif(LINUX) + if (LINUX) + # VWR-24366: gmock is underlinked, it needs gtest. + set(GOOGLEMOCK_LIBRARIES + gmock -Wl,--no-as-needed + gtest -Wl,--as-needed) + elseif(WINDOWS) + set(GOOGLEMOCK_LIBRARIES + gmock) + set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include + ${LIBS_PREBUILT_DIR}/include/gmock + ${LIBS_PREBUILT_DIR}/include/gmock/boost/tr1/tr1) + elseif(DARWIN) + set(GOOGLEMOCK_LIBRARIES + gmock + gtest) + endif(LINUX) +else() + + find_library( GOOGLETEST_LIBRARY gtest ) + find_library( GOOGLEMOCK_LIBRARY gmock ) + if( GOOGLETEST_LIBRARY STREQUAL "GOOGLETEST_LIBRARY-NOTFOUND" ) + message( FATAL_ERROR "Cannot find gtest library" ) + endif() + + if( GOOGLEMOCK_LIBRARY STREQUAL "GOOGLEMOCK_LIBRARY-NOTFOUND" ) + message( FATAL_ERROR "Cannot find gmock library" ) + endif() + + set(GOOGLEMOCK_LIBRARIES ${GOOGLEMOCK_LIBRARY} ${GOOGLETEST_LIBRARY} ) + +endif() diff --git a/indra/cmake/LLPhysicsExtensions.cmake b/indra/cmake/LLPhysicsExtensions.cmake index e18722c752..c8e81fe2ae 100644 --- a/indra/cmake/LLPhysicsExtensions.cmake +++ b/indra/cmake/LLPhysicsExtensions.cmake @@ -23,12 +23,13 @@ if (HAVOK) elseif (HAVOK_TPV) use_prebuilt_binary(llphysicsextensions_tpv) - set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensions_tpv) + #set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensions_tpv) + set(LLPHYSICSEXTENSIONS_LIBRARIES libhacd.a libnd_hacdConvexDecomposition.a libnd_Pathing.a ) else (HAVOK) use_prebuilt_binary(llphysicsextensions_stub) - #set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/stub) - set(LLPHYSICSEXTENSIONS_LIBRARIES libhacd.a libnd_hacdConvexDecomposition.a libnd_Pathing.a ) + set(LLPHYSICSEXTENSIONS_SRC_DIR ${LIBS_PREBUILT_DIR}/llphysicsextensions/stub) + set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensionsstub) endif (HAVOK) From b018712ebc6df5fc9f4f0dca3b4853d3b35107a0 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 11 Apr 2018 13:53:40 +0200 Subject: [PATCH 028/125] Disable packages-info.txt dependency when building standalone (for now?) --- indra/newview/CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index b1dbe7c9d5..37ef692052 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -4,7 +4,11 @@ project(viewer) include(00-Common) include(Boost) -include(BuildPackagesInfo) + +if( NOT USESYSTEMLIBS ) + include(BuildPackagesInfo) +endif() + include(BuildVersion) include(CMakeCopyIfDifferent) include(DBusGlib) @@ -1625,9 +1629,12 @@ set(viewer_APPSETTINGS_FILES ${CMAKE_SOURCE_DIR}/../etc/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg ${AUTOBUILD_INSTALL_DIR}/ca-bundle.crt - packages-info.txt ) +if( NOT USESYSTEMLIBS ) + set( viewer_APPSETTINGS_FILES ${viewer_APPSETTINGS_FILE} packages-info.txt ) +endif() + source_group("App Settings" FILES ${viewer_APPSETTINGS_FILES}) set_source_files_properties(${viewer_APPSETTINGS_FILES} From f6b560cf914f24b90da8078ee6dae12bc2bf2d7c Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 11 Apr 2018 17:23:31 +0200 Subject: [PATCH 029/125] Correct spelling of library variable. --- indra/cmake/LLPrimitive.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index 3888208fa6..777c9bb8cc 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -72,7 +72,7 @@ else() ${COLLADADOM_LIBRARY} ${MINIZIP_LIBRARIES} ${LIBXML2_LIBRARIES} - ${LIBPRCECCPP_LIBRARIES} + ${LIBPRCECPP_LIBRARIES} ) endif() From 487f2e0e50fb2e12686ca32c3335f0613832c60d Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 16 Jan 2019 09:49:02 +0100 Subject: [PATCH 030/125] Correct include path to find system installed breakpad version. --- indra/cmake/FindGoogleBreakpad.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/FindGoogleBreakpad.cmake b/indra/cmake/FindGoogleBreakpad.cmake index 4cda561864..78890ca5c2 100644 --- a/indra/cmake/FindGoogleBreakpad.cmake +++ b/indra/cmake/FindGoogleBreakpad.cmake @@ -9,7 +9,7 @@ # also defined, but not for general use are # BREAKPAD_EXCEPTION_HANDLER_LIBRARY, where to find the Google BreakPad library. -FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR google_breakpad/client/linux/handler/exception_handler.h) +FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR breakpad/client/linux/handler/exception_handler.h) SET(BREAKPAD_EXCEPTION_HANDLER_NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} breakpad_client) FIND_LIBRARY(BREAKPAD_EXCEPTION_HANDLER_LIBRARY From 88c887e879cf4772fb47924dc1822457f2c33525 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:34:49 +0100 Subject: [PATCH 031/125] Use phoenix::bind rather than only bind, as the latter will clash with newer (GCC) compilers when std::bind gets pulled into the translation unit. --- indra/llmath/llcalcparser.h | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/indra/llmath/llcalcparser.h b/indra/llmath/llcalcparser.h index e2388d6702..dff5bf3af3 100644 --- a/indra/llmath/llcalcparser.h +++ b/indra/llmath/llcalcparser.h @@ -81,28 +81,28 @@ struct LLCalcParser : grammar ; unary_func = - ((str_p("SIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sin)(self,arg1)]) | - (str_p("COS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_cos)(self,arg1)]) | - (str_p("TAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_tan)(self,arg1)]) | - (str_p("ASIN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_asin)(self,arg1)]) | - (str_p("ACOS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_acos)(self,arg1)]) | - (str_p("ATAN") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_atan)(self,arg1)]) | - (str_p("SQRT") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_sqrt)(self,arg1)]) | - (str_p("LOG") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_log)(self,arg1)]) | - (str_p("EXP") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_exp)(self,arg1)]) | - (str_p("ABS") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_fabs)(self,arg1)]) | - (str_p("FLR") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_floor)(self,arg1)]) | - (str_p("CEIL") >> '(' >> expression[unary_func.value = bind(&LLCalcParser::_ceil)(self,arg1)]) + ((str_p("SIN") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_sin)(self,arg1)]) | + (str_p("COS") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_cos)(self,arg1)]) | + (str_p("TAN") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_tan)(self,arg1)]) | + (str_p("ASIN") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_asin)(self,arg1)]) | + (str_p("ACOS") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_acos)(self,arg1)]) | + (str_p("ATAN") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_atan)(self,arg1)]) | + (str_p("SQRT") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_sqrt)(self,arg1)]) | + (str_p("LOG") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_log)(self,arg1)]) | + (str_p("EXP") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_exp)(self,arg1)]) | + (str_p("ABS") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_fabs)(self,arg1)]) | + (str_p("FLR") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_floor)(self,arg1)]) | + (str_p("CEIL") >> '(' >> expression[unary_func.value = phoenix::bind(&LLCalcParser::_ceil)(self,arg1)]) ) >> assert_syntax(ch_p(')')) ; binary_func = ((str_p("ATAN2") >> '(' >> expression[binary_func.value = arg1] >> ',' >> - expression[binary_func.value = bind(&LLCalcParser::_atan2)(self, binary_func.value, arg1)]) | + expression[binary_func.value = phoenix::bind(&LLCalcParser::_atan2)(self, binary_func.value, arg1)]) | (str_p("MIN") >> '(' >> expression[binary_func.value = arg1] >> ',' >> - expression[binary_func.value = bind(&LLCalcParser::_min)(self, binary_func.value, arg1)]) | + expression[binary_func.value = phoenix::bind(&LLCalcParser::_min)(self, binary_func.value, arg1)]) | (str_p("MAX") >> '(' >> expression[binary_func.value = arg1] >> ',' >> - expression[binary_func.value = bind(&LLCalcParser::_max)(self, binary_func.value, arg1)]) + expression[binary_func.value = phoenix::bind(&LLCalcParser::_max)(self, binary_func.value, arg1)]) ) >> assert_syntax(ch_p(')')) ; @@ -118,10 +118,10 @@ struct LLCalcParser : grammar // Lookup throws an Unknown Symbol error if it is unknown, while this works fine, // would be "neater" to handle symbol lookup from here with an assertive parser. // constants_p[factor.value = arg1]| - identifier[factor.value = bind(&LLCalcParser::lookup)(self, arg1, arg2)] + identifier[factor.value = phoenix::bind(&LLCalcParser::lookup)(self, arg1, arg2)] ) >> // Detect and throw math errors. - assert_domain(eps_p(bind(&LLCalcParser::checkNaN)(self, factor.value))) + assert_domain(eps_p(phoenix::bind(&LLCalcParser::checkNaN)(self, factor.value))) ; unary_expr = @@ -131,14 +131,14 @@ struct LLCalcParser : grammar power = unary_expr[power.value = arg1] >> - *('^' >> assert_syntax(unary_expr[power.value = bind(&powf)(power.value, arg1)])) + *('^' >> assert_syntax(unary_expr[power.value = phoenix::bind(&powf)(power.value, arg1)])) ; term = power[term.value = arg1] >> *(('*' >> assert_syntax(power[term.value *= arg1])) | ('/' >> assert_syntax(power[term.value /= arg1])) | - ('%' >> assert_syntax(power[term.value = bind(&fmodf)(term.value, arg1)])) + ('%' >> assert_syntax(power[term.value = phoenix::bind(&fmodf)(term.value, arg1)])) ) ; From 1a1e13c5fec93e612e9949e2f01f314e7bd17f0b Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:48:36 +0100 Subject: [PATCH 032/125] LLFilePicker::getSaveFile did not match the declaration in llfilepicker.h --- indra/newview/llfilepicker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 0f22b6200f..c87637bac3 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -1204,7 +1204,7 @@ static std::string add_save_texture_filter_to_gtkchooser(GtkWindow *picker) return caption; } -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) { BOOL rtn = FALSE; From 43d21bdff28a24e0339c456a942dc835243481ba Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:50:06 +0100 Subject: [PATCH 033/125] FindOpenJPEG.cmake did not set the correct cmake variable for the include directory --- indra/cmake/FindOpenJPEG.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/FindOpenJPEG.cmake b/indra/cmake/FindOpenJPEG.cmake index ce85e1f4bd..17ccf1e13d 100644 --- a/indra/cmake/FindOpenJPEG.cmake +++ b/indra/cmake/FindOpenJPEG.cmake @@ -4,4 +4,4 @@ include(FindPkgConfig) pkg_check_modules( OPENJPEG REQUIRED libopenjpeg1 ) - +set(OPENJPEG_INCLUDE_DIR ${OPENJPEG_INCLUDE_DIRS}) From 541af2edfebd6e0066f2837ffb2147b15100c04c Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:51:09 +0100 Subject: [PATCH 034/125] Do not try to run pkg-config for pangox, it's not needed/will fail for (newer?) systems. --- indra/cmake/UI.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 77fd505df3..247bf52709 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -18,7 +18,7 @@ if (USESYSTEMLIBS) libpng pango pangoft2 - pangox + #pangox pangoxft sdl ) From 577743fe981072becac4a09f46966e242475419e Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:54:26 +0100 Subject: [PATCH 035/125] For a system wide colladom installation: Use include paths make install will create (.../include/colladadom NOT ../include/collada) --- indra/cmake/LLPrimitive.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index 777c9bb8cc..1fe2cc6a9c 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -59,13 +59,13 @@ else() pkg_check_modules( LIBPCRECPP REQUIRED libpcrecpp ) find_library( COLLADADOM_LIBRARY collada14dom ) - find_path( COLLADADOM_INCLUDE_DIR collada/dae.h ) + find_path( COLLADADOM_INCLUDE_DIR colladadom/dae.h ) if( COLLADADOM_INCLUDE_DIR STREQUAL "COLLADADOM_INCLUDE_DIR-NOTFOUND" ) message( FATAL_ERROR "Cannot find colladadom include dir" ) endif() - set( COLLADADOM_INCLUDE_DIRS ${COLLADADOM_INCLUDE_DIR}/collada ${COLLADADOM_INCLUDE_DIR}/collada/1.4 ) + set( COLLADADOM_INCLUDE_DIRS ${COLLADADOM_INCLUDE_DIR}/colladadom ${COLLADADOM_INCLUDE_DIR}/colladadom/1.4 ) set(LLPRIMITIVE_LIBRARIES llprimitive From 2cfe9bc9e5bf170972bab69dea000db6e7dfae64 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 07:56:59 +0100 Subject: [PATCH 036/125] Disable gstreamer010 --- indra/media_plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 75edd58a44..48560f7d97 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -3,7 +3,7 @@ add_subdirectory(base) if (LINUX) - add_subdirectory(gstreamer010) + #add_subdirectory(gstreamer010) #add_subdirectory(libvlc) add_subdirectory(example) endif (LINUX) From 6d31a84fffac491711d100a51e48c5f282dadf57 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 15:03:00 +0100 Subject: [PATCH 037/125] Remove gstreamer plugin from viewer dependencies. --- indra/newview/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 8abd30d362..527d7f760f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2003,7 +2003,7 @@ if (LINUX) ${VIEWER_BINARY_NAME} linux-crash-logger SLPlugin - media_plugin_gstreamer010 + #media_plugin_gstreamer010 #media_plugin_libvlc llcommon ) From ede5e0e858ae751927d862d0eb535e83b4925358 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 17 Jan 2019 15:03:17 +0100 Subject: [PATCH 038/125] Add X11 to required UI libraries. --- indra/cmake/UI.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 247bf52709..bac796278e 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -31,6 +31,7 @@ if (USESYSTEMLIBS) list(APPEND UI_LIBRARIES ${${pkg}_LIBRARIES}) add_definitions(${${pkg}_CFLAGS_OTHERS}) endforeach(pkg) + list(APPEND UI_LIBRARIES X11) else (USESYSTEMLIBS) if (LINUX) use_prebuilt_binary(gtk-atk-pango-glib) From 464c8e25379f19b08ab52d77887bb55849a17721 Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 21 Jan 2019 14:18:22 +0100 Subject: [PATCH 039/125] Add media plugin for gstreamer 1.0 --- indra/cmake/GStreamer10Plugin.cmake | 31 + indra/media_plugins/CMakeLists.txt | 1 + .../media_plugins/gstreamer10/CMakeLists.txt | 77 ++ .../gstreamer10/llmediaimplgstreamer.h | 53 + .../gstreamer10/llmediaimplgstreamer_syms.cpp | 197 ++++ .../gstreamer10/llmediaimplgstreamer_syms.h | 68 ++ .../llmediaimplgstreamer_syms_raw.inc | 68 ++ .../gstreamer10/media_plugin_gstreamer10.cpp | 980 ++++++++++++++++++ 8 files changed, 1475 insertions(+) create mode 100644 indra/cmake/GStreamer10Plugin.cmake create mode 100644 indra/media_plugins/gstreamer10/CMakeLists.txt create mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer.h create mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp create mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h create mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc create mode 100644 indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp diff --git a/indra/cmake/GStreamer10Plugin.cmake b/indra/cmake/GStreamer10Plugin.cmake new file mode 100644 index 0000000000..4f9d255436 --- /dev/null +++ b/indra/cmake/GStreamer10Plugin.cmake @@ -0,0 +1,31 @@ +# -*- cmake -*- +include(Prebuilt) + +if (USESYSTEMLIBS) + include(FindPkgConfig) + + pkg_check_modules(GSTREAMER10 REQUIRED gstreamer-1.0) + pkg_check_modules(GSTREAMER10_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0) +elseif (LINUX OR WINDOWS) + use_prebuilt_binary(gstreamer10) + use_prebuilt_binary(libxml2) + set(GSTREAMER10_FOUND ON FORCE BOOL) + set(GSTREAMER10_PLUGINS_BASE_FOUND ON FORCE BOOL) + set(GSTREAMER10_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0 + ${LIBS_PREBUILT_DIR}/include/glib-2.0 + ${LIBS_PREBUILT_DIR}/include/libxml2 + ) + # We don't need to explicitly link against gstreamer itself, because + # LLMediaImplGStreamer probes for the system's copy at runtime. + set(GSTREAMER10_LIBRARIES) +endif (USESYSTEMLIBS) + +if (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) + set(GSTREAMER10 ON CACHE BOOL "Build with GStreamer-1.0 streaming media support.") +endif (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) + +if (GSTREAMER10) + add_definitions(-DLL_GSTREAMER10_ENABLED=1) +endif (GSTREAMER10) + diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 48560f7d97..803fee551d 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -6,6 +6,7 @@ if (LINUX) #add_subdirectory(gstreamer010) #add_subdirectory(libvlc) add_subdirectory(example) + add_subdirectory(gstreamer10) endif (LINUX) if (DARWIN) diff --git a/indra/media_plugins/gstreamer10/CMakeLists.txt b/indra/media_plugins/gstreamer10/CMakeLists.txt new file mode 100644 index 0000000000..3a98df5da1 --- /dev/null +++ b/indra/media_plugins/gstreamer10/CMakeLists.txt @@ -0,0 +1,77 @@ +# -*- cmake -*- + +project(media_plugin_gstreamer10) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLPlugin) +include(LLMath) +include(LLRender) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(MediaPluginBase) +include(OpenGL) + +include(GStreamer10Plugin) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} + ${GSTREAMER10_INCLUDE_DIRS} + ${GSTREAMER10_PLUGINS_BASE_INCLUDE_DIRS} +) +include_directories(SYSTEM + ${LLCOMMON_SYSTEM_INCLUDE_DIRS} + ) + +### media_plugin_gstreamer10 + +if(NOT WORD_SIZE EQUAL 32) + if(NOT WINDOWS) # not windows therefore gcc LINUX and DARWIN + add_definitions(-fPIC) + endif() +endif(NOT WORD_SIZE EQUAL 32) + +set(media_plugin_gstreamer10_SOURCE_FILES + media_plugin_gstreamer10.cpp + llmediaimplgstreamer_syms.cpp + ) + +set(media_plugin_gstreamer10_HEADER_FILES + llmediaimplgstreamer_syms.h + llmediaimplgstreamertriviallogging.h + ) + +add_library(media_plugin_gstreamer10 + SHARED + ${media_plugin_gstreamer10_SOURCE_FILES} +) + +target_link_libraries(media_plugin_gstreamer10 + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} + ${GSTREAMER10_LIBRARIES} +) + +#add_dependencies(media_plugin_gstreamer10 +# ${LLPLUGIN_LIBRARIES} +# ${MEDIA_PLUGIN_BASE_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) + +if (WINDOWS) + set_target_properties( + media_plugin_gstreamer10 + PROPERTIES + LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /NODEFAULTLIB:LIBCMT" + ) +endif (WINDOWS) diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h new file mode 100644 index 0000000000..6bc272c009 --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h @@ -0,0 +1,53 @@ +/** + * @file llmediaimplgstreamer.h + * @author Tofu Linden + * @brief implementation that supports media playback via GStreamer. + * + * @cond + * $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$ + * @endcond + */ + +// header guard +#ifndef llmediaimplgstreamer_h +#define llmediaimplgstreamer_h + +#if LL_GSTREAMER010_ENABLED + +extern "C" { +#include +#include + +#include "apr_pools.h" +#include "apr_dso.h" +} + + +extern "C" { +gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, + GstMessage *message, + gpointer data); +} + +#endif // LL_GSTREAMER010_ENABLED + +#endif // llmediaimplgstreamer_h diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp new file mode 100644 index 0000000000..e5e5c1c9a3 --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp @@ -0,0 +1,197 @@ +/** + * @file llmediaimplgstreamer_syms.cpp + * @brief dynamic GStreamer symbol-grabbing code + * + * @cond + * $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$ + * @endcond + */ + +#include +#include +#include + +#ifdef LL_WINDOWS +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0502 +#include +#endif + +#include "linden_common.h" + +extern "C" { +#include +#include +} + +#include "apr_pools.h" +#include "apr_dso.h" + +#ifdef LL_WINDOWS + +#ifndef _M_AMD64 +#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86" +#define GSTREAMER_DIR_SUFFIX "1.0\\x86\\bin\\" +#else +#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86_64" +#define GSTREAMER_DIR_SUFFIX "1.0\\x86_64\\bin\\" +#endif + +bool openRegKey( HKEY &aKey ) +{ + // Try native (32 bit view/64 bit view) of registry first. + if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE, &aKey ) ) + return true; + + // If native view fails, use 32 bit view or registry. + if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &aKey ) ) + return true; + + return false; +} + +std::string getGStreamerDir() +{ + std::string ret; + HKEY hKey; + + if( openRegKey( hKey ) ) + { + DWORD dwLen(0); + ::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, nullptr, &dwLen ); + + if( dwLen > 0 ) + { + std::vector< char > vctBuffer; + vctBuffer.resize( dwLen ); + ::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, reinterpret_cast< LPBYTE>(&vctBuffer[ 0 ]), &dwLen ); + ret = &vctBuffer[0]; + + if( ret[ dwLen-1 ] != '\\' ) + ret += "\\"; + ret += GSTREAMER_DIR_SUFFIX; + + SetDllDirectoryA( ret.c_str() ); + } + ::RegCloseKey( hKey ); + } + return ret; +} +#else +std::string getGStreamerDir() { return ""; } +#endif + +#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) RTN (*ll##GSTSYM)(__VA_ARGS__) = NULL; +#include "llmediaimplgstreamer_syms_raw.inc" +#undef LL_GST_SYM + +struct Symloader +{ + bool mRequired; + char const *mName; + apr_dso_handle_sym_t *mPPFunc; +}; + +#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) { REQ, #GSTSYM , (apr_dso_handle_sym_t*)&ll##GSTSYM}, +Symloader sSyms[] = { +#include "llmediaimplgstreamer_syms_raw.inc" +{ false, 0, 0 } }; +#undef LL_GST_SYM + +// a couple of stubs for disgusting reasons +GstDebugCategory* +ll_gst_debug_category_new(gchar *name, guint color, gchar *description) +{ + static GstDebugCategory dummy; + return &dummy; +} +void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname) +{ +} + +static bool sSymsGrabbed = false; +static apr_pool_t *sSymGSTDSOMemoryPool = NULL; + +std::vector< apr_dso_handle_t* > sLoadedLibraries; + +bool grab_gst_syms( std::vector< std::string > const &aDSONames ) +{ + if (sSymsGrabbed) + return true; + + //attempt to load the shared libraries + apr_pool_create(&sSymGSTDSOMemoryPool, NULL); + + for( std::vector< std::string >::const_iterator itr = aDSONames.begin(); itr != aDSONames.end(); ++itr ) + { + apr_dso_handle_t *pDSO(NULL); + std::string strDSO = getGStreamerDir() + *itr; + if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymGSTDSOMemoryPool )) + sLoadedLibraries.push_back( pDSO ); + + for( int i = 0; sSyms[ i ].mName; ++i ) + { + if( !*sSyms[ i ].mPPFunc ) + { + apr_dso_sym( sSyms[ i ].mPPFunc, pDSO, sSyms[ i ].mName ); + } + } + } + + std::stringstream strm; + bool sym_error = false; + for( int i = 0; sSyms[ i ].mName; ++i ) + { + if( sSyms[ i ].mRequired && ! *sSyms[ i ].mPPFunc ) + { + sym_error = true; + strm << sSyms[ i ].mName << std::endl; + } + } + + sSymsGrabbed = !sym_error; + return sSymsGrabbed; +} + + +void ungrab_gst_syms() +{ + // should be safe to call regardless of whether we've + // actually grabbed syms. + + for( std::vector< apr_dso_handle_t* >::iterator itr = sLoadedLibraries.begin(); itr != sLoadedLibraries.end(); ++itr ) + apr_dso_unload( *itr ); + + sLoadedLibraries.clear(); + + if ( sSymGSTDSOMemoryPool ) + { + apr_pool_destroy(sSymGSTDSOMemoryPool); + sSymGSTDSOMemoryPool = NULL; + } + + for( int i = 0; sSyms[ i ].mName; ++i ) + *sSyms[ i ].mPPFunc = NULL; + + sSymsGrabbed = false; +} + diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h new file mode 100644 index 0000000000..0874644ee6 --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h @@ -0,0 +1,68 @@ +/** + * @file llmediaimplgstreamer_syms.h + * @brief dynamic GStreamer symbol-grabbing code + * + * @cond + * $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$ + * @endcond + */ + +#include "linden_common.h" +#include +extern "C" { +#include +} + +bool grab_gst_syms( std::vector< std::string > const&); +void ungrab_gst_syms(); + +#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) extern RTN (*ll##GSTSYM)(__VA_ARGS__); +#include "llmediaimplgstreamer_syms_raw.inc" +#undef LL_GST_SYM + +// regrettable hacks to give us better runtime compatibility with older systems +#define llg_return_if_fail(COND) do{if (!(COND)) return;}while(0) +#define llg_return_val_if_fail(COND,V) do{if (!(COND)) return V;}while(0) + +// regrettable hacks because GStreamer was not designed for runtime loading +#undef GST_TYPE_MESSAGE +#define GST_TYPE_MESSAGE (llgst_message_get_type()) +#undef GST_TYPE_OBJECT +#define GST_TYPE_OBJECT (llgst_object_get_type()) +#undef GST_TYPE_PIPELINE +#define GST_TYPE_PIPELINE (llgst_pipeline_get_type()) +#undef GST_TYPE_ELEMENT +#define GST_TYPE_ELEMENT (llgst_element_get_type()) +#undef GST_TYPE_VIDEO_SINK +#define GST_TYPE_VIDEO_SINK (llgst_video_sink_get_type()) +// more regrettable hacks to stub-out these .h-exposed GStreamer internals +void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname); +#undef _gst_debug_register_funcptr +#define _gst_debug_register_funcptr ll_gst_debug_register_funcptr +GstDebugCategory* ll_gst_debug_category_new(gchar *name, guint color, gchar *description); +#undef _gst_debug_category_new +#define _gst_debug_category_new ll_gst_debug_category_new +#undef __gst_debug_enabled +#define __gst_debug_enabled (0) + +// more hacks +#define LLGST_MESSAGE_TYPE_NAME(M) (llgst_message_type_get_name(GST_MESSAGE_TYPE(M))) diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc new file mode 100644 index 0000000000..da1aa8ec1d --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc @@ -0,0 +1,68 @@ +LL_GST_SYM(true, gst_buffer_new, GstBuffer*, void) +LL_GST_SYM(true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*) +LL_GST_SYM(true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err) +LL_GST_SYM(true, gst_message_get_type, GType, void) +LL_GST_SYM(true, gst_message_type_get_name, const gchar*, GstMessageType type) +LL_GST_SYM(true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GST_SYM(true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GST_SYM(true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending) +LL_GST_SYM(true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state) +LL_GST_SYM(true, gst_object_unref, void, gpointer object) +LL_GST_SYM(true, gst_object_get_type, GType, void) +LL_GST_SYM(true, gst_pipeline_get_type, GType, void) +LL_GST_SYM(true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline) +LL_GST_SYM(true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data) +LL_GST_SYM(true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name) +LL_GST_SYM(true, gst_element_get_type, GType, void) +LL_GST_SYM(true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template) +LL_GST_SYM(true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp) +LL_GST_SYM(true, gst_caps_from_string, GstCaps *, const gchar *string) +LL_GST_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index) +LL_GST_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type) +LL_GST_SYM(true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value) +LL_GST_SYM(true, gst_structure_get_value, G_CONST_RETURN GValue *, const GstStructure *structure, const gchar *fieldname) +LL_GST_SYM(true, gst_value_get_fraction_numerator, gint, const GValue *value) +LL_GST_SYM(true, gst_value_get_fraction_denominator, gint, const GValue *value) +LL_GST_SYM(true, gst_structure_get_name, G_CONST_RETURN gchar *, const GstStructure *structure) +LL_GST_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64) + +LL_GST_SYM(false, gst_registry_fork_set_enabled, void, gboolean enabled) +LL_GST_SYM(false, gst_segtrap_set_enabled, void, gboolean enabled) +LL_GST_SYM(false, gst_message_parse_buffering, void, GstMessage *message, gint *percent) +LL_GST_SYM(false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GST_SYM(false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur) +LL_GST_SYM(false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano) + +LL_GST_SYM( true, gst_message_parse_tag, void, GstMessage *, GstTagList **) +LL_GST_SYM( true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer) +LL_GST_SYM( true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *) +LL_GST_SYM( true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint) + +LL_GST_SYM( true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) + +LL_GST_SYM( true, gst_sample_get_caps, GstCaps*, GstSample* ) +LL_GST_SYM( true, gst_sample_get_buffer, GstBuffer*, GstSample* ) +LL_GST_SYM( true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags ) +LL_GST_SYM( true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* ) + +LL_GST_SYM( true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* ) +LL_GST_SYM( true, gst_app_sink_pull_sample, GstSample*, GstAppSink* ) + +LL_GST_SYM( true, g_free, void, gpointer ) +LL_GST_SYM( true, g_error_free, void, GError* ) + +LL_GST_SYM( true, g_main_context_pending, gboolean, GMainContext* ) +LL_GST_SYM( true, g_main_loop_get_context, GMainContext*, GMainLoop* ) +LL_GST_SYM( true, g_main_context_iteration, gboolean, GMainContext*, gboolean ) +LL_GST_SYM( true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean ) +LL_GST_SYM( true, g_main_loop_quit, void, GMainLoop* ) +LL_GST_SYM( true, gst_mini_object_unref, void, GstMiniObject* ) +LL_GST_SYM( true, g_object_set, void, gpointer, gchar const*, ... ) +LL_GST_SYM( true, g_source_remove, gboolean, guint ) +LL_GST_SYM( true, g_value_get_string, gchar const*, GValue const* ) + + +LL_GST_SYM( true, gst_debug_set_active, void, gboolean ) +LL_GST_SYM( true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify ) +LL_GST_SYM( true, gst_debug_set_default_threshold, void, GstDebugLevel ) +LL_GST_SYM( true, gst_debug_message_get , gchar const*, GstDebugMessage * ) \ No newline at end of file diff --git a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp new file mode 100644 index 0000000000..5c931ea8f0 --- /dev/null +++ b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp @@ -0,0 +1,980 @@ +/** + * @file media_plugin_gstreamer10.cpp + * @brief GStreamer-1.0 plugin for LLMedia API plugin system + * + * @cond + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2016, Linden Research, Inc. / Nicky Dasmijn + * + * 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$ + * @endcond + */ + +#define FLIP_Y + +#include "linden_common.h" + +#include "llgl.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" +#include "media_plugin_base.h" + +#define G_DISABLE_CAST_CHECKS +extern "C" { +#include +#include + +} + +#include "llmediaimplgstreamer.h" +#include "llmediaimplgstreamer_syms.h" + +static inline void llgst_caps_unref( GstCaps * caps ) +{ + llgst_mini_object_unref( GST_MINI_OBJECT_CAST( caps ) ); +} + +static inline void llgst_sample_unref( GstSample *aSample ) +{ + llgst_mini_object_unref( GST_MINI_OBJECT_CAST( aSample ) ); +} + +////////////////////////////////////////////////////////////////////////////// +// +class MediaPluginGStreamer10 : public MediaPluginBase +{ +public: + MediaPluginGStreamer10(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + ~MediaPluginGStreamer10(); + + /* virtual */ void receiveMessage(const char *message_string); + + static bool startup(); + static bool closedown(); + + gboolean processGSTEvents(GstBus *bus, GstMessage *message); + +private: + std::string getVersion(); + bool navigateTo( const std::string urlIn ); + bool seek( double time_sec ); + bool setVolume( float volume ); + + // misc + bool pause(); + bool stop(); + bool play(double rate); + bool getTimePos(double &sec_out); + + double MIN_LOOP_SEC = 1.0F; + U32 INTERNAL_TEXTURE_SIZE = 1024; + + bool mIsLooping; + + enum ECommand { + COMMAND_NONE, + COMMAND_STOP, + COMMAND_PLAY, + COMMAND_FAST_FORWARD, + COMMAND_FAST_REWIND, + COMMAND_PAUSE, + COMMAND_SEEK, + }; + ECommand mCommand; + +private: + bool unload(); + bool load(); + + bool update(int milliseconds); + void mouseDown( int x, int y ); + void mouseUp( int x, int y ); + void mouseMove( int x, int y ); + + static bool mDoneInit; + + guint mBusWatchID; + + float mVolume; + + int mDepth; + + // padded texture size we need to write into + int mTextureWidth; + int mTextureHeight; + + bool mSeekWanted; + double mSeekDestination; + + // Very GStreamer-specific + GMainLoop *mPump; // event pump for this media + GstElement *mPlaybin; + GstAppSink *mAppSink; +}; + +//static +bool MediaPluginGStreamer10::mDoneInit = false; + +MediaPluginGStreamer10::MediaPluginGStreamer10( LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data ) + : MediaPluginBase(host_send_func, host_user_data) + , mBusWatchID ( 0 ) + , mSeekWanted(false) + , mSeekDestination(0.0) + , mPump ( NULL ) + , mPlaybin ( NULL ) + , mAppSink ( NULL ) + , mCommand ( COMMAND_NONE ) +{ +} + +gboolean MediaPluginGStreamer10::processGSTEvents(GstBus *bus, GstMessage *message) +{ + if (!message) + return TRUE; // shield against GStreamer bug + + switch (GST_MESSAGE_TYPE (message)) + { + case GST_MESSAGE_BUFFERING: + { + // NEEDS GST 0.10.11+ + if (llgst_message_parse_buffering) + { + gint percent = 0; + llgst_message_parse_buffering(message, &percent); + } + break; + } + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state; + GstState new_state; + GstState pending_state; + llgst_message_parse_state_changed(message, + &old_state, + &new_state, + &pending_state); + + switch (new_state) + { + case GST_STATE_VOID_PENDING: + break; + case GST_STATE_NULL: + break; + case GST_STATE_READY: + setStatus(STATUS_LOADED); + break; + case GST_STATE_PAUSED: + setStatus(STATUS_PAUSED); + break; + case GST_STATE_PLAYING: + setStatus(STATUS_PLAYING); + break; + } + break; + } + case GST_MESSAGE_ERROR: + { + GError *err = NULL; + gchar *debug = NULL; + + llgst_message_parse_error (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + + mCommand = COMMAND_STOP; + + setStatus(STATUS_ERROR); + + break; + } + case GST_MESSAGE_INFO: + { + if (llgst_message_parse_info) + { + GError *err = NULL; + gchar *debug = NULL; + + llgst_message_parse_info (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + } + break; + } + case GST_MESSAGE_WARNING: + { + GError *err = NULL; + gchar *debug = NULL; + + llgst_message_parse_warning (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + + break; + } + case GST_MESSAGE_EOS: + /* end-of-stream */ + if (mIsLooping) + { + double eos_pos_sec = 0.0F; + bool got_eos_position = getTimePos(eos_pos_sec); + + if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) + { + // if we know that the movie is really short, don't + // loop it else it can easily become a time-hog + // because of GStreamer spin-up overhead + // inject a COMMAND_PAUSE + mCommand = COMMAND_PAUSE; + } + else + { + stop(); + play(1.0); + } + } + else // not a looping media + { + // inject a COMMAND_STOP + mCommand = COMMAND_STOP; + } + break; + default: + /* unhandled message */ + break; + } + + /* we want to be notified again the next time there is a message + * on the bus, so return true (false means we want to stop watching + * for messages on the bus and our callback should not be called again) + */ + return TRUE; +} + +extern "C" { + gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, + GstMessage *message, + gpointer data) + { + MediaPluginGStreamer10 *impl = (MediaPluginGStreamer10*)data; + return impl->processGSTEvents(bus, message); + } +} // extern "C" + + + +bool MediaPluginGStreamer10::navigateTo ( const std::string urlIn ) +{ + if (!mDoneInit) + return false; // error + + setStatus(STATUS_LOADING); + + mSeekWanted = false; + + if (NULL == mPump || NULL == mPlaybin) + { + setStatus(STATUS_ERROR); + return false; // error + } + + llg_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL); + + // navigateTo implicitly plays, too. + play(1.0); + + return true; +} + + +class GstSampleUnref +{ + GstSample *mT; +public: + GstSampleUnref( GstSample *aT ) + : mT( aT ) + { llassert_always( mT ); } + + ~GstSampleUnref( ) + { llgst_sample_unref( mT ); } +}; + +bool MediaPluginGStreamer10::update(int milliseconds) +{ + if (!mDoneInit) + return false; // error + + // DEBUGMSG("updating media..."); + + // sanity check + if (NULL == mPump || NULL == mPlaybin) + { + return false; + } + + // see if there's an outstanding seek wanted + if (mSeekWanted && + // bleh, GST has to be happy that the movie is really truly playing + // or it may quietly ignore the seek (with rtsp:// at least). + (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) + { + seek(mSeekDestination); + mSeekWanted = false; + } + + // *TODO: time-limit - but there isn't a lot we can do here, most + // time is spent in gstreamer's own opaque worker-threads. maybe + // we can do something sneaky like only unlock the video object + // for 'milliseconds' and otherwise hold the lock. + while (llg_main_context_pending(llg_main_loop_get_context(mPump))) + { + llg_main_context_iteration(llg_main_loop_get_context(mPump), FALSE); + } + + // check for availability of a new frame + + if( !mAppSink ) + return true; + + if( GST_STATE(mPlaybin) != GST_STATE_PLAYING) // Do not try to pull a sample if not in playing state + return true; + + GstSample *pSample = llgst_app_sink_pull_sample( mAppSink ); + if(!pSample) + return false; // Done playing + + GstSampleUnref oSampleUnref( pSample ); + GstCaps *pCaps = llgst_sample_get_caps ( pSample ); + if (!pCaps) + return false; + + gint width, height; + GstStructure *pStruct = llgst_caps_get_structure ( pCaps, 0); + + int res = llgst_structure_get_int ( pStruct, "width", &width); + res |= llgst_structure_get_int ( pStruct, "height", &height); + + if( !mPixels ) + return true; + + GstBuffer *pBuffer = llgst_sample_get_buffer ( pSample ); + GstMapInfo map; + llgst_buffer_map ( pBuffer, &map, GST_MAP_READ); + + // Our render buffer is always 1kx1k + + U32 rowSkip = INTERNAL_TEXTURE_SIZE / mTextureHeight; + U32 colSkip = INTERNAL_TEXTURE_SIZE / mTextureWidth; + + for (int row = 0; row < mTextureHeight; ++row) + { + U8 const *pTexelIn = map.data + (row*rowSkip * width *3); +#ifndef FLIP_Y + U8 *pTexelOut = mPixels + (row * mTextureWidth * mDepth ); +#else + U8 *pTexelOut = mPixels + ((mTextureHeight-row-1) * mTextureWidth * mDepth ); +#endif + for( int col = 0; col < mTextureWidth; ++col ) + { + pTexelOut[ 0 ] = pTexelIn[0]; + pTexelOut[ 1 ] = pTexelIn[1]; + pTexelOut[ 2 ] = pTexelIn[2]; + pTexelOut += mDepth; + pTexelIn += colSkip*3; + } + } + + llgst_buffer_unmap( pBuffer, &map ); + setDirty(0,0,mTextureWidth,mTextureHeight); + + return true; +} + +void MediaPluginGStreamer10::mouseDown( int x, int y ) +{ + // do nothing +} + +void MediaPluginGStreamer10::mouseUp( int x, int y ) +{ + // do nothing +} + +void MediaPluginGStreamer10::mouseMove( int x, int y ) +{ + // do nothing +} + + +bool MediaPluginGStreamer10::pause() +{ + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::stop() +{ + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_READY); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::play(double rate) +{ + // NOTE: we don't actually support non-natural rate. + + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::setVolume( float volume ) +{ + // we try to only update volume as conservatively as + // possible, as many gst-plugins-base versions up to at least + // November 2008 have critical race-conditions in setting volume - sigh + if (mVolume == volume) + return true; // nothing to do, everything's fine + + mVolume = volume; + if (mDoneInit && mPlaybin) + { + llg_object_set(mPlaybin, "volume", mVolume, NULL); + return true; + } + + return false; +} + +bool MediaPluginGStreamer10::seek(double time_sec) +{ + bool success = false; + if (mDoneInit && mPlaybin) + { + success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_FLUSH | + GST_SEEK_FLAG_KEY_UNIT), + GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + } + return success; +} + +bool MediaPluginGStreamer10::getTimePos(double &sec_out) +{ + bool got_position = false; + if (mDoneInit && mPlaybin) + { + gint64 pos(0); + GstFormat timefmt = GST_FORMAT_TIME; + got_position = + llgst_element_query_position && + llgst_element_query_position(mPlaybin, + &timefmt, + &pos); + got_position = got_position + && (timefmt == GST_FORMAT_TIME); + // GStreamer may have other ideas, but we consider the current position + // undefined if not PLAYING or PAUSED + got_position = got_position && + (GST_STATE(mPlaybin) == GST_STATE_PLAYING || + GST_STATE(mPlaybin) == GST_STATE_PAUSED); + if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) + { + if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) + { + // if we're playing then we treat an invalid clock time + // as 0, for complicated reasons (insert reason here) + pos = 0; + } + else + { + got_position = false; + } + + } + // If all the preconditions succeeded... we can trust the result. + if (got_position) + { + sec_out = double(pos) / double(GST_SECOND); // gst to sec + } + } + return got_position; +} + +bool MediaPluginGStreamer10::load() +{ + if (!mDoneInit) + return false; // error + + setStatus(STATUS_LOADING); + + mIsLooping = false; + mVolume = 0.1234567f; // minor hack to force an initial volume update + + // Create a pumpable main-loop for this media + mPump = llg_main_loop_new (NULL, FALSE); + if (!mPump) + { + setStatus(STATUS_ERROR); + return false; // error + } + + // instantiate a playbin element to do the hard work + mPlaybin = llgst_element_factory_make ("playbin", ""); + if (!mPlaybin) + { + setStatus(STATUS_ERROR); + return false; // error + } + + // get playbin's bus + GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); + if (!bus) + { + setStatus(STATUS_ERROR); + return false; // error + } + mBusWatchID = llgst_bus_add_watch (bus, + llmediaimplgstreamer_bus_callback, + this); + llgst_object_unref (bus); + + mAppSink = (GstAppSink*)(llgst_element_factory_make ("appsink", "")); + + GstCaps* pCaps = llgst_caps_new_simple( "video/x-raw", + "format", G_TYPE_STRING, "RGB", + "width", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, + "height", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, + NULL ); + + llgst_app_sink_set_caps( mAppSink, pCaps ); + llgst_caps_unref( pCaps ); + + if (!mAppSink) + { + setStatus(STATUS_ERROR); + return false; + } + + llg_object_set(mPlaybin, "video-sink", mAppSink, NULL); + + return true; +} + +bool MediaPluginGStreamer10::unload () +{ + if (!mDoneInit) + return false; // error + + // stop getting callbacks for this bus + llg_source_remove(mBusWatchID); + mBusWatchID = 0; + + if (mPlaybin) + { + llgst_element_set_state (mPlaybin, GST_STATE_NULL); + llgst_object_unref (GST_OBJECT (mPlaybin)); + mPlaybin = NULL; + } + + if (mPump) + { + llg_main_loop_quit(mPump); + mPump = NULL; + } + + mAppSink = NULL; + + setStatus(STATUS_NONE); + + return true; +} + +void LogFunction(GstDebugCategory *category, GstDebugLevel level, const gchar *file, const gchar *function, gint line, GObject *object, GstDebugMessage *message, gpointer user_data ) +#ifndef LL_LINUX // Docu says we need G_GNUC_NO_INSTRUMENT, but GCC says 'error' + G_GNUC_NO_INSTRUMENT +#endif +{ +#ifdef LL_LINUX + std::cerr << file << ":" << line << "(" << function << "): " << llgst_debug_message_get( message ) << std::endl; +#endif +} + +//static +bool MediaPluginGStreamer10::startup() +{ + // first - check if GStreamer is explicitly disabled + if (NULL != getenv("LL_DISABLE_GSTREAMER")) + return false; + + // only do global GStreamer initialization once. + if (!mDoneInit) + { + ll_init_apr(); + + // Get symbols! + std::vector< std::string > vctDSONames; +#if LL_DARWIN +#elif LL_WINDOWS + vctDSONames.push_back( "libgstreamer-1.0-0.dll" ); + vctDSONames.push_back( "libgstapp-1.0-0.dll" ); + vctDSONames.push_back( "libglib-2.0-0.dll" ); + vctDSONames.push_back( "libgobject-2.0-0.dll" ); +#else // linux or other ELFy unixoid + vctDSONames.push_back( "libgstreamer-1.0.so.0" ); + vctDSONames.push_back( "libgstapp-1.0.so.0" ); + vctDSONames.push_back( "libglib-2.0.so.0" ); + vctDSONames.push_back( "libgobject-2.0.so" ); +#endif + if( !grab_gst_syms( vctDSONames ) ) + { + return false; + } + + if (llgst_segtrap_set_enabled) + { + llgst_segtrap_set_enabled(FALSE); + } +#if LL_LINUX + // Gstreamer tries a fork during init, waitpid-ing on it, + // which conflicts with any installed SIGCHLD handler... + struct sigaction tmpact, oldact; + if (llgst_registry_fork_set_enabled ) { + // if we can disable SIGCHLD-using forking behaviour, + // do it. + llgst_registry_fork_set_enabled(false); + } + else { + // else temporarily install default SIGCHLD handler + // while GStreamer initialises + tmpact.sa_handler = SIG_DFL; + sigemptyset( &tmpact.sa_mask ); + tmpact.sa_flags = SA_SIGINFO; + sigaction(SIGCHLD, &tmpact, &oldact); + } +#endif // LL_LINUX + // Protect against GStreamer resetting the locale, yuck. + static std::string saved_locale; + saved_locale = setlocale(LC_ALL, NULL); + +// _putenv_s( "GST_PLUGIN_PATH", "E:\\gstreamer\\1.0\\x86\\lib\\gstreamer-1.0" ); + + llgst_debug_set_default_threshold( GST_LEVEL_WARNING ); + llgst_debug_add_log_function( LogFunction, NULL, NULL ); + llgst_debug_set_active( false ); + + // finally, try to initialize GStreamer! + GError *err = NULL; + gboolean init_gst_success = llgst_init_check(NULL, NULL, &err); + + // restore old locale + setlocale(LC_ALL, saved_locale.c_str() ); + +#if LL_LINUX + // restore old SIGCHLD handler + if (!llgst_registry_fork_set_enabled) + sigaction(SIGCHLD, &oldact, NULL); +#endif // LL_LINUX + + if (!init_gst_success) // fail + { + if (err) + { + llg_error_free(err); + } + return false; + } + + mDoneInit = true; + } + + return true; +} + +//static +bool MediaPluginGStreamer10::closedown() +{ + if (!mDoneInit) + return false; // error + + ungrab_gst_syms(); + + mDoneInit = false; + + return true; +} + +MediaPluginGStreamer10::~MediaPluginGStreamer10() +{ + closedown(); +} + + +std::string MediaPluginGStreamer10::getVersion() +{ + std::string plugin_version = "GStreamer10 media plugin, GStreamer version "; + if (mDoneInit && + llgst_version) + { + guint major, minor, micro, nano; + llgst_version(&major, &minor, µ, &nano); + plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, + (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, + (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); + } + else + { + plugin_version += "(unknown)"; + } + return plugin_version; +} + +void MediaPluginGStreamer10::receiveMessage(const char *message_string) +{ + LLPluginMessage message_in; + + if(message_in.parse(message_string) >= 0) + { + std::string message_class = message_in.getClass(); + std::string message_name = message_in.getName(); + + if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) + { + if(message_name == "init") + { + LLPluginMessage message("base", "init_response"); + LLSD versions = LLSD::emptyMap(); + versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; + versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; + versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; + message.setValueLLSD("versions", versions); + + load(); + + message.setValue("plugin_version", getVersion()); + sendMessage(message); + } + else if(message_name == "idle") + { + // no response is necessary here. + double time = message_in.getValueReal("time"); + + // Convert time to milliseconds for update() + update((int)(time * 1000.0f)); + } + else if(message_name == "cleanup") + { + unload(); + closedown(); + } + else if(message_name == "shm_added") + { + SharedSegmentInfo info; + info.mAddress = message_in.getValuePointer("address"); + info.mSize = (size_t)message_in.getValueS32("size"); + std::string name = message_in.getValue("name"); + + mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); + } + else if(message_name == "shm_remove") + { + std::string name = message_in.getValue("name"); + + SharedSegmentMap::iterator iter = mSharedSegments.find(name); + if(iter != mSharedSegments.end()) + { + if(mPixels == iter->second.mAddress) + { + // This is the currently active pixel buffer. Make sure we stop drawing to it. + mPixels = NULL; + mTextureSegmentName.clear(); + } + mSharedSegments.erase(iter); + } + + // Send the response so it can be cleaned up. + LLPluginMessage message("base", "shm_remove_response"); + message.setValue("name", name); + sendMessage(message); + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) + { + if(message_name == "init") + { + // Plugin gets to decide the texture parameters to use. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); + // lame to have to decide this now, it depends on the movie. Oh well. + mDepth = 4; + + mTextureWidth = 1; + mTextureHeight = 1; + + message.setValueU32("format", GL_RGBA); + message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); + + message.setValueS32("depth", mDepth); + message.setValueS32("default_width", INTERNAL_TEXTURE_SIZE ); + message.setValueS32("default_height", INTERNAL_TEXTURE_SIZE ); + message.setValueU32("internalformat", GL_RGBA8); + message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. + message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale + sendMessage(message); + } + else if(message_name == "size_change") + { + std::string name = message_in.getValue("name"); + S32 width = message_in.getValueS32("width"); + S32 height = message_in.getValueS32("height"); + S32 texture_width = message_in.getValueS32("texture_width"); + S32 texture_height = message_in.getValueS32("texture_height"); + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); + message.setValue("name", name); + message.setValueS32("width", width); + message.setValueS32("height", height); + message.setValueS32("texture_width", texture_width); + message.setValueS32("texture_height", texture_height); + sendMessage(message); + + if(!name.empty()) + { + // Find the shared memory region with this name + SharedSegmentMap::iterator iter = mSharedSegments.find(name); + if(iter != mSharedSegments.end()) + { + mPixels = (unsigned char*)iter->second.mAddress; + mTextureSegmentName = name; + + mTextureWidth = texture_width; + mTextureHeight = texture_height; + memset( mPixels, 0, mTextureWidth*mTextureHeight*mDepth ); + } + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); + message.setValue("name", mTextureSegmentName); + message.setValueS32("width", INTERNAL_TEXTURE_SIZE ); + message.setValueS32("height", INTERNAL_TEXTURE_SIZE ); + sendMessage(message); + + } + } + else if(message_name == "load_uri") + { + std::string uri = message_in.getValue("uri"); + navigateTo( uri ); + sendStatus(); + } + else if(message_name == "mouse_event") + { + std::string event = message_in.getValue("event"); + S32 x = message_in.getValueS32("x"); + S32 y = message_in.getValueS32("y"); + + if(event == "down") + { + mouseDown(x, y); + } + else if(event == "up") + { + mouseUp(x, y); + } + else if(event == "move") + { + mouseMove(x, y); + }; + }; + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) + { + if(message_name == "stop") + { + stop(); + } + else if(message_name == "start") + { + double rate = 0.0; + if(message_in.hasValue("rate")) + { + rate = message_in.getValueReal("rate"); + } + // NOTE: we don't actually support rate. + play(rate); + } + else if(message_name == "pause") + { + pause(); + } + else if(message_name == "seek") + { + double time = message_in.getValueReal("time"); + // defer the actual seek in case we haven't + // really truly started yet in which case there + // is nothing to seek upon + mSeekWanted = true; + mSeekDestination = time; + } + else if(message_name == "set_loop") + { + bool loop = message_in.getValueBoolean("loop"); + mIsLooping = loop; + } + else if(message_name == "set_volume") + { + double volume = message_in.getValueReal("volume"); + setVolume(volume); + } + } + } +} + +int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ + if( MediaPluginGStreamer10::startup() ) + { + MediaPluginGStreamer10 *self = new MediaPluginGStreamer10(host_send_func, host_user_data); + *plugin_send_func = MediaPluginGStreamer10::staticReceiveMessage; + *plugin_user_data = (void*)self; + + return 0; // okay + } + else + { + return -1; // failed to init + } +} From 49d6a2c856403fd1dbf6af8aa71276c3645b6bc6 Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 21 Jan 2019 13:43:33 +0100 Subject: [PATCH 040/125] Use gstreamer1.0 plugin for media --- indra/newview/CMakeLists.txt | 2 +- .../skins/default/xui/en/mime_types_linux.xml | 30 +++++++++---------- indra/newview/viewer_manifest.py | 4 ++- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 527d7f760f..a64a09616e 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2003,7 +2003,7 @@ if (LINUX) ${VIEWER_BINARY_NAME} linux-crash-logger SLPlugin - #media_plugin_gstreamer010 + media_plugin_gstreamer10 #media_plugin_libvlc llcommon ) diff --git a/indra/newview/skins/default/xui/en/mime_types_linux.xml b/indra/newview/skins/default/xui/en/mime_types_linux.xml index 7188b1e699..84aeaf3b54 100644 --- a/indra/newview/skins/default/xui/en/mime_types_linux.xml +++ b/indra/newview/skins/default/xui/en/mime_types_linux.xml @@ -130,7 +130,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -163,7 +163,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -174,7 +174,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -196,7 +196,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -218,7 +218,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -295,7 +295,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -306,7 +306,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -317,7 +317,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -328,7 +328,7 @@ audio - media_plugin_libvlc + media_plugin_gstreamer @@ -438,7 +438,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -449,7 +449,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -460,7 +460,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -471,7 +471,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -482,7 +482,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer @@ -493,7 +493,7 @@ movie - media_plugin_libvlc + media_plugin_gstreamer diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 541112a765..28b2264177 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1495,7 +1495,7 @@ class LinuxManifest(ViewerManifest): # plugins with self.prefix(src="../media_plugins", dst="bin/llplugin"): - self.path("gstreamer010/libmedia_plugin_gstreamer010.so", + self.path("gstreamer10/libmedia_plugin_gstreamer10.so", "libmedia_plugin_gstreamer.so") self.path2basename("libvlc", "libmedia_plugin_libvlc.so") @@ -1551,6 +1551,8 @@ class LinuxManifest(ViewerManifest): def strip_binaries(self): if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build" + if not os.path.isdir( os.path.join( self.get_dst_prefix(), "lib") ): + os.mkdir( os.path.join( self.get_dst_prefix(), "lib") ) # makes some small assumptions about our packaged dir structure self.run_command( ["find"] + From 0561e56b26b0c45070b5a186d5d20b8558ee668e Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 22 Jan 2019 15:20:50 +0100 Subject: [PATCH 041/125] Minor tweak for snap: Store userdata as /home//secondlife rather than /home//.secondlife --- indra/llvfs/lldir_linux.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index 2cd06b81f8..241d58ffeb 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -38,6 +38,11 @@ static std::string getCurrentUserHome(char* fallback) { + // snap-package: + // could use SNAP_USER_DATA (/home/nicky/snap/viewer-release/x1, whereas x1 is the release and thus will change, data is backed up/restored across snap refresh / snap revert) + // could use SNAP_USER_COMMON (/home/nicky/snap/viewer-release/common, data is NOT backed up/restored across snap refresh / snap revert) + // see https://docs.snapcraft.io/environment-variables/7983 + const uid_t uid = getuid(); struct passwd *pw; char *result_cstr = fallback; @@ -174,7 +179,10 @@ void LLDir_Linux::initAppDirs(const std::string &app_name, // traditionally on unixoids, MyApp gets ~/.myapp dir for data mOSUserAppDir = mOSUserDir; mOSUserAppDir += "/"; - mOSUserAppDir += "."; + // When running as a snap package we canot use /home//.secondlife as strict-mode snaps do not get access to .dot files + // In that case we use /home//secondlife + if( nullptr == getenv( "SNAP_USER_DATA" ) ) + mOSUserAppDir += "."; std::string lower_app_name(app_name); LLStringUtil::toLower(lower_app_name); mOSUserAppDir += lower_app_name; From 05331254ebe644aee52fad992ef24af82d771fe4 Mon Sep 17 00:00:00 2001 From: Nicky Date: Wed, 23 Jan 2019 13:34:50 +0100 Subject: [PATCH 042/125] Enable CEF for Linux. --- indra/media_plugins/CMakeLists.txt | 1 + indra/media_plugins/cef/media_plugin_cef.cpp | 17 +++++++- .../skins/default/xui/en/mime_types_linux.xml | 40 +++++++++---------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 803fee551d..51b55f900b 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -7,6 +7,7 @@ if (LINUX) #add_subdirectory(libvlc) add_subdirectory(example) add_subdirectory(gstreamer10) + add_subdirectory(cef) endif (LINUX) if (DARWIN) diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 2bd5526a86..f58a69d16a 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -119,6 +119,9 @@ MediaPluginBase(host_send_func, host_user_data) mPluginsEnabled = false; mJavascriptEnabled = true; mDisableGPU = false; +#ifdef LL_LINUX // Do not use GPU on Linux, using GPU messes with some window managers (https://bitbucket.org/NickyD/phoenix-firestorm-lgpl-linux/commits/14c936db5a02cf0f3ff24eb7f1c92136#comment-6048984) + mDisableGPU = true; +#endif mUserAgentSubtring = ""; mAuthUsername = ""; mAuthPassword = ""; @@ -680,7 +683,8 @@ void MediaPluginCEF::receiveMessage(const char* message_string) keyEvent(key_event, native_key_data); -#elif LL_WINDOWS +//#elif LL_WINDOWS // Windows & Linux +#else std::string event = message_in.getValue("event"); LLSD native_key_data = message_in.getValueLLSD("native_key_data"); @@ -825,6 +829,17 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif + +// Keyboard handling for Linux. +#if LL_LINUX + uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger()); + if( native_scan_code == '\n' ) + native_scan_code = '\r'; + mCEFLib->nativeKeyboardEvent(key_event, native_scan_code, native_virtual_key, native_modifiers); +#endif +// }; void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD::emptyMap()) diff --git a/indra/newview/skins/default/xui/en/mime_types_linux.xml b/indra/newview/skins/default/xui/en/mime_types_linux.xml index 84aeaf3b54..22a0024874 100644 --- a/indra/newview/skins/default/xui/en/mime_types_linux.xml +++ b/indra/newview/skins/default/xui/en/mime_types_linux.xml @@ -7,7 +7,7 @@ none - media_plugin_webkit + media_plugin_cef archive hash - db8a7126178e7230fd6917a28cd67bd7 + 63d804306cf94a338a8690050928cefb url - http://downloads.phoenixviewer.com/open_libndofdev-0.9.183150121-linux64-183150121.tar.bz2 + http://downloads.phoenixviewer.com/open_libndofdev-0.3.190761441-linux64-190761441.tar.bz2 name linux64 From 940cb4fcd54d317a69b556e73effbf29ded83bac Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 19 Mar 2019 05:23:33 +0100 Subject: [PATCH 080/125] - Correct MD5 for libndofdev. - New dullahan with updated SDL2 keyboard - Improve keyboard handling for SDL2 and make sure: - accelerators work again. - enter is properly forwarded as an input event. - mousewheel messages get handled. --- autobuild.xml | 10 +- indra/llwindow/llkeyboardsdl.cpp | 398 ++++++++++++------- indra/llwindow/llkeyboardsdl.h | 2 +- indra/llwindow/llwindowsdl.cpp | 47 ++- indra/llwindow/llwindowsdl.h | 2 - indra/media_plugins/cef/media_plugin_cef.cpp | 33 +- 6 files changed, 312 insertions(+), 180 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 3a83fa0dc3..35390e03af 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -80,9 +80,9 @@ archive hash - 3156400f22047d6e194884970842f571 + b2d2a51bca4e3c3acc98eaab881a652e url - http://downloads.phoenixviewer.com/dullahan_gcc5-1.1.1313_3.3626.1895.g7001d56-linux64-190760044.tar.bz2 + http://downloads.phoenixviewer.com/dullahan_gcc5-1.1.1313_3.3626.1895.g7001d56-linux64-190772217.tar.bz2 name linux64 @@ -866,9 +866,9 @@ archive hash - ccee21f79ca1628c38c55295cd2544e6 + 0f83072d15c7265b21d33b677a511efc url - http://downloads.phoenixviewer.com/dullahan-1.1.1313_3.3626.1895.g7001d56-linux64-190760005.tar.bz2 + http://downloads.phoenixviewer.com/dullahan-1.1.1313_3.3626.1895.g7001d56-linux64-190772141.tar.bz2 name linux64 @@ -2922,7 +2922,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 63d804306cf94a338a8690050928cefb + a32657211755c612faa3b7c5fb061465 url http://downloads.phoenixviewer.com/open_libndofdev-0.3.190761441-linux64-190761441.tar.bz2 diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 1874d4f323..af303ada7f 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -42,24 +42,23 @@ LLKeyboardSDL::LLKeyboardSDL() // SDL maps the letter keys to the ASCII you'd expect, but it's lowercase... - // Those are handled by SDL2 via text input, do not map them - - // U16 cur_char; - // for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) - // { - // mTranslateKeyMap[cur_char] = cur_char; - // } - // for (cur_char = 'a'; cur_char <= 'z'; cur_char++) - // { - // mTranslateKeyMap[cur_char] = (cur_char - 'a') + 'A'; - // } - // - // for (cur_char = '0'; cur_char <= '9'; cur_char++) - // { - // mTranslateKeyMap[cur_char] = cur_char; - // } + // Looks like we need to map those despite of SDL_TEXTINPUT handling most of this, but without + // the translation lower->upper here accelerators will not work. - // Compatibility shim for SDL2 > SDL1. -// The dullahan plugin still expects SDL1 codes. Temporarily map those SDL keyes till the plugin is updated -enum class SDL1Keys: U32 { - SDLK_UNKNOWN = 0, - SDLK_BACKSPACE = 8, - SDLK_TAB = 9, - SDLK_CLEAR = 12, - SDLK_RETURN = 13, - SDLK_PAUSE = 19, - SDLK_ESCAPE = 27, - SDLK_DELETE = 127, - SDLK_KP_PERIOD = 266, - SDLK_KP_DIVIDE = 267, - SDLK_KP_MULTIPLY= 268, - SDLK_KP_MINUS = 269, - SDLK_KP_PLUS = 270, - SDLK_KP_ENTER = 271, - SDLK_KP_EQUALS = 272, - SDLK_UP = 273, - SDLK_DOWN = 274, - SDLK_RIGHT = 275, - SDLK_LEFT = 276, - SDLK_INSERT = 277, - SDLK_HOME = 278, - SDLK_END = 279, - SDLK_PAGEUP = 280, - SDLK_PAGEDOWN = 281, - SDLK_F1 = 282, - SDLK_F2 = 283, - SDLK_F3 = 284, - SDLK_F4 = 285, - SDLK_F5 = 286, - SDLK_F6 = 287, - SDLK_F7 = 288, - SDLK_F8 = 289, - SDLK_F9 = 290, - SDLK_F10 = 291, - SDLK_F11 = 292, - SDLK_F12 = 293, - SDLK_F13 = 294, - SDLK_F14 = 295, - SDLK_F15 = 296, - SDLK_CAPSLOCK = 301, - SDLK_RSHIFT = 303, - SDLK_LSHIFT = 304, - SDLK_RCTRL = 305, - SDLK_LCTRL = 306, - SDLK_RALT = 307, - SDLK_LALT = 308, - SDLK_MODE = 313, /**< "Alt Gr" key */ - SDLK_HELP = 315, - SDLK_SYSREQ = 317, - SDLK_MENU = 319, - SDLK_POWER = 320, /**< Power Macintosh power key */ - SDLK_UNDO = 322, /**< Atari keyboard has Undo */ -}; - -std::map< U32, U32 > mSDL2_to_SDL1; - -U32 LLKeyboardSDL::mapSDL2toSDL1( U32 aSymbol ) +enum class WindowsVK : U32 { - if( mSDL2_to_SDL1.empty() ) - { - mSDL2_to_SDL1[ SDLK_UNKNOWN ] = (U32)SDL1Keys::SDLK_UNKNOWN; - mSDL2_to_SDL1[ SDLK_BACKSPACE ] = (U32)SDL1Keys::SDLK_BACKSPACE; - mSDL2_to_SDL1[ SDLK_TAB ] = (U32)SDL1Keys::SDLK_TAB; - mSDL2_to_SDL1[ SDLK_CLEAR ] = (U32)SDL1Keys::SDLK_CLEAR; - mSDL2_to_SDL1[ SDLK_RETURN ] = (U32)SDL1Keys::SDLK_RETURN; - mSDL2_to_SDL1[ SDLK_PAUSE ] = (U32)SDL1Keys::SDLK_PAUSE; - mSDL2_to_SDL1[ SDLK_ESCAPE ] = (U32)SDL1Keys::SDLK_ESCAPE; - mSDL2_to_SDL1[ SDLK_DELETE ] = (U32)SDL1Keys::SDLK_DELETE; - mSDL2_to_SDL1[ SDLK_KP_PERIOD ] = (U32)SDL1Keys::SDLK_KP_PERIOD; - mSDL2_to_SDL1[ SDLK_KP_DIVIDE ] = (U32)SDL1Keys::SDLK_KP_DIVIDE; - mSDL2_to_SDL1[ SDLK_KP_MULTIPLY] = (U32)SDL1Keys::SDLK_KP_MULTIPLY; - mSDL2_to_SDL1[ SDLK_KP_MINUS ] = (U32)SDL1Keys::SDLK_KP_MINUS; - mSDL2_to_SDL1[ SDLK_KP_PLUS ] = (U32)SDL1Keys::SDLK_KP_PLUS; - mSDL2_to_SDL1[ SDLK_KP_ENTER ] = (U32)SDL1Keys::SDLK_KP_ENTER; - mSDL2_to_SDL1[ SDLK_KP_EQUALS ] = (U32)SDL1Keys::SDLK_KP_EQUALS; - mSDL2_to_SDL1[ SDLK_UP ] = (U32)SDL1Keys::SDLK_UP; - mSDL2_to_SDL1[ SDLK_DOWN ] = (U32)SDL1Keys::SDLK_DOWN; - mSDL2_to_SDL1[ SDLK_RIGHT ] = (U32)SDL1Keys::SDLK_RIGHT; - mSDL2_to_SDL1[ SDLK_LEFT ] = (U32)SDL1Keys::SDLK_LEFT; - mSDL2_to_SDL1[ SDLK_INSERT ] = (U32)SDL1Keys::SDLK_INSERT; - mSDL2_to_SDL1[ SDLK_HOME ] = (U32)SDL1Keys::SDLK_HOME; - mSDL2_to_SDL1[ SDLK_END ] = (U32)SDL1Keys::SDLK_END; - mSDL2_to_SDL1[ SDLK_PAGEUP ] = (U32)SDL1Keys::SDLK_PAGEUP; - mSDL2_to_SDL1[ SDLK_PAGEDOWN ] = (U32)SDL1Keys::SDLK_PAGEDOWN; - mSDL2_to_SDL1[ SDLK_F1 ] = (U32)SDL1Keys::SDLK_F1; - mSDL2_to_SDL1[ SDLK_F2 ] = (U32)SDL1Keys::SDLK_F2; - mSDL2_to_SDL1[ SDLK_F3 ] = (U32)SDL1Keys::SDLK_F3; - mSDL2_to_SDL1[ SDLK_F4 ] = (U32)SDL1Keys::SDLK_F4; - mSDL2_to_SDL1[ SDLK_F5 ] = (U32)SDL1Keys::SDLK_F5; - mSDL2_to_SDL1[ SDLK_F6 ] = (U32)SDL1Keys::SDLK_F6; - mSDL2_to_SDL1[ SDLK_F7 ] = (U32)SDL1Keys::SDLK_F7; - mSDL2_to_SDL1[ SDLK_F8 ] = (U32)SDL1Keys::SDLK_F8; - mSDL2_to_SDL1[ SDLK_F9 ] = (U32)SDL1Keys::SDLK_F9; - mSDL2_to_SDL1[ SDLK_F10 ] = (U32)SDL1Keys::SDLK_F10; - mSDL2_to_SDL1[ SDLK_F11 ] = (U32)SDL1Keys::SDLK_F11; - mSDL2_to_SDL1[ SDLK_F12 ] = (U32)SDL1Keys::SDLK_F12; - mSDL2_to_SDL1[ SDLK_F13 ] = (U32)SDL1Keys::SDLK_F13; - mSDL2_to_SDL1[ SDLK_F14 ] = (U32)SDL1Keys::SDLK_F14; - mSDL2_to_SDL1[ SDLK_F15 ] = (U32)SDL1Keys::SDLK_F15; - mSDL2_to_SDL1[ SDLK_CAPSLOCK ] = (U32)SDL1Keys::SDLK_CAPSLOCK; - mSDL2_to_SDL1[ SDLK_RSHIFT ] = (U32)SDL1Keys::SDLK_RSHIFT; - mSDL2_to_SDL1[ SDLK_LSHIFT ] = (U32)SDL1Keys::SDLK_LSHIFT; - mSDL2_to_SDL1[ SDLK_RCTRL ] = (U32)SDL1Keys::SDLK_RCTRL; - mSDL2_to_SDL1[ SDLK_LCTRL ] = (U32)SDL1Keys::SDLK_LCTRL; - mSDL2_to_SDL1[ SDLK_RALT ] = (U32)SDL1Keys::SDLK_RALT; - mSDL2_to_SDL1[ SDLK_LALT ] = (U32)SDL1Keys::SDLK_LALT; - mSDL2_to_SDL1[ SDLK_MODE ] = (U32)SDL1Keys::SDLK_MODE; - mSDL2_to_SDL1[ SDLK_HELP ] = (U32)SDL1Keys::SDLK_HELP; - mSDL2_to_SDL1[ SDLK_SYSREQ ] = (U32)SDL1Keys::SDLK_SYSREQ; - mSDL2_to_SDL1[ SDLK_MENU ] = (U32)SDL1Keys::SDLK_MENU; - mSDL2_to_SDL1[ SDLK_POWER ] = (U32)SDL1Keys::SDLK_POWER; - mSDL2_to_SDL1[ SDLK_UNDO ] = (U32)SDL1Keys::SDLK_UNDO; - } + VK_UNKNOWN = 0, + VK_BACK = 0x08, + VK_TAB = 0x09, + VK_CLEAR = 0x0C, + VK_RETURN = 0x0D, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_KANA = 0x15, + VK_HANGUL = 0x15, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = 0x19, + VK_ESCAPE = 0x1B, + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + VK_0 = 0x30, + VK_1 = 0x31, + VK_2 = 0x32, + VK_3 = 0x33, + VK_4 = 0x34, + VK_5 = 0x35, + VK_6 = 0x36, + VK_7 = 0x37, + VK_8 = 0x38, + VK_9 = 0x39, + VK_A = 0x41, + VK_B = 0x42, + VK_C = 0x43, + VK_D = 0x44, + VK_E = 0x45, + VK_F = 0x46, + VK_G = 0x47, + VK_H = 0x48, + VK_I = 0x49, + VK_J = 0x4A, + VK_K = 0x4B, + VK_L = 0x4C, + VK_M = 0x4D, + VK_N = 0x4E, + VK_O = 0x4F, + VK_P = 0x50, + VK_Q = 0x51, + VK_R = 0x52, + VK_S = 0x53, + VK_T = 0x54, + VK_U = 0x55, + VK_V = 0x56, + VK_W = 0x57, + VK_X = 0x58, + VK_Y = 0x59, + VK_Z = 0x5A, + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + VK_SLEEP = 0x5F, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_MEDIA_LAUNCH_MAIL = 0xB4, + VK_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5, + VK_MEDIA_LAUNCH_APP1 = 0xB6, + VK_MEDIA_LAUNCH_APP2 = 0xB7, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_OEM_102 = 0xE2, + VK_PROCESSKEY = 0xE5, + VK_PACKET = 0xE7, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, +}; - auto itr = mSDL2_to_SDL1.find( aSymbol ); - if( itr != mSDL2_to_SDL1.end() ) +std::map< U32, U32 > mSDL2_to_Win; +std::set< U32 > mIgnoreSDL2Keys; + +U32 LLKeyboardSDL::mapSDL2toWin( U32 aSymbol ) +{ + // Map SDLK_ virtual keys to Windows VK_ virtual keys. + // Text is handled via unicode input (SDL_TEXTINPUT event) and does not need to be translated into VK_ values as those match already. + if( mSDL2_to_Win.empty() ) + { + mSDL2_to_Win[ SDLK_UNKNOWN ] = (U32)WindowsVK::VK_UNKNOWN; + mSDL2_to_Win[ SDLK_BACKSPACE ] = (U32)WindowsVK::VK_BACK; + mSDL2_to_Win[ SDLK_TAB ] = (U32)WindowsVK::VK_TAB; + mSDL2_to_Win[ SDLK_CLEAR ] = (U32)WindowsVK::VK_CLEAR; + mSDL2_to_Win[ SDLK_RETURN ] = (U32)WindowsVK::VK_RETURN; + mSDL2_to_Win[ SDLK_PAUSE ] = (U32)WindowsVK::VK_PAUSE; + mSDL2_to_Win[ SDLK_ESCAPE ] = (U32)WindowsVK::VK_ESCAPE; + mSDL2_to_Win[ SDLK_DELETE ] = (U32)WindowsVK::VK_DELETE; + mSDL2_to_Win[ SDLK_KP_PERIOD ] = (U32)WindowsVK::VK_OEM_PERIOD; + mSDL2_to_Win[ SDLK_KP_DIVIDE ] = (U32)WindowsVK::VK_DIVIDE; + mSDL2_to_Win[ SDLK_KP_MULTIPLY] = (U32)WindowsVK::VK_MULTIPLY; + mSDL2_to_Win[ SDLK_KP_MINUS ] = (U32)WindowsVK::VK_OEM_MINUS; + mSDL2_to_Win[ SDLK_KP_PLUS ] = (U32)WindowsVK::VK_OEM_PLUS; + mSDL2_to_Win[ SDLK_KP_ENTER ] = (U32)WindowsVK::VK_RETURN; + + // ? + //mSDL2_to_Win[ SDLK_KP_EQUALS ] = (U32)WindowsVK::VK_EQUALS; + + mSDL2_to_Win[ SDLK_UP ] = (U32)WindowsVK::VK_UP; + mSDL2_to_Win[ SDLK_DOWN ] = (U32)WindowsVK::VK_DOWN; + mSDL2_to_Win[ SDLK_RIGHT ] = (U32)WindowsVK::VK_RIGHT; + mSDL2_to_Win[ SDLK_LEFT ] = (U32)WindowsVK::VK_LEFT; + mSDL2_to_Win[ SDLK_INSERT ] = (U32)WindowsVK::VK_INSERT; + mSDL2_to_Win[ SDLK_HOME ] = (U32)WindowsVK::VK_HOME; + mSDL2_to_Win[ SDLK_END ] = (U32)WindowsVK::VK_END; + mSDL2_to_Win[ SDLK_PAGEUP ] = (U32)WindowsVK::VK_PRIOR; + mSDL2_to_Win[ SDLK_PAGEDOWN ] = (U32)WindowsVK::VK_NEXT; + mSDL2_to_Win[ SDLK_F1 ] = (U32)WindowsVK::VK_F1; + mSDL2_to_Win[ SDLK_F2 ] = (U32)WindowsVK::VK_F2; + mSDL2_to_Win[ SDLK_F3 ] = (U32)WindowsVK::VK_F3; + mSDL2_to_Win[ SDLK_F4 ] = (U32)WindowsVK::VK_F4; + mSDL2_to_Win[ SDLK_F5 ] = (U32)WindowsVK::VK_F5; + mSDL2_to_Win[ SDLK_F6 ] = (U32)WindowsVK::VK_F6; + mSDL2_to_Win[ SDLK_F7 ] = (U32)WindowsVK::VK_F7; + mSDL2_to_Win[ SDLK_F8 ] = (U32)WindowsVK::VK_F8; + mSDL2_to_Win[ SDLK_F9 ] = (U32)WindowsVK::VK_F9; + mSDL2_to_Win[ SDLK_F10 ] = (U32)WindowsVK::VK_F10; + mSDL2_to_Win[ SDLK_F11 ] = (U32)WindowsVK::VK_F11; + mSDL2_to_Win[ SDLK_F12 ] = (U32)WindowsVK::VK_F12; + mSDL2_to_Win[ SDLK_F13 ] = (U32)WindowsVK::VK_F13; + mSDL2_to_Win[ SDLK_F14 ] = (U32)WindowsVK::VK_F14; + mSDL2_to_Win[ SDLK_F15 ] = (U32)WindowsVK::VK_F15; + mSDL2_to_Win[ SDLK_CAPSLOCK ] = (U32)WindowsVK::VK_CAPITAL; + mSDL2_to_Win[ SDLK_RSHIFT ] = (U32)WindowsVK::VK_SHIFT; + mSDL2_to_Win[ SDLK_LSHIFT ] = (U32)WindowsVK::VK_SHIFT; + mSDL2_to_Win[ SDLK_RCTRL ] = (U32)WindowsVK::VK_CONTROL; + mSDL2_to_Win[ SDLK_LCTRL ] = (U32)WindowsVK::VK_CONTROL; + mSDL2_to_Win[ SDLK_RALT ] = (U32)WindowsVK::VK_MENU; + mSDL2_to_Win[ SDLK_LALT ] = (U32)WindowsVK::VK_MENU; + + // VK_MODECHANGE ? + // mSDL2_to_Win[ SDLK_MODE ] = (U32)WindowsVK::VK_MODE; + + mSDL2_to_Win[ SDLK_HELP ] = (U32)WindowsVK::VK_HELP; + + // ? + // mSDL2_to_Win[ SDLK_SYSREQ ] = (U32)WindowsVK::VK_SYSREQ; + mSDL2_to_Win[ SDLK_MENU ] = (U32)WindowsVK::VK_MENU; + + // ? + // mSDL2_to_Win[ SDLK_POWER ] = (U32)WindowsVK::VK_POWER; + + // ? + //mSDL2_to_Win[ SDLK_UNDO ] = (U32)WindowsVK::VK_UNDO; + } + + auto itr = mSDL2_to_Win.find( aSymbol ); + if( itr != mSDL2_to_Win.end() ) return itr->second; return aSymbol; } -// Compatibility shim for SDL2 > SDL1. - #endif diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index cefde07148..e31229eb6e 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -52,7 +52,7 @@ private: std::map mInvTranslateNumpadMap; // inverse of the above public: - static U32 mapSDL2toSDL1( U32 ); + static U32 mapSDL2toWin( U32 ); }; #endif diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 5fe817923a..20873bb151 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -250,7 +250,6 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, mFlashing = FALSE; #endif // LL_X11 - mKeyScanCode = 0; mKeyVirtualKey = 0; mKeyModifiers = KMOD_NONE; } @@ -1738,6 +1737,11 @@ void LLWindowSDL::gatherInput() { switch (event.type) { + case SDL_MOUSEWHEEL: + if( event.wheel.y != 0 ) + mCallbacks->handleScrollWheel(this, event.wheel.y); + break; + case SDL_MOUSEMOTION: { LLCoordWindow winCoord(event.button.x, event.button.y); @@ -1753,44 +1757,39 @@ void LLWindowSDL::gatherInput() auto string = utf8str_to_utf16str( event.text.text ); for( auto key: string ) { - mKeyScanCode = string[0]; mKeyVirtualKey = string[0]; mKeyModifiers = SDL_GetModState(); - mSDLSym = string[0]; - handleUnicodeUTF16( key, gKeyboard->currentMask(FALSE)); + handleUnicodeUTF16( key, mKeyModifiers ); } break; } case SDL_KEYDOWN: - mKeyScanCode = event.key.keysym.scancode; - mKeyVirtualKey = LLKeyboardSDL::mapSDL2toSDL1( event.key.keysym.sym ); + mKeyVirtualKey = event.key.keysym.sym; mKeyModifiers = event.key.keysym.mod; - mSDLSym = LLKeyboardSDL::mapSDL2toSDL1( event.key.keysym.sym ); - gKeyboard->handleKeyDown(event.key.keysym.sym, event.key.keysym.mod); + gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers ); + + // Slightly hacky :| To make the viewer honor enter (eg to accept form input) we've to not only send handleKeyDown but also send a + // invoke handleUnicodeUTF16 in case the user hits return. + // Note that we cannot blindly use handleUnicodeUTF16 for each SDL_KEYDOWN. Doing so will create bogus keyboard input (like % for cursor left). + if( mKeyVirtualKey == SDLK_RETURN ) + handleUnicodeUTF16( mKeyVirtualKey, mKeyModifiers ); + // part of the fix for SL-13243 if (SDLCheckGrabbyKeys(event.key.keysym.sym, TRUE) != 0) SDLReallyCaptureInput(TRUE); - { - KEY dummyKey{}; - - if( gKeyboard->translateKey( mSDLSym, &dummyKey ) ) - handleUnicodeUTF16( mSDLSym, gKeyboard->currentMask(FALSE)); - } break; case SDL_KEYUP: - mKeyScanCode = event.key.keysym.scancode; - mKeyVirtualKey = LLKeyboardSDL::mapSDL2toSDL1( event.key.keysym.sym ); + mKeyVirtualKey = event.key.keysym.sym; mKeyModifiers = event.key.keysym.mod; - mSDLSym = LLKeyboardSDL::mapSDL2toSDL1( event.key.keysym.sym ); - if (SDLCheckGrabbyKeys(event.key.keysym.sym, FALSE) == 0) + if (SDLCheckGrabbyKeys(mKeyVirtualKey, FALSE) == 0) SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 - gKeyboard->handleKeyUp(event.key.keysym.sym, event.key.keysym.mod); + gKeyboard->handleKeyUp(mKeyVirtualKey,mKeyModifiers); break; case SDL_MOUSEBUTTONDOWN: @@ -2365,7 +2364,7 @@ static void color_changed_callback(GtkWidget *widget, */ LLSD LLWindowSDL::getNativeKeyData() { - LLSD result = LLSD::emptyMap(); + LLSD result = LLSD::emptyMap(); U32 modifiers = 0; // pretend-native modifiers... oh what a tangled web we weave! @@ -2383,11 +2382,11 @@ LLSD LLWindowSDL::getNativeKeyData() // *todo: test ALTs - I don't have a case for testing these. Do you? // *todo: NUM? - I don't care enough right now (and it's not a GDK modifier). - result["scan_code"] = (S32)mKeyScanCode; - result["virtual_key"] = (S32)mKeyVirtualKey; + result["virtual_key"] = (S32)mKeyVirtualKey; + result["virtual_key_win"] = (S32)LLKeyboardSDL::mapSDL2toWin( mKeyVirtualKey ); result["modifiers"] = (S32)modifiers; - result[ "sdl_sym" ] = (S32)mSDLSym; // Store the SDL Keysym too. - return result; + + return result; } diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index c2b366a690..027bd0ab6b 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -215,10 +215,8 @@ private: LLTimer mFlashTimer; #endif //LL_X11 - U32 mKeyScanCode; U32 mKeyVirtualKey; U32 mKeyModifiers; - U32 mSDLSym; // Store the SDL Keysym too. BOOL mUseLegacyCursors; // Legacy cursor setting from main program private: diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 607a4b4ec0..cd653df257 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -832,12 +832,20 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat // Keyboard handling for Linux. #if LL_LINUX - uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); - uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); - uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger()); - if( native_scan_code == '\n' ) - native_scan_code = '\r'; - mCEFLib->nativeKeyboardEvent(key_event, native_scan_code, native_virtual_key, native_modifiers); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + if( native_virtual_key == '\n' ) + native_virtual_key = '\r'; + + mCEFLib->nativeKeyboardEventSDL2(key_event, native_virtual_key, native_modifiers, false); + + // Slightly hacky :| To make CEF honor enter (eg to accept form input) we've to not only send KE_KEY_UP/KE_KEY_DOWN + // but also a KE_KEY_CHAR event. + // Note that we cannot blindly send a KE_CHAR for each KE_KEY_UP. Doing so will create bogus keyboard input (like % for cursor left). + // Adding this just in llwindowsdl does not seem to fire an appropriate unicodeInput event down below, thus repeat this check here again :( + if( dullahan::KE_KEY_UP == key_event && native_virtual_key == '\r' ) + mCEFLib->nativeKeyboardEventSDL2(dullahan::KE_KEY_CHAR, native_virtual_key, native_modifiers, false); #endif // }; @@ -872,12 +880,13 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD #endif // Keyboard handling for Linux. #if LL_LINUX - uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); - uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); - uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger()); - if( native_scan_code == '\n' ) - native_scan_code = '\r'; - mCEFLib->nativeKeyboardEvent(dullahan::KE_KEY_DOWN, native_scan_code, native_virtual_key, native_modifiers); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + if( native_virtual_key == '\n' ) + native_virtual_key = '\r'; + + mCEFLib->nativeKeyboardEventSDL2(dullahan::KE_KEY_CHAR, native_virtual_key, native_modifiers, false); #endif // }; From abdf415be6c22b4946c860e42fbc67c88ebdb803 Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 19 Mar 2019 13:28:49 +0100 Subject: [PATCH 081/125] Make jemalloc default. --- indra/cmake/jemalloc.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/cmake/jemalloc.cmake b/indra/cmake/jemalloc.cmake index da77d0f160..b0c9da340c 100644 --- a/indra/cmake/jemalloc.cmake +++ b/indra/cmake/jemalloc.cmake @@ -1,9 +1,11 @@ # -*- cmake -*- include(Prebuilt) +set( USE_JEMALLOC ON CACHE BOOL "Ship prebuild jemalloc library with packaged viewer" ) + if( USE_JEMALLOC ) if (USESYSTEMLIBS) - message( WARNING "Not implemented" ) + message( WARNING "Search for jemalloc not implemented for standalone builds" ) else (USESYSTEMLIBS) use_prebuilt_binary(jemalloc) endif (USESYSTEMLIBS) From c5722bc83c8ff6253515d99473060ab1dbb452e7 Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 19 Mar 2019 15:01:10 +0100 Subject: [PATCH 082/125] Update ndof for Linux x64. --- autobuild.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 35390e03af..4841c6b399 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2922,9 +2922,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - a32657211755c612faa3b7c5fb061465 + 878638bb7d53edc6e466bf4adebababa url - http://downloads.phoenixviewer.com/open_libndofdev-0.3.190761441-linux64-190761441.tar.bz2 + http://downloads.phoenixviewer.com/open_libndofdev-0.8.190781155-linux64-190781155.tar.bz2 name linux64 From 36231a947ffbcbae97ce6388233e2fa2b140e4c6 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 21 Mar 2019 15:58:18 +0100 Subject: [PATCH 083/125] Make sure newGrab is always initialized. While at it delete some dead code. --- indra/llwindow/llwindowsdl.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 20873bb151..8a05a66b5c 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1480,7 +1480,7 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) else mReallyCapturedCount = 0; - bool wantGrab, newGrab; + bool wantGrab; if (mReallyCapturedCount <= 0) // uncapture { wantGrab = false; @@ -1495,9 +1495,11 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) LL_WARNS() << "ReallyCapture count was < 0" << LL_ENDL; } + bool newGrab = wantGrab; + +#if LL_X11 if (!mFullscreen) /* only bother if we're windowed anyway */ { -#if LL_X11 if (mSDL_Display) { /* we dirtily mix raw X11 with SDL so that our pointer @@ -1512,8 +1514,6 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) int result; if (wantGrab == true) { - //LL_INFOS() << "X11 POINTER GRABBY" << LL_ENDL; - //newGrab = SDL_WM_GrabInput(wantGrab); maybe_lock_display(); result = XGrabPointer(mSDL_Display, mSDL_XWindowID, True, 0, GrabModeAsync, @@ -1526,9 +1526,7 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) newGrab = false; } else if (wantGrab == false) { - //LL_INFOS() << "X11 POINTER UNGRABBY" << LL_ENDL; newGrab = false; - //newGrab = SDL_WM_GrabInput(false); maybe_lock_display(); XUngrabPointer(mSDL_Display, CurrentTime); @@ -1538,12 +1536,8 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) } else // not actually running on X11, for some reason newGrab = wantGrab; } -#endif // LL_X11 - } else { - // pretend we got what we wanted, when really we don't care. - newGrab = wantGrab; } - +#endif // LL_X11 // return boolean success for whether we ended up in the desired state return (capture && newGrab) || (!capture && !newGrab); } From 2a234a093bcd6b4db35752eb17f4fdf83c0f4119 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 21 Mar 2019 15:58:39 +0100 Subject: [PATCH 084/125] Update SDL2 --- autobuild.xml | 8 ++++---- indra/cmake/LLWindow.cmake | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 4841c6b399..cef61f7193 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -296,9 +296,9 @@ archive hash - b92593733b5b9183dbf261089aaa968c + f33a49af7e126d6e105b2e1b71462f43 url - http://downloads.phoenixviewer.com/SDL-2.0.8-linux64-190761430.tar.bz2 + http://downloads.phoenixviewer.com/SDL-2.0.9-linux64-190801246.tar.bz2 name linux64 @@ -2922,9 +2922,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 878638bb7d53edc6e466bf4adebababa + 8cd00feef1e9b5ff9d4bcb05fd3d3e80 url - http://downloads.phoenixviewer.com/open_libndofdev-0.8.190781155-linux64-190781155.tar.bz2 + http://downloads.phoenixviewer.com/open_libndofdev-0.9.190801254-linux64-190801254.tar.bz2 name linux64 diff --git a/indra/cmake/LLWindow.cmake b/indra/cmake/LLWindow.cmake index 9247b149de..e2930f062e 100644 --- a/indra/cmake/LLWindow.cmake +++ b/indra/cmake/LLWindow.cmake @@ -19,7 +19,7 @@ else (USESYSTEMLIBS) use_prebuilt_binary(SDL) set (SDL_FOUND TRUE) set (SDL_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/i686-linux) - set (SDL_LIBRARY SDL2 directfb fusion direct X11) + set (SDL_LIBRARY SDL2 X11) endif (LINUX) endif (USESYSTEMLIBS) From 0380ce2ec747e4a286fd762b81fb232704f0cc90 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 24 Mar 2019 20:24:40 +0100 Subject: [PATCH 085/125] Linux; update ndof --- autobuild.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index cef61f7193..6a7b9f7dc2 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2922,9 +2922,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 8cd00feef1e9b5ff9d4bcb05fd3d3e80 + 4813c6ca3daddf5b2e1725f40583c416 url - http://downloads.phoenixviewer.com/open_libndofdev-0.9.190801254-linux64-190801254.tar.bz2 + http://downloads.phoenixviewer.com/open_libndofdev-0.10.190831827-linux64-190831827.tar.bz2 name linux64 From 7b5ed25918f1ff72b21e0a6c4b343fd6a8997feb Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 19 Apr 2019 22:36:24 +0200 Subject: [PATCH 086/125] Speculative fix to allow viewer to run when started via flatpak. --- indra/newview/linux_tools/wrapper.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index 2fa1c60d12..6319a22248 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -160,6 +160,17 @@ for ARG in "$@"; do fi done +# Hack, otherwise eg sscanf inside flatpak fails. +# This happens if the locale is set (for example) to de_DE which uses , as a decimal separator. +# There is code in the viewer which should handle this but when run from "flatpak run" it will not work. +# Needs some more investigation; but at least this hack will allow the viewer to run. +if [ ! -z "${FLATPAK_ID}" ] +then + echo "Setting LC_NUMERIC to en_US.utf8 due to running inside flatpak" + export LC_NUMERIC=en_US.utf8 +fi +# + # Run the program. # Don't quote $LL_WRAPPER because, if empty, it should simply vanish from the # command line. But DO quote "${ARGS[@]}": preserve separate args as From 31c40a1cb175358049dbfb637f3df004a36ba0cb Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 6 Oct 2019 13:52:00 +0200 Subject: [PATCH 087/125] - Make sure to call all SDL_GL_Set fuctions before creatig the window - Create a context via SDL_GL_CreateContext. Some drivers are okay without, some are not. - Quert GL attributes with SDL_GL_Get rather than OpenGL calls. --- indra/llwindow/llwindowsdl.cpp | 132 +++++++++++---------------------- 1 file changed, 44 insertions(+), 88 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 8a05a66b5c..c9dd4d30fe 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -326,12 +326,6 @@ static int x11_detect_VRAM_kb_fp(FILE *fp, const char *prefix_str) static int x11_detect_VRAM_kb() { -#if LL_SOLARIS && defined(__sparc) - // NOTE: there's no Xorg server on SPARC so just return 0 - // and allow SDL to attempt to get the amount of VRAM - return(0); -#else - std::string x_log_location("/var/log/"); std::string fname; int rtn = 0; // 'could not detect' @@ -412,7 +406,6 @@ static int x11_detect_VRAM_kb() } } return rtn; -#endif // LL_SOLARIS } #endif // LL_X11 @@ -474,7 +467,6 @@ void LLWindowSDL::tryFindFullscreenSize( int &width, int &height ) } } - BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) { //bool glneedsinit = false; @@ -523,7 +515,33 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B mSDLFlags = sdlflags; - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + GLint redBits{8}, greenBits{8}, blueBits{8}, alphaBits{8}; + + GLint depthBits{(bits <= 16) ? 16 : 24}, stencilBits{8}; + + if (getenv("LL_GL_NO_STENCIL")) + stencilBits = 0; + + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, alphaBits); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, redBits); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, greenBits); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, blueBits); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthBits ); + + // We need stencil support for a few (minor) things. + if (stencilBits) + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilBits); + + // *FIX: try to toggle vsync here? + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + + if (mFSAASamples > 0) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); + } + mWindow = SDL_CreateWindow( mWindowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, mSDLFlags ); if( mWindow ) @@ -580,45 +598,14 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B bmpsurface = NULL; } - // note: these SetAttributes make Tom's 9600-on-AMD64 fail to - // get a visual, but it's broken anyway when it does, and without - // these SetAttributes we might easily get an avoidable substandard - // visual to work with on most other machines. - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); -#if !LL_SOLARIS - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); - // We need stencil support for a few (minor) things. - if (!getenv("LL_GL_NO_STENCIL")) - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); -#else - // NOTE- use smaller Z-buffer to enable more graphics cards - // - This should not affect better GPUs and has been proven - // to provide 24-bit z-buffers when available. - // - // As the API states: - // - // GLX_DEPTH_SIZE Must be followed by a nonnegative - // minimum size specification. If this - // value is zero, visuals with no depth - // buffer are preferred. Otherwise, the - // largest available depth buffer of at - // least the minimum size is preferred. + SDL_GLContext glContext = SDL_GL_CreateContext( mWindow ); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); -#endif - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, (bits <= 16) ? 1 : 8); - - // *FIX: try to toggle vsync here? - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - if (mFSAASamples > 0) + if( glContext == 0 ) { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); + LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; + return FALSE; } + // Detect video memory size. # if LL_X11 @@ -644,46 +631,32 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B // explicitly unsupported cards. //const char* RENDERER = (const char*) glGetString(GL_RENDERER); - GLint depthBits, stencilBits, redBits, greenBits, blueBits, alphaBits; - - glGetIntegerv(GL_RED_BITS, &redBits); - glGetIntegerv(GL_GREEN_BITS, &greenBits); - glGetIntegerv(GL_BLUE_BITS, &blueBits); - glGetIntegerv(GL_ALPHA_BITS, &alphaBits); - glGetIntegerv(GL_DEPTH_BITS, &depthBits); - glGetIntegerv(GL_STENCIL_BITS, &stencilBits); + SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redBits); + SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenBits); + SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueBits); + SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &alphaBits); + SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthBits); + SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilBits); LL_INFOS() << "GL buffer:" << LL_ENDL; - LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; - LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; - LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; - LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; - LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; - LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; + LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; + LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; + LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; + LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; + LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; + LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; GLint colorBits = redBits + greenBits + blueBits + alphaBits; // fixme: actually, it's REALLY important for picking that we get at // least 8 bits each of red,green,blue. Alpha we can be a bit more // relaxed about if we have to. -#if LL_SOLARIS && defined(__sparc) -// again the __sparc required because Xsun support, 32bit are very pricey on SPARC - if(colorBits < 24) //HACK: on SPARC allow 24-bit color -#else if (colorBits < 32) -#endif { close(); setupFailure( -#if LL_SOLARIS && defined(__sparc) - "Second Life requires at least 24-bit color on SPARC to run in a window.\n" - "Please use fbconfig to set your default color depth to 24 bits.\n" - "You may also need to adjust the X11 setting in SMF. To do so use\n" - " 'svccfg -s svc:/application/x11/x11-server setprop options/default_depth=24'\n" -#else "Second Life requires True Color (32-bit) to run in a window.\n" "Please go to Control Panels -> Display -> Settings and\n" "set the screen to 32-bit color.\n" -#endif "Alternately, if you choose to run fullscreen, Second Life\n" "will automatically adjust the screen each time it runs.", "Error", @@ -691,23 +664,6 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B return FALSE; } -#if 0 // *FIX: we're going to brave it for now... - if (alphaBits < 8) - { - close(); - setupFailure( - "Second Life is unable to run because it can't get an 8 bit alpha\n" - "channel. Usually this is due to video card driver issues.\n" - "Please make sure you have the latest video card drivers installed.\n" - "Also be sure your monitor is set to True Color (32-bit) in\n" - "Control Panels -> Display -> Settings.\n" - "If you continue to receive this message, contact customer service.", - "Error", - OSMB_OK); - return FALSE; - } -#endif - #if LL_X11 /* Grab the window manager specific information */ SDL_SysWMinfo info; From 021984e937e986082b7a92a4d0a2af1cfe55f5c4 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 6 Oct 2019 15:20:07 +0200 Subject: [PATCH 088/125] Update SDL --- autobuild.xml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index d5789d39fe..3d365b9506 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -348,9 +348,9 @@ archive hash - f33a49af7e126d6e105b2e1b71462f43 + 1d4819f2944d572667b6a009bfadd8e0 url - http://downloads.phoenixviewer.com/SDL-2.0.9-linux64-190801246.tar.bz2 + http://3p.firestormviewer.org/SDL-2.0.10-linux64-192791159.tar.bz2 name linux64 @@ -2943,26 +2943,14 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors open-libndofdev platforms - linux - - archive - - hash - 246ed298944fd5200e4cac3703d6e075 - url - http://downloads.phoenixviewer.com/open_libndofdev-0.9.183150124-linux-183150124.tar.bz2 - - name - linux - linux64 archive hash - 4813c6ca3daddf5b2e1725f40583c416 + db686600d804c74147aea9b4363e7a1e url - http://downloads.phoenixviewer.com/open_libndofdev-0.10.190831827-linux64-190831827.tar.bz2 + http://3p.firestormviewer.org/open_libndofdev-0.10.192791216-linux64-192791216.tar.bz2 name linux64 From 88081398ad81f5a44f4c975d14c9e844dfc79ee1 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 02:54:31 +0100 Subject: [PATCH 089/125] Update open_libndofdev 0.12 (includes compatibility with vs2017 branch and official SDL2 compatibility). --- autobuild.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 2964d0a7af..7f6acfa174 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2948,9 +2948,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - db686600d804c74147aea9b4363e7a1e + e52e1223fe1603485178ac65d6562a61 url - http://3p.firestormviewer.org/open_libndofdev-0.10.192791216-linux64-192791216.tar.bz2 + http://3p.firestormviewer.org/open_libndofdev-0.12.193591719-linux64-193591719.tar.bz2 name linux64 From 460647607a16b5e13d8be71e76747c901d869542 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 18:16:12 +0100 Subject: [PATCH 090/125] Linux; Do not try to package any vlc files. --- indra/newview/viewer_manifest.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 16eca3201b..6cf655e7b0 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1850,23 +1850,23 @@ class LinuxManifest(ViewerManifest): # plugins with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'media_plugins'), dst="bin/llplugin"): #self.path("gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so") - self.path2basename("libvlc", "libmedia_plugin_libvlc.so") + #self.path2basename("libvlc", "libmedia_plugin_libvlc.so") self.path("cef/libmedia_plugin_cef.so", "libmedia_plugin_cef.so" ) - with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): - self.path( "plugins.dat" ) - self.path( "*/*.so" ) + #with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): + # self.path( "plugins.dat" ) + # self.path( "*/*.so" ) - with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): - self.path( "libvlc*.so*" ) + #with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): + # self.path( "libvlc*.so*" ) - with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): - self.path( "plugins.dat" ) - self.path( "*/*.so" ) + #with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): + # self.path( "plugins.dat" ) + # self.path( "*/*.so" ) - with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): - self.path( "libvlc*.so*" ) + #with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): + # self.path( "libvlc*.so*" ) snapStage = os.environ.get( "SNAPCRAFT_STAGE" ) if snapStage != None: From 84cdf8d902c412d11ddf8a5fa5d79fc04d0cd81d Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 18:17:17 +0100 Subject: [PATCH 091/125] Linux; Do not try to package directfb files (they get removed wth SDL2). --- indra/newview/viewer_manifest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 6cf655e7b0..11db500301 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -2004,9 +2004,9 @@ class LinuxManifest(ViewerManifest): #self.fs_path("libminizip.so") self.path("libuuid.so*") self.path("libSDL*") - self.path("libdirectfb*.so*") - self.path("libfusion*.so*") - self.path("libdirect*.so*") + #self.path("libdirectfb*.so*") + #self.path("libfusion*.so*") + #self.path("libdirect*.so*") self.fs_try_path("libopenjpeg.so*") self.path("libhunspell-1.3.so*") self.path("libalut.so*") From 0bb5632dd9782b3307f6f8c74071798338d810d1 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 19:29:03 +0100 Subject: [PATCH 092/125] Linux; Option to disable tarball via "FS_CREATE_NO_TAR=1 make" when using --pacckage to create a directory to run the viewer from but not having any need for the tar.xz (saving symbols and creating the tarball is slow). --- indra/newview/viewer_manifest.py | 51 ++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 11db500301..3e7f1b8d46 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -2059,10 +2059,16 @@ class LinuxManifest(ViewerManifest): installer_name = "_".join(installer_name_components) #installer_name = self.installer_base_name() - self.fs_delete_linux_symbols() # Delete old syms - self.strip_binaries() - self.fs_save_linux_symbols() # Package symbols, add debug link - self.fs_setuid_chromesandbox() # Chown chrome-sandbox to root:root and set the setuid bit + createTar = True + if os.environ.get( "FS_CREATE_NO_TAR" ): + createTar = False + + + if createTar: + self.fs_delete_linux_symbols() # Delete old syms + self.strip_binaries() + self.fs_save_linux_symbols() # Package symbols, add debug link + self.fs_setuid_chromesandbox() # Chown chrome-sandbox to root:root and set the setuid bit # Fix access permissions self.run_command(['find', self.get_dst_prefix(), @@ -2079,24 +2085,25 @@ class LinuxManifest(ViewerManifest): self.package_file = installer_name + '.tar.xz' - # temporarily move directory tree so that it has the right - # name in the tarfile - realname = self.get_dst_prefix() - tempname = self.build_path_of(installer_name) - self.run_command(["mv", realname, tempname]) - try: - # only create tarball if it's a release build. - if self.args['buildtype'].lower() == 'release': - # --numeric-owner hides the username of the builder for - # security etc. - self.run_command(['tar', '-C', self.get_build_prefix(), - '--numeric-owner', self.fs_linux_tar_excludes(), '-caf', - tempname + '.tar.xz', installer_name]) - else: - print "Skipping %s.tar.xz for non-Release build (%s)" % \ - (installer_name, self.args['buildtype']) - finally: - self.run_command(["mv", tempname, realname]) + if createTar: + # temporarily move directory tree so that it has the right + # name in the tarfile + realname = self.get_dst_prefix() + tempname = self.build_path_of(installer_name) + self.run_command(["mv", realname, tempname]) + try: + # only create tarball if it's a release build. + if self.args['buildtype'].lower() == 'release': + # --numeric-owner hides the username of the builder for + # security etc. + self.run_command(['tar', '-C', self.get_build_prefix(), + '--numeric-owner', self.fs_linux_tar_excludes(), '-caf', + tempname + '.tar.xz', installer_name]) + else: + print "Skipping %s.tar.xz for non-Release build (%s)" % \ + (installer_name, self.args['buildtype']) + finally: + self.run_command(["mv", tempname, realname]) def strip_binaries(self): if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): From 759c43cbb3c7fc0cea035212c591663559fe61fc Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 19:33:57 +0100 Subject: [PATCH 093/125] Linux; Do ot call SDL_GL_CreateContext if the environment variable FS_NO_SDL_CREATE_CONTEXT is set to 1. NVidia does not seem to like he SDL_GL_CreateContext while AMD needs it. The documentation on the other hand agrees the call is a must. Needs some more investigation. --- indra/llwindow/llwindowsdl.cpp | 18 ++++++++++++------ indra/llwindow/llwindowsdl.h | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index c9dd4d30fe..549bb74604 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -203,6 +203,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, // Ignore use_gl for now, only used for drones on PC mWindow = NULL; + mContext = {}; mNeedsResize = FALSE; mOverrideAspectRatio = 0.f; mGrabbyKeyFlags = 0; @@ -598,15 +599,20 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B bmpsurface = NULL; } - SDL_GLContext glContext = SDL_GL_CreateContext( mWindow ); - - if( glContext == 0 ) + char const *pEnv = getenv("FS_NO_SDL_CREATE_CONTEXT" ); + if( pEnv == nullptr || std::string( pEnv ) == "1" ) { - LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; - return FALSE; + mContext = SDL_GL_CreateContext( mWindow ); + + if( mContext == 0 ) + { + LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; + raise(SIGTRAP); + return FALSE; + } + // SDL_GL_SetSwapInterval(1); } - // Detect video memory size. # if LL_X11 gGLManager.mVRAM = x11_detect_VRAM_kb() / 1024; diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 027bd0ab6b..698f678b80 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -190,8 +190,12 @@ protected: // U32 mGrabbyKeyFlags; int mReallyCapturedCount; + SDL_Window* mWindow; SDL_Surface* mSurface; + SDL_GLContext mContext; + SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; + std::string mWindowTitle; double mOriginalAspectRatio; BOOL mNeedsResize; // Constructor figured out the window is too big, it needs a resize. @@ -202,7 +206,6 @@ protected: int mSDLFlags; - SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ int mIsMinimized; /* 0=no, 1=yes, else unknown */ From ea7526eecf9d9c1d4d4e122a22829660ebbeda43 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 26 Dec 2019 20:15:00 +0100 Subject: [PATCH 094/125] Linux; Move SDL_GL_CreateContext right after SDL_CreateImage. Putting it before SDL_GetWindowSurface seems to fix the problems with the NVidia driver. --- indra/llwindow/llwindowsdl.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 549bb74604..f1ae8c1f4c 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -546,8 +546,20 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B mWindow = SDL_CreateWindow( mWindowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, mSDLFlags ); if( mWindow ) + { + mContext = SDL_GL_CreateContext( mWindow ); + + if( mContext == 0 ) + { + LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; + setupFailure("GL Context creation error creation error", "Error", OSMB_OK); + return FALSE; + } + // SDL_GL_SetSwapInterval(1); mSurface = SDL_GetWindowSurface( mWindow ); - + } + + if( mFullscreen ) { if (mSurface) @@ -599,20 +611,6 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B bmpsurface = NULL; } - char const *pEnv = getenv("FS_NO_SDL_CREATE_CONTEXT" ); - if( pEnv == nullptr || std::string( pEnv ) == "1" ) - { - mContext = SDL_GL_CreateContext( mWindow ); - - if( mContext == 0 ) - { - LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; - raise(SIGTRAP); - return FALSE; - } - // SDL_GL_SetSwapInterval(1); - } - // Detect video memory size. # if LL_X11 gGLManager.mVRAM = x11_detect_VRAM_kb() / 1024; From 2cb95d6e0a1f20af1bb608accd1cf5db2503201a Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sat, 1 Feb 2020 13:08:05 +0100 Subject: [PATCH 095/125] Replace some deprecated gdk/gtk functions. --- indra/llwindow/llwindowsdl.cpp | 18 ++++++++++-------- indra/newview/llfilepicker.cpp | 6 +++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index f1ae8c1f4c..d77499d302 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -43,6 +43,7 @@ #if LL_GTK extern "C" { # include "gtk/gtk.h" +#include } #include #endif // LL_GTK @@ -2248,9 +2249,8 @@ S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 typ gWindowImplementation->mSDL_XWindowID != None) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(gWindowImplementation->mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); + GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(), gWindowImplementation->mSDL_XWindowID); + gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); } # endif //LL_X11 @@ -2363,13 +2363,12 @@ BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) if (mSDL_XWindowID != None) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); + GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(), gWindowImplementation->mSDL_XWindowID); + gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); } # endif //LL_X11 - GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); + GtkColorSelection *colorsel = GTK_COLOR_SELECTION ( gtk_color_selection_dialog_get_color_selection( GTK_COLOR_SELECTION_DIALOG(win) ) ); GdkColor color, orig_color; orig_color.pixel = 0; @@ -2396,7 +2395,10 @@ BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) gtk_window_set_modal(GTK_WINDOW(win), TRUE); gtk_widget_show_all(win); // hide the help button - we don't service it. - gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); + GtkWidget* help_button; + g_object_get(win, "help-button", &help_button, NULL); + gtk_widget_hide( help_button ); + // gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); gtk_main(); if (response == GTK_RESPONSE_OK && diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index db95618bd0..93750addd9 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -38,6 +38,7 @@ #if LL_SDL #include "llwindowsdl.h" // for some X/GTK utils to help with filepickers +#include #endif // LL_SDL #if LL_LINUX || LL_SOLARIS @@ -1186,9 +1187,8 @@ GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::stri if (None != XWindowID) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); + GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(),XWindowID); + gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); } else { From 86f89ff16651b5a74d269a7ce4f9c347509201c9 Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sun, 2 Feb 2020 10:41:38 +0100 Subject: [PATCH 096/125] Remove gtk_color_selection_dialog_new as it is deprecated, thus rendering LLWindowSDL::dialogColorPicker useless. Normally there is LLFloaterColorPicker to handle colour selection and one has to switch the debug "UseDefaultColorPicker" to even activate the OS colour picker. The non deprecated alternative is GtkColorChooserDialog / gtk_color_chooser_dialog_new which could be used if really absolutely necessary. --- indra/llwindow/llwindowsdl.cpp | 78 +--------------------------------- 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index d77499d302..d65bde527c 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -2302,14 +2302,7 @@ S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 typ return rtn; } -static void color_changed_callback(GtkWidget *widget, - gpointer user_data) -{ - GtkColorSelection *colorsel = GTK_COLOR_SELECTION(widget); - GdkColor *colorp = (GdkColor*)user_data; - gtk_color_selection_get_current_color(colorsel, colorp); -} /* @@ -2346,76 +2339,7 @@ LLSD LLWindowSDL::getNativeKeyData() BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) { - BOOL rtn = FALSE; - - beforeDialog(); - - if (ll_try_gtk_init()) - { - GtkWidget *win = NULL; - - win = gtk_color_selection_dialog_new(NULL); - -# if LL_X11 - // Get GTK to tell the window manager to associate this - // dialog with our non-GTK SDL window, which should try - // to keep it on top etc. - if (mSDL_XWindowID != None) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(), gWindowImplementation->mSDL_XWindowID); - gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); - } -# endif //LL_X11 - - GtkColorSelection *colorsel = GTK_COLOR_SELECTION ( gtk_color_selection_dialog_get_color_selection( GTK_COLOR_SELECTION_DIALOG(win) ) ); - - GdkColor color, orig_color; - orig_color.pixel = 0; - orig_color.red = guint16(65535 * *r); - orig_color.green= guint16(65535 * *g); - orig_color.blue = guint16(65535 * *b); - color = orig_color; - - gtk_color_selection_set_previous_color (colorsel, &color); - gtk_color_selection_set_current_color (colorsel, &color); - gtk_color_selection_set_has_palette (colorsel, TRUE); - gtk_color_selection_set_has_opacity_control(colorsel, FALSE); - - gint response = GTK_RESPONSE_NONE; - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); - - g_signal_connect (G_OBJECT (colorsel), "color_changed", - G_CALLBACK (color_changed_callback), - &color); - - gtk_window_set_modal(GTK_WINDOW(win), TRUE); - gtk_widget_show_all(win); - // hide the help button - we don't service it. - GtkWidget* help_button; - g_object_get(win, "help-button", &help_button, NULL); - gtk_widget_hide( help_button ); - // gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); - gtk_main(); - - if (response == GTK_RESPONSE_OK && - (orig_color.red != color.red - || orig_color.green != color.green - || orig_color.blue != color.blue) ) - { - *r = color.red / 65535.0f; - *g = color.green / 65535.0f; - *b = color.blue / 65535.0f; - rtn = TRUE; - } - } - - afterDialog(); - - return rtn; + return FALSE; } #else S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) From cf13b1ecc4e0ce8608f2a4d3800bf80e1f51e694 Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sun, 2 Feb 2020 10:42:37 +0100 Subject: [PATCH 097/125] Replace deprecated GTK_STOCK_ defines with the newish label names. --- indra/newview/llfilepicker.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 93750addd9..e84f68d2fe 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -1136,17 +1136,14 @@ GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::stri GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN); + gchar const *acceptText = is_folder ? "_Apply" :(is_save ? "_Save" : "_Open"); win = gtk_file_chooser_dialog_new(NULL, NULL, - pickertype, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - is_folder ? - GTK_STOCK_APPLY : - (is_save ? - GTK_STOCK_SAVE : - GTK_STOCK_OPEN), - GTK_RESPONSE_ACCEPT, - (gchar *)NULL); + pickertype, + "_Cancel", + GTK_RESPONSE_CANCEL, + acceptText, + GTK_RESPONSE_ACCEPT, + (gchar *)NULL); mCurContextName = context; // get the default path for this usage context if it's been From 03a60eb67105084010a93ebc698d4a601b00e511 Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sun, 2 Feb 2020 10:43:14 +0100 Subject: [PATCH 098/125] Disable include of deprecated gtk header via GTK_DISABLE_DEPRECATED. --- indra/cmake/UI.cmake | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index d0125a7bea..a08c5ea3db 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -5,23 +5,29 @@ include(FreeType) if (USESYSTEMLIBS) include(FindPkgConfig) + if( NOT GTK_VERSION ) + set( GTK_VERSION 2.0 ) + endif() if (LINUX) set(PKGCONFIG_PACKAGES atk cairo - gdk-2.0 + gdk-${GTK_VERSION} gdk-pixbuf-2.0 glib-2.0 gmodule-2.0 - gtk+-2.0 + gtk+-${GTK_VERSION} gthread-2.0 libpng pango pangoft2 - #pangox - pangoxft sdl2 ) + if( GTK_VERSION LESS "3.0" ) + LIST( APPEND PKGCONFIG_PACKAGES pangoxft ) + else() + add_definitions( -DGTK_DISABLE_DEPRECATED) + endif() endif (LINUX) foreach(pkg ${PKGCONFIG_PACKAGES}) From b12aca2fe4da0a7762d69a365b9b25db92d14c44 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 2 Feb 2020 14:27:29 +0100 Subject: [PATCH 099/125] Resing a SDL window invalidates the surface, grab a new one via SDL_GetWindowSurface after a resize. --- indra/llwindow/llwindowsdl.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index d65bde527c..8ec220b9ec 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1831,13 +1831,14 @@ void LLWindowSDL::gatherInput() case SDL_WINDOWEVENT: // *FIX: handle this? { - if( event.window.event == SDL_WINDOWEVENT_RESIZED || - event.window.event == SDL_WINDOWEVENT_RESIZED ) + if( event.window.event == SDL_WINDOWEVENT_RESIZED + /* || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED*/ ) // SDL_WINDOWEVENT_SIZE_CHANGED is followed by SDL_WINDOWEVENT_RESIZED, so handling one shall be enough { LL_INFOS() << "Handling a resize event: " << event.window.data1 << "x" << event.window.data2 << LL_ENDL; S32 width = llmax(event.window.data1, (S32)mMinWindowWidth); S32 height = llmax(event.window.data2, (S32)mMinWindowHeight); + mSurface = SDL_GetWindowSurface( mWindow ); // *FIX: I'm not sure this is necessary! // I think is is not From 45b59dc1b089d56d1d3065823997c9960f2263a2 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 7 Feb 2020 22:14:44 +0100 Subject: [PATCH 100/125] Implement window resizing with advanced menu. --- indra/llwindow/llwindowsdl.cpp | 37 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 8ec220b9ec..0a6d15003d 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -902,24 +902,37 @@ BOOL LLWindowSDL::setPosition(const LLCoordScreen position) return TRUE; } +template< typename T > bool setSizeImpl( const T& newSize, SDL_Window *pWin ) +{ + if( !pWin ) + return false; + + auto nFlags = SDL_GetWindowFlags( pWin ); + + if( nFlags & SDL_WINDOW_MAXIMIZED ) + SDL_RestoreWindow( pWin ); + + + SDL_SetWindowSize( pWin, newSize.mX, newSize.mY ); + SDL_Event event; + event.type = SDL_WINDOWEVENT; + event.window.event = SDL_WINDOWEVENT_RESIZED; + event.window.windowID = SDL_GetWindowID( pWin ); + event.window.data1 = newSize.mX; + event.window.data2 = newSize.mY; + SDL_PushEvent( &event ); + + return true; +} + BOOL LLWindowSDL::setSizeImpl(const LLCoordScreen size) { - if(mWindow) - { - return TRUE; - } - - return FALSE; + return ::setSizeImpl( size, mWindow ); } BOOL LLWindowSDL::setSizeImpl(const LLCoordWindow size) { - if(mWindow) - { - return TRUE; - } - - return FALSE; + return ::setSizeImpl( size, mWindow ); } From a1f2aba7e959e92f150fc34c7f4828804ffc27d2 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 19 Apr 2020 23:28:27 +0200 Subject: [PATCH 101/125] Linux/SDL2: Implement changes suggest by Henri Beauchamp. --- indra/llwindow/llkeyboardsdl.cpp | 2 +- indra/llwindow/llwindowsdl.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index af303ada7f..7416b4f7b2 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -281,7 +281,7 @@ MASK LLKeyboardSDL::currentMask(BOOL for_mouse_event) // For keyboard events, consider Meta keys equivalent to Control if (!for_mouse_event) { - if (mask & KMOD_ALT) result |= MASK_CONTROL; + if (mask & KMOD_GUI) result |= MASK_CONTROL; } return result; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 0a6d15003d..02873fca6d 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1498,7 +1498,8 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) newGrab = true; else newGrab = false; - } else if (wantGrab == false) + } + else { newGrab = false; @@ -1507,13 +1508,12 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) // Make sure the ungrab happens RIGHT NOW. XSync(mSDL_Display, False); maybe_unlock_display(); - } else // not actually running on X11, for some reason - newGrab = wantGrab; + } } } #endif // LL_X11 // return boolean success for whether we ended up in the desired state - return (capture && newGrab) || (!capture && !newGrab); + return capture == newGrab; } U32 LLWindowSDL::SDLCheckGrabbyKeys(U32 keysym, BOOL gain) @@ -1707,7 +1707,7 @@ void LLWindowSDL::gatherInput() { case SDL_MOUSEWHEEL: if( event.wheel.y != 0 ) - mCallbacks->handleScrollWheel(this, event.wheel.y); + mCallbacks->handleScrollWheel(this, -event.wheel.y); break; case SDL_MOUSEMOTION: From b3effe7e7e2e38590f82cae557decf818e40c474 Mon Sep 17 00:00:00 2001 From: Nicky Dasmijn Date: Sat, 30 May 2020 17:49:27 +0200 Subject: [PATCH 102/125] Windows keyboard compatibility with the SDL2 changes. --- indra/llwindow/llkeyboardwin32.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 04b99187eb..5ce422a294 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -119,8 +119,10 @@ LLKeyboardWin32::LLKeyboardWin32() mTranslateKeyMap[VK_APPS] = KEY_CONTEXT_MENU; // FIRE-19933: Open context menu on context menu key press // Build inverse map - std::map::iterator iter; - for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + // Change to U32 for SDL2 + //std::map::iterator iter; + // for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + for (auto iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } @@ -142,7 +144,9 @@ LLKeyboardWin32::LLKeyboardWin32() mTranslateNumpadMap[0x6E] = KEY_PAD_DEL; // keypad . mTranslateNumpadMap[0x6F] = KEY_PAD_DIVIDE; // keypad / - for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + // Change to U32 for SDL2 + // for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + for (auto iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) { mInvTranslateNumpadMap[iter->second] = iter->first; } From a077a0fcedc4a89cb6401de4645572d7637643d8 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 31 May 2020 03:02:51 +0200 Subject: [PATCH 103/125] Linux; implement LLWindowSDL::setTitle via SDL_SetWindowTitle --- indra/llwindow/llwindowsdl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 02873fca6d..de9da919a2 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -413,7 +413,7 @@ static int x11_detect_VRAM_kb() void LLWindowSDL::setTitle(const std::string &title) { - // SDL_WM_SetCaption(title.c_str(), title.c_str()); + SDL_SetWindowTitle( mWindow, title.c_str() ); } void LLWindowSDL::tryFindFullscreenSize( int &width, int &height ) From b0b97c15db4ca1517b5cac5ed95e27805a52ab15 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 16 Aug 2020 13:48:12 +0200 Subject: [PATCH 104/125] Linux; Replace GTK file and directory ticker with FLTK. --- autobuild.xml | 28 ++++++++++++ indra/cmake/UI.cmake | 4 +- indra/newview/lldirpicker.cpp | 41 ++++++++++++++++- indra/newview/lldirpicker.h | 2 +- indra/newview/llfilepicker.cpp | 82 ++++++++++++++++++++++++++++++++-- indra/newview/llfilepicker.h | 17 ++++++- 6 files changed, 164 insertions(+), 10 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 46f94d91e8..e77bc889e5 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3,6 +3,34 @@ installables + fltk + + copyright + Copyright (C) fltk project + license + LGPL/fltk + license_file + LICENSES/fltk.txt + name + fltk + platforms + + linux64 + + archive + + hash + 81fe1e927e4fe3c5e5f15ce6219ca883 + url + http://3p.firestormviewer.org/fltk-1.3.5.202282121-linux64-202282121.tar.bz2 + + name + linux + + + version + 1.3.5 + gstreamer10 copyright diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index a08c5ea3db..7f0d9e7620 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -41,10 +41,12 @@ if (USESYSTEMLIBS) else (USESYSTEMLIBS) if (LINUX) use_prebuilt_binary(gtk-atk-pango-glib) + use_prebuilt_binary(fltk) endif (LINUX) if (LINUX) set(UI_LIB_NAMES + libfltk.a freetype atk-1.0 gdk-x11-2.0 @@ -87,5 +89,5 @@ else (USESYSTEMLIBS) endif (USESYSTEMLIBS) if (LINUX) - add_definitions(-DLL_GTK=1 -DLL_X11=1) + add_definitions(-DLL_GTK=1 -DLL_X11=1 -DLL_FLTK=1) endif (LINUX) diff --git a/indra/newview/lldirpicker.cpp b/indra/newview/lldirpicker.cpp index b8e6e81ee6..3c72a3db28 100644 --- a/indra/newview/lldirpicker.cpp +++ b/indra/newview/lldirpicker.cpp @@ -41,6 +41,11 @@ # include "llfilepicker.h" #endif +#ifdef LL_FLTK + #include "FL/Fl.H" + #include "FL/Fl_Native_File_Chooser.H" +#endif + // // Globals // @@ -193,32 +198,41 @@ LLDirPicker::LLDirPicker() : mFileName(NULL), mLocked(false) { +#ifndef LL_FLTK mFilePicker = new LLFilePicker(); +#endif + reset(); } LLDirPicker::~LLDirPicker() { +#ifndef LL_FLTK delete mFilePicker; +#endif } void LLDirPicker::reset() { +#ifndef LL_FLTK if (mFilePicker) - mFilePicker->reset(); + mFilePicker->reset(); +#else + mDir = ""; +#endif } BOOL LLDirPicker::getDir(std::string* filename, bool blocking) { reset(); - // if local file browsing is turned off, return without opening dialog if ( check_local_file_access_enabled() == false ) { return FALSE; } +#ifndef LL_FLTK #if !LL_MESA_HEADLESS if (mFilePicker) @@ -237,15 +251,38 @@ BOOL LLDirPicker::getDir(std::string* filename, bool blocking) #endif // !LL_MESA_HEADLESS return FALSE; +#else + Fl_Native_File_Chooser flDlg; + flDlg.title("Pick a dir"); + flDlg.type(Fl_Native_File_Chooser::BROWSE_DIRECTORY ); + + int res = flDlg.show(); + if( res == 0 ) + { + char const *pDir = flDlg.filename(0); + if( pDir ) + mDir = pDir; + } + else if( res == -1 ) + { + LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL; + } + + return !mDir.empty(); +#endif } std::string LLDirPicker::getDirName() { +#ifndef LL_FLTK if (mFilePicker) { return mFilePicker->getFirstFile(); } return ""; +#else + return mDir; +#endif } #else // not implemented diff --git a/indra/newview/lldirpicker.h b/indra/newview/lldirpicker.h index c7dba12130..861913c51c 100644 --- a/indra/newview/lldirpicker.h +++ b/indra/newview/lldirpicker.h @@ -80,7 +80,7 @@ private: #if LL_LINUX || LL_SOLARIS || LL_DARWIN // On Linux we just implement LLDirPicker on top of LLFilePicker - LLFilePicker *mFilePicker; + // LLFilePicker *mFilePicker; #endif diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index e84f68d2fe..ec048ea752 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -36,11 +36,18 @@ #include "llviewercontrol.h" #include "llwindow.h" // beforeDialog() +#undef LL_GTK #if LL_SDL -#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers -#include +// #include "llwindowsdl.h" // for some X/GTK utils to help with filepickers +// #include #endif // LL_SDL +#ifdef LL_FLTK + #include "FL/Fl.H" + #include "FL/Fl_Native_File_Chooser.H" +#endif + + #if LL_LINUX || LL_SOLARIS #include "llhttpconstants.h" // file picker uses some of thes constants on Linux #endif @@ -1563,12 +1570,79 @@ BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) return rtn; } +#elif LL_FLTK +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) +{ + return openFileDialog( filter, blocking, eSaveFile ); +} + +BOOL LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) +{ + return openFileDialog( filter, blocking, eOpenFile ); +} + +BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) +{ + return openFileDialog( filter, blocking, eOpenMultiple ); +} + +void setupFilter( Fl_Native_File_Chooser &chooser, LLFilePicker::ESaveFilter filter ) +{ +} + +void setupFilter( Fl_Native_File_Chooser &chooser, LLFilePicker::ELoadFilter filter ) +{ +} + +bool LLFilePicker::openFileDialog( int32_t filter, bool blocking, EType aType ) +{ + if ( check_local_file_access_enabled() == false ) + return false; + + reset(); + Fl_Native_File_Chooser::Type flType = Fl_Native_File_Chooser::BROWSE_FILE; + + if( aType == eOpenMultiple ) + flType = Fl_Native_File_Chooser::BROWSE_MULTI_FILE; + else if( aType == eSaveFile ) + flType = Fl_Native_File_Chooser::BROWSE_SAVE_FILE; + + Fl_Native_File_Chooser flDlg; + flDlg.title("Pick a file"); + flDlg.type( flType ); + + if( aType == eSaveFile ) + setupFilter( flDlg, (ESaveFilter) filter ); + else + setupFilter( flDlg, (ELoadFilter) filter ); + + int res = flDlg.show(); + if( res == 0 ) + { + int32_t count = flDlg.count(); + if( count < 0 ) + count = 0; + for( int32_t i = 0; i < count; ++i ) + { + char const *pFile = flDlg.filename(i); + if( pFile && strlen(pFile) > 0 ) + mFiles.push_back( pFile ); + } + } + else if( res == -1 ) + { + LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL; + } + + return mFiles.empty()?FALSE:TRUE; +} + # else // LL_GTK // Hacky stubs designed to facilitate fake getSaveFile and getOpenFile with // static results, when we don't have a real filepicker. -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) { // if local file browsing is turned off, return without opening dialog // (Even though this is a stub, I think we still should not return anything at all) @@ -1631,7 +1705,7 @@ BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) #else // not implemented -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blockin ) { reset(); return FALSE; diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 163997b31f..70df523c0a 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -33,6 +33,12 @@ #ifndef LL_LLFILEPICKER_H #define LL_LLFILEPICKER_H +#if LL_FLTK + #if LL_GTK + #undef LL_GTK + #endif +#endif + #include "stdtypes.h" #if LL_DARWIN @@ -63,7 +69,7 @@ extern "C" { class LLFilePicker { -#ifdef LL_GTK +#if LL_GTK friend class LLDirPicker; friend void chooser_responder(GtkWidget *, gint, gpointer); #endif // LL_GTK @@ -187,7 +193,14 @@ private: // we also remember the extension of the last added file. std::string mCurrentExtension; #endif - +#if LL_FLTK + enum EType + { + eSaveFile, eOpenFile, eOpenMultiple + }; + bool openFileDialog( int32_t filter, bool blocking, EType aType ); +#endif + std::vector mFiles; S32 mCurrentFile; bool mLocked; From 11da6293bd5abbd872ad51d705d231a0510c3335 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 16 Aug 2020 23:39:55 +0200 Subject: [PATCH 105/125] Remove dependency on gtk/atk etc. This breaks a few things. Some of them need to be implemented via SDL for others a glib package needs be be pulled in. --- indra/cmake/Copy3rdPartyLibs.cmake | 3 -- indra/cmake/DBusGlib.cmake | 4 +++ indra/cmake/GLIB.cmake | 0 indra/cmake/UI.cmake | 18 +----------- indra/llwindow/llwindowsdl.cpp | 38 +++++++++++++------------- indra/media_plugins/CMakeLists.txt | 8 ++++-- indra/newview/CMakeLists.txt | 4 +-- indra/newview/desktopnotifierlinux.cpp | 24 ++++++++++++++-- indra/newview/llappviewerlinux.cpp | 2 +- indra/newview/llappviewerlinux.h | 8 ++++-- indra/newview/viewer_manifest.py | 4 +-- 11 files changed, 62 insertions(+), 51 deletions(-) create mode 100644 indra/cmake/GLIB.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 8c0043d03e..35c94a3a68 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -206,14 +206,11 @@ elseif(LINUX) set(release_files #libapr-1.so.0 #libaprutil-1.so.0 - libatk-1.0.so #libdb-5.1.so ${EXPAT_COPY} #libfreetype.so.6.6.2 #libfreetype.so.6 #libGLOD.so - libgmodule-2.0.so - libgobject-2.0.so libhunspell-1.3.so.0.0.0 libopenal.so #libopenjpeg.so diff --git a/indra/cmake/DBusGlib.cmake b/indra/cmake/DBusGlib.cmake index 5e46b6711a..0010c48c52 100644 --- a/indra/cmake/DBusGlib.cmake +++ b/indra/cmake/DBusGlib.cmake @@ -1,6 +1,8 @@ # -*- cmake -*- include(Prebuilt) +include(GLIB) +if( GLIB_FOUND ) if (USESYSTEMLIBS) include(FindPkgConfig) @@ -27,3 +29,5 @@ endif (DBUSGLIB_FOUND) if (DBUSGLIB) add_definitions(-DLL_DBUS_ENABLED=1) endif (DBUSGLIB) + +endif() diff --git a/indra/cmake/GLIB.cmake b/indra/cmake/GLIB.cmake new file mode 100644 index 0000000000..e69de29bb2 diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 7f0d9e7620..8c5d266cf5 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -40,7 +40,6 @@ if (USESYSTEMLIBS) list(APPEND UI_LIBRARIES X11) else (USESYSTEMLIBS) if (LINUX) - use_prebuilt_binary(gtk-atk-pango-glib) use_prebuilt_binary(fltk) endif (LINUX) @@ -48,21 +47,6 @@ else (USESYSTEMLIBS) set(UI_LIB_NAMES libfltk.a freetype - atk-1.0 - gdk-x11-2.0 - gdk_pixbuf-2.0 - glib-2.0 - gmodule-2.0 - gobject-2.0 - gthread-2.0 - gtk-x11-2.0 - pango-1.0 - pangoft2-1.0 - pangox-1.0 - #pangoxft-1.0 - gio-2.0 - pangocairo-1.0 - ffi ) foreach(libname ${UI_LIB_NAMES}) @@ -89,5 +73,5 @@ else (USESYSTEMLIBS) endif (USESYSTEMLIBS) if (LINUX) - add_definitions(-DLL_GTK=1 -DLL_X11=1 -DLL_FLTK=1) + add_definitions(-DLL_X11=1 -DLL_FLTK=1) endif (LINUX) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index de9da919a2..f79a3bdcf3 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -2316,7 +2316,25 @@ S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 typ return rtn; } - +BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) +{ + return FALSE; +} + +#else + +S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) +{ + LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; + return 0; +} + +BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) +{ + return (FALSE); +} +#endif // LL_GTK + /* @@ -2350,24 +2368,6 @@ LLSD LLWindowSDL::getNativeKeyData() return result; } - -BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) -{ - return FALSE; -} -#else -S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) -{ - LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; - return 0; -} - -BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) -{ - return (FALSE); -} -#endif // LL_GTK - #if LL_LINUX || LL_SOLARIS // extracted from spawnWebBrowser for clarity and to eliminate // compiler confusion regarding close(int fd) vs. LLWindow::close() diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 51b55f900b..ac4bbecc76 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -3,10 +3,14 @@ add_subdirectory(base) if (LINUX) + include(GLIB) #add_subdirectory(gstreamer010) - #add_subdirectory(libvlc) add_subdirectory(example) - add_subdirectory(gstreamer10) + if( GLIB_FOUND ) + add_subdirectory(gstreamer10) + else() + add_subdirectory(libvlc) + endif() add_subdirectory(cef) endif (LINUX) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 398d1da456..638a2fdb0a 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2501,8 +2501,8 @@ else (NOT ENABLE_MEDIA_PLUGINS) linux-crash-logger SLPlugin media_plugin_cef - media_plugin_gstreamer10 - #media_plugin_libvlc + #media_plugin_gstreamer10 + media_plugin_libvlc llcommon ) endif (NOT ENABLE_MEDIA_PLUGINS) diff --git a/indra/newview/desktopnotifierlinux.cpp b/indra/newview/desktopnotifierlinux.cpp index f3b2508409..be5cd722c7 100644 --- a/indra/newview/desktopnotifierlinux.cpp +++ b/indra/newview/desktopnotifierlinux.cpp @@ -38,8 +38,25 @@ #include +#ifdef LL_GLIB + #include +#else + +// ND: If we cannot use glib headers just stub out what'ns needed to compile. +// In theory that is enough to have a (leaky, see g_free) implementation. But to play it safe tryLoadLibnotify won't try to load any DSO, thus effectivly +// disabling the usage of libnotify. +typedef int gint; +typedef gint gboolean; +typedef char gchar; +struct GError; + +void g_free( void* ) +{ +} + +#endif typedef enum { @@ -65,7 +82,6 @@ typedef void (*pND_notify_notification_set_urgency) ( NotifyNotification *notifi // the unused one is always pushed first and qualifies just as dead weight. typedef NotifyNotification* (*pND_notify_notification_new) (const char *summary, const char *body, const char *icon, void*); - void* tryLoadLibnotify() { char const* aNames[] = { @@ -81,6 +97,7 @@ void* tryLoadLibnotify() void *pLibHandle(0); +#ifdef LL_GLIB for( int i = 0; !pLibHandle && aNames[i]; ++i ) { pLibHandle = dlopen( aNames[i], RTLD_NOW ); @@ -89,7 +106,8 @@ void* tryLoadLibnotify() else LL_INFOS( "DesktopNotifierLinux" ) << "Loaded " << aNames[i] << LL_ENDL; } - +#endif + return pLibHandle; }; @@ -298,10 +316,12 @@ void DesktopNotifierLinux::showNotification( const std::string& notification_tit { LL_INFOS( "DesktopNotifierLinux" ) << "Linux desktop notification type " << notification_type << " sent." << LL_ENDL; } +#if LL_GLIB else { LL_WARNS( "DesktopNotifierLinux" ) << "Linux desktop notification type " << notification_type << " FAILED to send, error was " << error->message << LL_ENDL; } +#endif } bool DesktopNotifierLinux::isUsable() diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 18f4b5ad26..ebaa64e2a1 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -2,7 +2,7 @@ * @file llappviewerlinux.cpp * @brief The LLAppViewerLinux class definitions * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ +1 * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * diff --git a/indra/newview/llappviewerlinux.h b/indra/newview/llappviewerlinux.h index ed71ff36fd..1196bc58c1 100644 --- a/indra/newview/llappviewerlinux.h +++ b/indra/newview/llappviewerlinux.h @@ -27,15 +27,17 @@ #ifndef LL_LLAPPVIEWERLINUX_H #define LL_LLAPPVIEWERLINUX_H +#ifdef LL_GLIB extern "C" { # include -} #if LL_DBUS_ENABLED -extern "C" { # include # include +#endif + } + #endif #ifndef LL_LLAPPVIEWER_H @@ -70,7 +72,7 @@ protected: virtual bool sendURLToOtherInstance(const std::string& url); }; -#if LL_DBUS_ENABLED +#if LL_DBUS_ENABLED && LL_GLIB typedef struct { GObject parent; diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c0ecf93fe6..63a0286075 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -2014,7 +2014,7 @@ class LinuxManifest(ViewerManifest): self.path("libopenal.so", "libopenal.so.1") # Install as versioned file in case it's missing from the 3p- and won't get copied below self.path("libopenal.so*") #self.path("libnotify.so.1.1.2", "libnotify.so.1") # LO - uncomment when testing libnotify(growl) on linux - self.path("libpangox-1.0.so*") + self.fs_try_path("libpangox-1.0.so*") # 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 @@ -2218,7 +2218,7 @@ class Linux_x86_64_Manifest(LinuxManifest): if self.is_packaging_viewer(): with self.prefix(src=os.path.join(pkgdir, 'lib', 'release'), dst="lib"): - self.path("libffi*.so*") + self.fs_try_path("libffi*.so*") # vivox 32-bit hack. # one has to extract libopenal.so from the 32-bit openal package, or official LL viewer, and rename it to libopenal32.so # and place it in the prebuilt lib/release directory From 3b17cc36d488eef9297efd7e3b060d3edd433bd5 Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 17 Aug 2020 00:30:14 +0200 Subject: [PATCH 106/125] Implement OSMessagBox and clipboard via SDL. --- indra/llwindow/llwindowsdl.cpp | 145 +++++++++++---------------------- 1 file changed, 48 insertions(+), 97 deletions(-) diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index f79a3bdcf3..1d842755b0 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1216,126 +1216,50 @@ void LLWindowSDL::flashIcon(F32 seconds) } -#if LL_GTK BOOL LLWindowSDL::isClipboardTextAvailable() { - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - return gtk_clipboard_wait_is_text_available(clipboard) ? - TRUE : FALSE; - } - return FALSE; // failure + return SDL_HasClipboardText()==SDL_TRUE ? TRUE: FALSE; } BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &text) { - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - gchar * const data = gtk_clipboard_wait_for_text(clipboard); - if (data) - { - text = LLWString(utf8str_to_wstring(data)); - g_free(data); - return TRUE; - } - } - return FALSE; // failure + char *pText = SDL_GetClipboardText(); + if( !pText ) + return FALSE; + + text = LLWString(utf8str_to_wstring(pText)); + SDL_free( pText ); + return TRUE; } BOOL LLWindowSDL::copyTextToClipboard(const LLWString &text) { - if (ll_try_gtk_init()) - { - const std::string utf8 = wstring_to_utf8str(text); - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); - return TRUE; - } - return FALSE; // failure -} + std::string utf8 = wstring_to_utf8str(text); + int res = SDL_SetClipboardText( utf8.c_str() ); + if( res != 0 ) + { + LL_WARNS() << "SDL_SetClipboardText failed: " << SDL_GetError() << LL_ENDL; + } + + return res == 0 ? TRUE : FALSE; +} BOOL LLWindowSDL::isPrimaryTextAvailable() { - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - return gtk_clipboard_wait_is_text_available(clipboard) ? - TRUE : FALSE; - } - return FALSE; // failure + return FALSE; // unsupported } BOOL LLWindowSDL::pasteTextFromPrimary(LLWString &text) { - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - gchar * const data = gtk_clipboard_wait_for_text(clipboard); - if (data) - { - text = LLWString(utf8str_to_wstring(data)); - g_free(data); - return TRUE; - } - } - return FALSE; // failure + return FALSE; // unsupported } BOOL LLWindowSDL::copyTextToPrimary(const LLWString &text) -{ - if (ll_try_gtk_init()) - { - const std::string utf8 = wstring_to_utf8str(text); - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); - return TRUE; - } - return FALSE; // failure -} - -#else - -BOOL LLWindowSDL::isClipboardTextAvailable() { return FALSE; // unsupported } -BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &dst) -{ - return FALSE; // unsupported -} - -BOOL LLWindowSDL::copyTextToClipboard(const LLWString &s) -{ - return FALSE; // unsupported -} - -BOOL LLWindowSDL::isPrimaryTextAvailable() -{ - return FALSE; // unsupported -} - -BOOL LLWindowSDL::pasteTextFromPrimary(LLWString &dst) -{ - return FALSE; // unsupported -} - -BOOL LLWindowSDL::copyTextToPrimary(const LLWString &s) -{ - return FALSE; // unsupported -} - -#endif // LL_GTK - LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_resolutions) { if (!mSupportedResolutions) @@ -2325,8 +2249,35 @@ BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) { - LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; - return 0; + SDL_MessageBoxData oData = { SDL_MESSAGEBOX_INFORMATION, nullptr, caption.c_str(), text.c_str(), 0, nullptr, nullptr }; + SDL_MessageBoxButtonData btnOk[] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }}; + SDL_MessageBoxButtonData btnOkCancel [] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_CANCEL, "Cancel"} }; + SDL_MessageBoxButtonData btnYesNo[] = { {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_YES, "Yes" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_NO, "No"} }; + + switch (type) + { + default: + case OSMB_OK: + oData.flags = SDL_MESSAGEBOX_WARNING; + oData.buttons = btnOk; + oData.numbuttons = 1; + break; + case OSMB_OKCANCEL: + oData.flags = SDL_MESSAGEBOX_INFORMATION; + oData.buttons = btnOkCancel; + oData.numbuttons = 2; + break; + case OSMB_YESNO: + oData.flags = SDL_MESSAGEBOX_INFORMATION; + oData.buttons = btnYesNo; + oData.numbuttons = 2; + break; + } + + int btn{0}; + if( 0 == SDL_ShowMessageBox( &oData, &btn ) ) + return btn; + return OSBTN_CANCEL; } BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) From d6997696014b5f81cd6d7a110455ac2b7ca9701a Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 17 Aug 2020 21:43:43 +0200 Subject: [PATCH 107/125] Add glib package and use this instead of the old glib+gtk+atk+... --- autobuild.xml | 28 +++++++++++++++++++ indra/cmake/00-Common.cmake | 11 -------- indra/cmake/DBusGlib.cmake | 9 ++---- indra/cmake/GLIB.cmake | 11 ++++++++ indra/cmake/GStreamer010Plugin.cmake | 9 +++--- indra/cmake/GStreamer10Plugin.cmake | 9 +++--- indra/media_plugins/CMakeLists.txt | 3 +- .../media_plugins/gstreamer10/CMakeLists.txt | 1 + indra/newview/CMakeLists.txt | 2 +- 9 files changed, 55 insertions(+), 28 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index e77bc889e5..ba2e20ca20 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3,6 +3,34 @@ installables + glib + + copyright + Copyright (C) glib project + license + LGPL + license_file + LICENSES/glib.txt + name + glib + platforms + + linux64 + + archive + + hash + 9c93ba8b8af97fc8379f77de77e1540a + url + http://3p.firestormviewer.org/glib-2.48.0.202301938-linux64-202301938.tar.bz2 + + name + linux + + + version + 2.48.0 + fltk copyright diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 34aed62ba0..f7b68aa76d 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -270,17 +270,6 @@ if (USESYSTEMLIBS) if (LINUX AND ADDRESS_SIZE EQUAL 32) add_definitions(-march=pentiumpro) endif (LINUX AND ADDRESS_SIZE EQUAL 32) - -else (USESYSTEMLIBS) - set(${ARCH}_linux_INCLUDES - atk-1.0 - cairo - freetype - glib-2.0 - gstreamer-0.10 - gtk-2.0 - pango-1.0 - ) endif (USESYSTEMLIBS) endif(NOT DEFINED ${CMAKE_CURRENT_LIST_FILE}_INCLUDED) diff --git a/indra/cmake/DBusGlib.cmake b/indra/cmake/DBusGlib.cmake index 0010c48c52..d0f6c221cc 100644 --- a/indra/cmake/DBusGlib.cmake +++ b/indra/cmake/DBusGlib.cmake @@ -12,14 +12,11 @@ elseif (LINUX) use_prebuilt_binary(dbus_glib) set(DBUSGLIB_FOUND ON FORCE BOOL) set(DBUSGLIB_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include/dbus - ) + ${GLIB_INCLUDE_DIRS} + ${LIBS_PREBUILT_DIR}/include/dbus + ) # We don't need to explicitly link against dbus-glib itself, because # the viewer probes for the system's copy at runtime. - set(DBUSGLIB_LIBRARIES - gobject-2.0 - glib-2.0 - ) endif (USESYSTEMLIBS) if (DBUSGLIB_FOUND) diff --git a/indra/cmake/GLIB.cmake b/indra/cmake/GLIB.cmake index e69de29bb2..0024cb26ac 100644 --- a/indra/cmake/GLIB.cmake +++ b/indra/cmake/GLIB.cmake @@ -0,0 +1,11 @@ + +include(Prebuilt) + +if( LINUX ) + use_prebuilt_binary(glib) + set(GLIB_FOUND ON CACHE BOOL "Build against glib 2") + set(GLIB_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/glib-2.0 ${LIBS_PREBUILT_DIR}/lib/release/glib-2.0/include ) + set(GLIB_LIBRARIES libgobject-2.0.a libglib-2.0.a libffi.a libpcre.a) + + add_definitions(-DLL_GLIB=1) +endif() diff --git a/indra/cmake/GStreamer010Plugin.cmake b/indra/cmake/GStreamer010Plugin.cmake index 3fbc40ef8f..2a41cc5314 100644 --- a/indra/cmake/GStreamer010Plugin.cmake +++ b/indra/cmake/GStreamer010Plugin.cmake @@ -1,5 +1,6 @@ # -*- cmake -*- include(Prebuilt) +include(GLIB) if (USESYSTEMLIBS) include(FindPkgConfig) @@ -13,10 +14,10 @@ elseif (LINUX) set(GSTREAMER010_FOUND ON FORCE BOOL) set(GSTREAMER010_PLUGINS_BASE_FOUND ON FORCE BOOL) set(GSTREAMER010_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include/gstreamer-0.10 - ${LIBS_PREBUILT_DIR}/include/glib-2.0 - ${LIBS_PREBUILT_DIR}/include/libxml2 - ) + ${GLIB_INCLUDE_DIRS} + ${LIBS_PREBUILT_DIR}/include/gstreamer-0.10 + ${LIBS_PREBUILT_DIR}/include/libxml2 + ) # We don't need to explicitly link against gstreamer itself, because # LLMediaImplGStreamer probes for the system's copy at runtime. set(GSTREAMER010_LIBRARIES diff --git a/indra/cmake/GStreamer10Plugin.cmake b/indra/cmake/GStreamer10Plugin.cmake index 4f9d255436..ded45da610 100644 --- a/indra/cmake/GStreamer10Plugin.cmake +++ b/indra/cmake/GStreamer10Plugin.cmake @@ -1,5 +1,6 @@ # -*- cmake -*- include(Prebuilt) +include(GLIB) if (USESYSTEMLIBS) include(FindPkgConfig) @@ -12,10 +13,10 @@ elseif (LINUX OR WINDOWS) set(GSTREAMER10_FOUND ON FORCE BOOL) set(GSTREAMER10_PLUGINS_BASE_FOUND ON FORCE BOOL) set(GSTREAMER10_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0 - ${LIBS_PREBUILT_DIR}/include/glib-2.0 - ${LIBS_PREBUILT_DIR}/include/libxml2 - ) + ${GLIB_INCLUDE_DIRS} + ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0 + ${LIBS_PREBUILT_DIR}/include/libxml2 + ) # We don't need to explicitly link against gstreamer itself, because # LLMediaImplGStreamer probes for the system's copy at runtime. set(GSTREAMER10_LIBRARIES) diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index ac4bbecc76..b12382c814 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -8,9 +8,8 @@ if (LINUX) add_subdirectory(example) if( GLIB_FOUND ) add_subdirectory(gstreamer10) - else() - add_subdirectory(libvlc) endif() + add_subdirectory(libvlc) add_subdirectory(cef) endif (LINUX) diff --git a/indra/media_plugins/gstreamer10/CMakeLists.txt b/indra/media_plugins/gstreamer10/CMakeLists.txt index 3a98df5da1..730a2c27fb 100644 --- a/indra/media_plugins/gstreamer10/CMakeLists.txt +++ b/indra/media_plugins/gstreamer10/CMakeLists.txt @@ -13,6 +13,7 @@ include(Linking) include(PluginAPI) include(MediaPluginBase) include(OpenGL) +include(GLIB) include(GStreamer10Plugin) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 638a2fdb0a..f9e63ea261 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2438,7 +2438,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${BOOST_WAVE_LIBRARY} #FS specific ${BOOST_THREAD_LIBRARY} #FS specific ${BOOST_CONTEXT_LIBRARY} - ${DBUSGLIB_LIBRARIES} + ${GLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${FMODWRAPPER_LIBRARY} # must come after LLAudio ${OPENAL_LIBRARIES} From 1a9a94c291446a3543ba0cf52afe43ce615ef8d1 Mon Sep 17 00:00:00 2001 From: Nicky Date: Sun, 28 Feb 2021 07:28:29 +0100 Subject: [PATCH 108/125] Remove VLC --- indra/media_plugins/CMakeLists.txt | 4 +++- indra/newview/CMakeLists.txt | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 10560cb8ba..f1b617baa4 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -8,8 +8,10 @@ if (LINUX) #add_subdirectory(example) if( GLIB_FOUND ) add_subdirectory(gstreamer10) + else() + MESSAGE( "Building without gstreamer (missing GLIB)" ) endif() - add_subdirectory(libvlc) + #add_subdirectory(libvlc) add_subdirectory(cef) endif (LINUX) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 26d1a4492c..d8e99560c1 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2554,8 +2554,8 @@ else (NOT ENABLE_MEDIA_PLUGINS) linux-crash-logger SLPlugin media_plugin_cef - #media_plugin_gstreamer10 - media_plugin_libvlc + media_plugin_gstreamer10 + #media_plugin_libvlc llcommon ) endif (NOT ENABLE_MEDIA_PLUGINS) From f228b46c4f3f9e053c54f9d294bc10f11e132807 Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Wed, 3 Mar 2021 23:49:22 +0100 Subject: [PATCH 109/125] Updated Polish translation --- .../newview/skins/default/xui/pl/panel_preferences_sound.xml | 2 +- indra/newview/skins/default/xui/pl/strings.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/skins/default/xui/pl/panel_preferences_sound.xml b/indra/newview/skins/default/xui/pl/panel_preferences_sound.xml index cf8599bac1..8eb9949cb8 100644 --- a/indra/newview/skins/default/xui/pl/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/pl/panel_preferences_sound.xml @@ -105,7 +105,7 @@ - + diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index 0e5e62fdba..623c1aac44 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -72,8 +72,8 @@ Zaawansowane oświetlenie (Advanced Lighting Model): [ALMSTATUS] Pamięć tekstur (Texture memory): Dynamiczna ([TEXTUREMEMORYMIN] MB min / [TEXTUREMEMORYCACHERESERVE]% Cache / [TEXTUREMEMORYGPURESERVE]% VRAM) - - Czas utworzenia VFS (cache) w UTC: [VFS_DATE] + + Pamięć podręczna dysku (disk cache): [DISK_CACHE_INFO] Tryb obrazu HiDPI: [HIDPI] From c69f7e6f9a1cbf921c8288e2f6c7b40f91431575 Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Wed, 3 Mar 2021 23:54:17 +0100 Subject: [PATCH 110/125] FIRE-30803 Russian translation update, by Romka Swallowtail --- indra/newview/skins/default/xui/ru/panel_preferences_sound.xml | 2 ++ indra/newview/skins/default/xui/ru/strings.xml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/indra/newview/skins/default/xui/ru/panel_preferences_sound.xml b/indra/newview/skins/default/xui/ru/panel_preferences_sound.xml index a9396fa8f9..cd87ce4302 100644 --- a/indra/newview/skins/default/xui/ru/panel_preferences_sound.xml +++ b/indra/newview/skins/default/xui/ru/panel_preferences_sound.xml @@ -100,6 +100,8 @@ + + diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index ece8d1e5fc..b66ec18c51 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -92,6 +92,9 @@ SLURL: <nolink>[SLURL]</nolink> Память текстур: динамическая ([TEXTUREMEMORYMIN] МБ мин. / [TEXTUREMEMORYCACHERESERVE]% Кэш / [TEXTUREMEMORYGPURESERVE]% VRAM) + + Кэш диска: [DISK_CACHE_INFO] + Режим отображения HiDPI: [HIDPI] From abb6895896da5029ae53c4ed12ab8b03ca96f9ce Mon Sep 17 00:00:00 2001 From: Ansariel Date: Thu, 4 Mar 2021 11:46:12 +0100 Subject: [PATCH 111/125] Properly clean up landmark loaded callbacks in error situations; inspired by Chorazin Allen --- indra/newview/lllandmarklist.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index fa121e3147..71c74f88db 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -161,6 +161,7 @@ void LLLandmarkList::processGetAssetReply( gLandmarkList.makeCallbacks(uuid); } } + else gLandmarkList.mLoadedCallbackMap.erase(uuid); // Clean up callback map } else { @@ -179,6 +180,7 @@ void LLLandmarkList::processGetAssetReply( gLandmarkList.mBadList.insert(uuid); gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests // todo: this should clean mLoadedCallbackMap! + gLandmarkList.mLoadedCallbackMap.erase(uuid); // Clean up callback map } // getAssetData can fire callback immediately, causing @@ -222,6 +224,7 @@ void LLLandmarkList::onRegionHandle(const LLUUID& landmark_id) if (!landmark) { LL_WARNS() << "Got region handle but the landmark not found." << LL_ENDL; + mLoadedCallbackMap.erase(landmark_id); // Clean up callback map return; } @@ -231,6 +234,7 @@ void LLLandmarkList::onRegionHandle(const LLUUID& landmark_id) if (!landmark->getGlobalPos(pos)) { LL_WARNS() << "Got region handle but the landmark global position is still unknown." << LL_ENDL; + mLoadedCallbackMap.erase(landmark_id); // Clean up callback map return; } From 064f0516ded7e37d5e8cde49ddec9442350cb087 Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 4 Mar 2021 22:06:35 +0100 Subject: [PATCH 112/125] Do not save linux gdb symbols. Doing this just takes up a lot of times and wasn't never really used. --- indra/newview/fs_viewer_manifest.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/indra/newview/fs_viewer_manifest.py b/indra/newview/fs_viewer_manifest.py index 933c43be6a..5bea417026 100644 --- a/indra/newview/fs_viewer_manifest.py +++ b/indra/newview/fs_viewer_manifest.py @@ -78,31 +78,6 @@ class FSViewerManifest: # New Method, for reading cross platform stack traces on a linux/mac host print( "Packaging symbols" ) - """ - Copy symbols into a .debug subdir, then add a debug link into the stripped exe. - This allows gdb to automatically pick up symbols, even when they are not embedded. - Maybe at some point it is worth to extract the build-id (readelf -n) and store the - symbols in a .symbol server in form of xy/za*.debug where xyza* is the build-id. - """ - - fileBin = os.path.join( self.get_dst_prefix(), "bin", "do-not-directly-run-firestorm-bin" ) - fileSource = os.path.join( self.get_dst_prefix(), "..", "firestorm-bin" ) - - debugName = "firestorm-bin.debug" - debugIndexName = "firestorm-bin.debug.gdb-index" - debugDir = os.path.join( self.get_dst_prefix(), "bin", ".debug" ) - debugFile = os.path.join( self.get_dst_prefix(), "bin", ".debug", debugName ) - debugIndexFile = os.path.join( self.get_dst_prefix(), "bin", ".debug", debugIndexName ) - if not os.path.exists( debugDir ): - os.makedirs( debugDir ) - - self.run_command_shell( "objcopy %s %s" % (fileSource, debugFile) ) - - self.run_command_shell( "gdb -batch -ex \"save gdb-index %s\" %s" % (debugDir, debugFile ) ) - self.run_command_shell( "objcopy --add-section .gdb_index=%s --set-section-flags .gdb_index=readonly %s %s" % (debugIndexFile, debugFile, debugFile) ) - - self.run_command_shell( "cd %s && objcopy --add-gnu-debuglink=%s %s" % (debugDir, debugName, fileBin) ) - self.fs_save_symbols("linux") def fs_linux_tar_excludes(self): From c61360d5f7edf11deddd8ba6a84b75b14a9796cd Mon Sep 17 00:00:00 2001 From: Nicky Date: Thu, 4 Mar 2021 22:08:09 +0100 Subject: [PATCH 113/125] Remove crashhost. We're using bugsplat now and whatever isn't send there gets ignored. --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/app_settings/settings_crash_behavior.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 99d4ae4a83..4c4cc72d53 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1586,7 +1586,7 @@ Type String Value - http://crashlogs.phoenixviewer.com/upload_llsd + Backup 0 diff --git a/indra/newview/app_settings/settings_crash_behavior.xml b/indra/newview/app_settings/settings_crash_behavior.xml index a22d9a5452..36e9bcfa08 100644 --- a/indra/newview/app_settings/settings_crash_behavior.xml +++ b/indra/newview/app_settings/settings_crash_behavior.xml @@ -42,7 +42,7 @@ Type String Value - http://crashlogs.phoenixviewer.com/upload_llsd + From 104a8600946be01e2de44d10ad069ba854272d1f Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 5 Mar 2021 12:59:27 +0100 Subject: [PATCH 114/125] Fix even more broken asset caching... --- indra/llfilesystem/llfilesystem.cpp | 27 +++++++++++++++++++++++ indra/newview/llmeshrepository.cpp | 34 ++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index 1789a2dbb9..2e2a97826c 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -209,9 +209,36 @@ BOOL LLFileSystem::write(const U8* buffer, S32 bytes) { ofs.write((const char*)buffer, bytes); + mPosition = ofs.tellp(); // Fix asset caching + success = TRUE; } } + // Fix asset caching + else if (mMode == READ_WRITE) + { + // Don't truncate if file already exists + llofstream ofs(filename, std::ios::in | std::ios::binary); + if (ofs) + { + ofs.seekp(mPosition, std::ios::beg); + ofs.write((const char*)buffer, bytes); + mPosition += bytes; + success = TRUE; + } + else + { + // File doesn't exist - open in write mode + ofs.open(filename, std::ios::binary); + if (ofs.is_open()) + { + ofs.write((const char*)buffer, bytes); + mPosition += bytes; + success = TRUE; + } + } + } + // else { llofstream ofs(filename, std::ios::binary); diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index a594b9583c..fc9b5174d7 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -3352,13 +3352,29 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b // only allocate as much space in the cache as is needed for the local cache data_size = llmin(data_size, bytes); - LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // Fix asset caching + //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); if (file.getMaxSize() >= bytes) { LLMeshRepository::sCacheBytesWritten += data_size; ++LLMeshRepository::sCacheWrites; file.write(data, data_size); + + // Fix asset caching + S32 remaining = bytes - file.tell(); + if (remaining > 0) + { + U8* block = new(std::nothrow) U8[remaining]; + if (block) + { + memset(block, 0, remaining); + file.write(block, remaining); + delete[] block; + } + } + // } } else @@ -3411,7 +3427,9 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body if (result == MESH_OK) { // good fetch from sim, write to cache - LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + // Fix asset caching + //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3475,7 +3493,9 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) { // good fetch from sim, write to cache - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3523,7 +3543,9 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) { // good fetch from sim, write to cache - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3570,7 +3592,9 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S3 && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) { // good fetch from sim, write to cache for caching - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; From 9d820f140ce4a4b416438b186ca1f4f511fd5ee0 Mon Sep 17 00:00:00 2001 From: Nicky Date: Fri, 5 Mar 2021 23:17:42 +0100 Subject: [PATCH 115/125] Add option to keep frame pointer --- indra/cmake/00-Common.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 1622735423..8cf08950a7 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -189,6 +189,10 @@ if (LINUX) -pthread ) + if( FS_KEEP_FRAMEPOINTER ) + add_compile_options(-fno-omit-frame-pointer) + endif() + # force this platform to accept TOS via external browser No, do not. # add_definitions(-DEXTERNAL_TOS) From afd83c364d91d81d6c244402ac4f3af8168604c4 Mon Sep 17 00:00:00 2001 From: Liny Date: Sun, 7 Mar 2021 17:01:08 -0800 Subject: [PATCH 116/125] Part 1 of fixing mac builds: copied changes from llkeyboardwin32.cpp over to llkeyboardmacosx.cpp --- indra/llwindow/llkeyboardmacosx.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index f7dc734a3e..6eba84af5a 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -133,8 +133,10 @@ LLKeyboardMacOSX::LLKeyboardMacOSX() mTranslateKeyMap[0x7e] = KEY_UP; // Build inverse map - std::map::iterator iter; - for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + // Change to U32 for SDL2 + //std::map::iterator iter; + //for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + for (autoiter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } @@ -154,7 +156,9 @@ LLKeyboardMacOSX::LLKeyboardMacOSX() mTranslateNumpadMap[0x4c] = KEY_PAD_RETURN; // keypad enter // Build inverse numpad map - for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + // Change to U32 for SDL2 + //for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + for (auto iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) { mInvTranslateNumpadMap[iter->second] = iter->first; } From c83e740ef94e16ba85574454f3138905edecb029 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Mon, 8 Mar 2021 13:56:16 +0000 Subject: [PATCH 117/125] Revert "Merge branch 'master' of https://bitbucket.org/lindenlab/viewer into DRTVWR-519" This reverts commit e61f485a04dc8c8ac6bcf6a24848359092884d14, reversing changes made to 00c47d079f7e958e473ed4083a7f7691fa02dcd5. --- README.md | 1 - indra/CMakeLists.txt | 2 +- indra/cmake/00-Common.cmake | 3 +- indra/cmake/CMakeLists.txt | 2 +- indra/cmake/LLFileSystem.cmake | 7 - indra/cmake/LLVFS.cmake | 7 + .../llimage_libtest/CMakeLists.txt | 6 +- .../llui_libtest/CMakeLists.txt | 4 +- indra/linux_crash_logger/CMakeLists.txt | 7 +- indra/llappearance/CMakeLists.txt | 20 +- indra/llappearance/lltexlayer.cpp | 2 + indra/llaudio/CMakeLists.txt | 6 +- indra/llaudio/llaudiodecodemgr.cpp | 58 +- indra/llaudio/llaudiodecodemgr.h | 1 + indra/llaudio/llaudioengine.cpp | 15 +- indra/llaudio/llaudioengine.h | 13 +- indra/llcharacter/CMakeLists.txt | 18 +- indra/llcharacter/llkeyframefallmotion.cpp | 5 - indra/llcharacter/llkeyframemotion.cpp | 21 +- indra/llcharacter/llkeyframemotion.h | 12 +- indra/llcommon/llerror.cpp | 61 +- indra/llcrashlogger/CMakeLists.txt | 4 +- indra/llcrashlogger/llcrashlock.h | 2 +- indra/llfilesystem/lldiskcache.cpp | 327 --- indra/llfilesystem/lldiskcache.h | 183 -- indra/llfilesystem/llfilesystem.cpp | 283 --- indra/llfilesystem/llfilesystem.h | 78 - indra/llimage/CMakeLists.txt | 6 +- indra/llimage/llimage.cpp | 9 + indra/llinventory/CMakeLists.txt | 4 +- indra/llmessage/CMakeLists.txt | 12 +- indra/llmessage/llassetstorage.cpp | 97 +- indra/llmessage/llassetstorage.h | 26 +- indra/llmessage/llcorehttputil.cpp | 4 +- indra/llmessage/llextendedstatus.h | 14 +- indra/llmessage/lltransfersourceasset.cpp | 8 +- indra/llmessage/lltransfersourceasset.h | 4 +- indra/llmessage/lltransfertargetvfile.cpp | 9 +- indra/llmessage/lltransfertargetvfile.h | 4 +- indra/llmessage/llxfer_vfile.cpp | 57 +- indra/llmessage/llxfer_vfile.h | 12 +- indra/llmessage/llxfermanager.cpp | 35 +- indra/llmessage/llxfermanager.h | 12 +- indra/llrender/CMakeLists.txt | 12 +- indra/llui/CMakeLists.txt | 6 +- indra/llui/llviewereventrecorder.h | 1 + indra/{llfilesystem => llvfs}/CMakeLists.txt | 57 +- indra/{llfilesystem => llvfs}/lldir.cpp | 0 indra/{llfilesystem => llvfs}/lldir.h | 0 indra/{llfilesystem => llvfs}/lldir_linux.cpp | 0 indra/{llfilesystem => llvfs}/lldir_linux.h | 0 indra/{llfilesystem => llvfs}/lldir_mac.cpp | 2 +- indra/{llfilesystem => llvfs}/lldir_mac.h | 0 .../{llfilesystem => llvfs}/lldir_solaris.cpp | 0 indra/{llfilesystem => llvfs}/lldir_solaris.h | 0 indra/{llfilesystem => llvfs}/lldir_win32.cpp | 0 indra/{llfilesystem => llvfs}/lldir_win32.h | 0 indra/{llfilesystem => llvfs}/lldirguard.h | 0 .../{llfilesystem => llvfs}/lldiriterator.cpp | 0 indra/{llfilesystem => llvfs}/lldiriterator.h | 0 indra/{llfilesystem => llvfs}/lllfsthread.cpp | 0 indra/{llfilesystem => llvfs}/lllfsthread.h | 0 indra/llvfs/llpidlock.cpp | 276 ++ indra/llvfs/llpidlock.h | 60 + indra/llvfs/llvfile.cpp | 437 ++++ indra/llvfs/llvfile.h | 90 + indra/llvfs/llvfs.cpp | 2220 +++++++++++++++++ indra/llvfs/llvfs.h | 183 ++ .../lldir_utils_objc.h => llvfs/llvfs_objc.h} | 12 +- .../llvfs_objc.mm} | 8 +- indra/llvfs/llvfsthread.cpp | 300 +++ indra/llvfs/llvfsthread.h | 140 ++ .../tests/lldir_test.cpp | 0 .../tests/lldiriterator_test.cpp | 0 indra/llwindow/CMakeLists.txt | 8 +- indra/llxml/CMakeLists.txt | 6 +- indra/mac_crash_logger/CMakeLists.txt | 7 +- indra/mac_crash_logger/mac_crash_logger.cpp | 1 + indra/newview/CMakeLists.txt | 10 +- indra/newview/app_settings/settings.xml | 81 +- indra/newview/app_settings/static_data.db2 | Bin 0 -> 576578 bytes indra/newview/app_settings/static_index.db2 | Bin 0 -> 9894 bytes indra/newview/llappviewer.cpp | 293 ++- indra/newview/llappviewer.h | 9 +- indra/newview/llappviewerwin32.cpp | 2 +- indra/newview/llcompilequeue.cpp | 12 +- indra/newview/llcompilequeue.h | 2 +- indra/newview/llfilepicker.h | 2 +- indra/newview/llfloaterauction.cpp | 11 +- indra/newview/llfloaterbvhpreview.cpp | 5 +- indra/newview/llfloatermodelpreview.cpp | 1 - indra/newview/llfloaterpreference.h | 2 +- indra/newview/llfloaterregioninfo.cpp | 11 +- indra/newview/llfloaterregioninfo.h | 4 +- indra/newview/llfloaterreporter.cpp | 12 +- indra/newview/llfloatertos.cpp | 2 +- indra/newview/llfloatertos.h | 1 + indra/newview/llgesturemgr.cpp | 20 +- indra/newview/llgesturemgr.h | 11 +- indra/newview/lllandmarklist.cpp | 5 +- indra/newview/lllandmarklist.h | 1 + indra/newview/llmeshrepository.cpp | 73 +- indra/newview/llmeshrepository.h | 1 + indra/newview/lloutfitgallery.cpp | 2 +- indra/newview/lloutfitgallery.h | 1 + indra/newview/llpostcard.cpp | 3 +- indra/newview/llpreviewgesture.cpp | 16 +- indra/newview/llpreviewgesture.h | 4 +- indra/newview/llpreviewnotecard.cpp | 16 +- indra/newview/llpreviewnotecard.h | 3 +- indra/newview/llpreviewscript.cpp | 30 +- indra/newview/llpreviewscript.h | 11 +- indra/newview/llsettingsvo.cpp | 10 +- indra/newview/llsettingsvo.h | 3 +- indra/newview/llsnapshotlivepreview.cpp | 6 +- indra/newview/llstartup.cpp | 16 +- indra/newview/lltexturecache.cpp | 46 +- indra/newview/llviewerassetstorage.cpp | 53 +- indra/newview/llviewerassetstorage.h | 8 +- indra/newview/llviewerassetupload.cpp | 23 +- indra/newview/llviewerassetupload.h | 2 +- .../newview/llviewermedia_streamingaudio.cpp | 2 + indra/newview/llviewermenufile.cpp | 2 + indra/newview/llviewermessage.cpp | 12 +- indra/newview/llviewermessage.h | 4 +- indra/newview/llviewerprecompiledheaders.h | 1 + indra/newview/llviewerstats.cpp | 3 + indra/newview/llviewerstats.h | 1 + indra/newview/llviewertexlayer.cpp | 2 + indra/newview/llviewertexture.cpp | 2 + indra/newview/llviewertexture.h | 2 +- indra/newview/llviewertexturelist.cpp | 4 +- indra/newview/llviewerwearable.cpp | 1 + indra/newview/llvoavatar.cpp | 1 + indra/newview/llvovolume.h | 7 +- .../skins/default/xui/da/floater_stats.xml | 1 + .../newview/skins/default/xui/da/strings.xml | 3 + .../xui/de/floater_scene_load_stats.xml | 1 + .../skins/default/xui/de/floater_stats.xml | 1 + .../newview/skins/default/xui/de/strings.xml | 4 + .../xui/en/floater_scene_load_stats.xml | 6 + .../skins/default/xui/en/floater_stats.xml | 4 + .../newview/skins/default/xui/en/strings.xml | 5 +- .../xui/es/floater_scene_load_stats.xml | 1 + .../skins/default/xui/es/floater_stats.xml | 1 + .../newview/skins/default/xui/es/strings.xml | 4 + .../xui/fr/floater_scene_load_stats.xml | 1 + .../skins/default/xui/fr/floater_stats.xml | 1 + .../newview/skins/default/xui/fr/strings.xml | 4 + .../xui/it/floater_scene_load_stats.xml | 1 + .../skins/default/xui/it/floater_stats.xml | 1 + .../newview/skins/default/xui/it/strings.xml | 4 + .../xui/ja/floater_scene_load_stats.xml | 1 + .../skins/default/xui/ja/floater_stats.xml | 1 + .../newview/skins/default/xui/ja/strings.xml | 4 + .../xui/pl/floater_scene_load_stats.xml | 1 + .../skins/default/xui/pl/floater_stats.xml | 1 + .../newview/skins/default/xui/pl/strings.xml | 3 + .../xui/pt/floater_scene_load_stats.xml | 1 + .../skins/default/xui/pt/floater_stats.xml | 1 + .../newview/skins/default/xui/pt/strings.xml | 4 + .../xui/ru/floater_scene_load_stats.xml | 1 + .../skins/default/xui/ru/floater_stats.xml | 1 + .../newview/skins/default/xui/ru/strings.xml | 4 + .../xui/tr/floater_scene_load_stats.xml | 1 + .../skins/default/xui/tr/floater_stats.xml | 1 + .../newview/skins/default/xui/tr/strings.xml | 4 + .../xui/zh/floater_scene_load_stats.xml | 1 + .../skins/default/xui/zh/floater_stats.xml | 1 + .../newview/skins/default/xui/zh/strings.xml | 4 + indra/newview/viewer_manifest.py | 1 + indra/test/CMakeLists.txt | 7 +- indra/win_crash_logger/CMakeLists.txt | 6 +- 173 files changed, 4792 insertions(+), 1455 deletions(-) delete mode 100644 indra/cmake/LLFileSystem.cmake create mode 100644 indra/cmake/LLVFS.cmake delete mode 100644 indra/llfilesystem/lldiskcache.cpp delete mode 100644 indra/llfilesystem/lldiskcache.h delete mode 100644 indra/llfilesystem/llfilesystem.cpp delete mode 100644 indra/llfilesystem/llfilesystem.h rename indra/{llfilesystem => llvfs}/CMakeLists.txt (51%) rename indra/{llfilesystem => llvfs}/lldir.cpp (100%) rename indra/{llfilesystem => llvfs}/lldir.h (100%) rename indra/{llfilesystem => llvfs}/lldir_linux.cpp (100%) rename indra/{llfilesystem => llvfs}/lldir_linux.h (100%) rename indra/{llfilesystem => llvfs}/lldir_mac.cpp (99%) rename indra/{llfilesystem => llvfs}/lldir_mac.h (100%) rename indra/{llfilesystem => llvfs}/lldir_solaris.cpp (100%) rename indra/{llfilesystem => llvfs}/lldir_solaris.h (100%) rename indra/{llfilesystem => llvfs}/lldir_win32.cpp (100%) rename indra/{llfilesystem => llvfs}/lldir_win32.h (100%) rename indra/{llfilesystem => llvfs}/lldirguard.h (100%) rename indra/{llfilesystem => llvfs}/lldiriterator.cpp (100%) rename indra/{llfilesystem => llvfs}/lldiriterator.h (100%) rename indra/{llfilesystem => llvfs}/lllfsthread.cpp (100%) rename indra/{llfilesystem => llvfs}/lllfsthread.h (100%) create mode 100644 indra/llvfs/llpidlock.cpp create mode 100644 indra/llvfs/llpidlock.h create mode 100644 indra/llvfs/llvfile.cpp create mode 100644 indra/llvfs/llvfile.h create mode 100644 indra/llvfs/llvfs.cpp create mode 100644 indra/llvfs/llvfs.h rename indra/{llfilesystem/lldir_utils_objc.h => llvfs/llvfs_objc.h} (85%) rename indra/{llfilesystem/lldir_utils_objc.mm => llvfs/llvfs_objc.mm} (95%) create mode 100644 indra/llvfs/llvfsthread.cpp create mode 100644 indra/llvfs/llvfsthread.h rename indra/{llfilesystem => llvfs}/tests/lldir_test.cpp (100%) rename indra/{llfilesystem => llvfs}/tests/lldiriterator_test.cpp (100%) create mode 100644 indra/newview/app_settings/static_data.db2 create mode 100644 indra/newview/app_settings/static_index.db2 diff --git a/README.md b/README.md index 729c0ae368..e4078770f3 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,3 @@ To download the current default version, visit [the download page](https://secondlife.com/support/downloads). For even newer versions try [the Alternate Viewers page](https://wiki.secondlife.com/wiki/Linden_Lab_Official:Alternate_Viewers) - diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 4b39bfe332..53e5d7b6a5 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -40,7 +40,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llmath) add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) -add_subdirectory(${LIBS_OPEN_PREFIX}llfilesystem) +add_subdirectory(${LIBS_OPEN_PREFIX}llvfs) add_subdirectory(${LIBS_OPEN_PREFIX}llwindow) add_subdirectory(${LIBS_OPEN_PREFIX}llxml) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index f4071793d5..8aea50e02b 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -66,10 +66,11 @@ if (WINDOWS) # CP changed to only append the flag for 32bit builds - on 64bit builds, # locally at least, the build output is spammed with 1000s of 'D9002' # warnings about this switch being ignored. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") if( ADDRESS_SIZE EQUAL 32 ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /p:PreferredToolArchitecture=x64") endif() + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo" CACHE STRING "C++ compiler release-with-debug options" FORCE) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 352dfc0641..a17e37cd32 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -69,7 +69,7 @@ set(cmake_SOURCE_FILES LLSharedLibs.cmake LLTestCommand.cmake LLUI.cmake - LLFileSystem.cmake + LLVFS.cmake LLWindow.cmake LLXML.cmake Linking.cmake diff --git a/indra/cmake/LLFileSystem.cmake b/indra/cmake/LLFileSystem.cmake deleted file mode 100644 index 2e6c42c30c..0000000000 --- a/indra/cmake/LLFileSystem.cmake +++ /dev/null @@ -1,7 +0,0 @@ -# -*- cmake -*- - -set(LLFILESYSTEM_INCLUDE_DIRS - ${LIBS_OPEN_DIR}/llfilesystem - ) - -set(LLFILESYSTEM_LIBRARIES llfilesystem) diff --git a/indra/cmake/LLVFS.cmake b/indra/cmake/LLVFS.cmake new file mode 100644 index 0000000000..0fe87cdea6 --- /dev/null +++ b/indra/cmake/LLVFS.cmake @@ -0,0 +1,7 @@ +# -*- cmake -*- + +set(LLVFS_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/llvfs + ) + +set(LLVFS_LIBRARIES llvfs) diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt index bd59f57e49..5787d4d600 100644 --- a/indra/integration_tests/llimage_libtest/CMakeLists.txt +++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt @@ -10,11 +10,11 @@ include(LLImage) include(LLMath) include(LLImageJ2COJ) include(LLKDU) -include(LLFileSystem) +include(LLVFS) include_directories( ${LLCOMMON_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ) @@ -66,7 +66,7 @@ endif (DARWIN) target_link_libraries(llimage_libtest ${LEGACY_STDIO_LIBS} ${LLCOMMON_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLKDU_LIBRARIES} diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt index d7706e73b2..1cec660eb0 100644 --- a/indra/integration_tests/llui_libtest/CMakeLists.txt +++ b/indra/integration_tests/llui_libtest/CMakeLists.txt @@ -16,7 +16,7 @@ include(LLMessage) include(LLRender) include(LLWindow) include(LLUI) -include(LLFileSystem) +include(LLVFS) # ugh, needed for LLDir include(LLXML) include(Hunspell) include(Linking) @@ -29,7 +29,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLUI_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LIBS_PREBUILD_DIR}/include/hunspell diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index aa82ed12cc..d789c850a0 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -9,7 +9,7 @@ include(LLCommon) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLXML) include(Linking) include(UI) @@ -21,7 +21,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCRASHLOGGER_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS} ) @@ -62,9 +62,10 @@ set(LIBRT_LIBRARY rt) target_link_libraries(linux-crash-logger ${LLCRASHLOGGER_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index 268849ad74..20eb4678dd 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -11,7 +11,7 @@ include(LLMath) include(LLMessage) include(LLCoreHttp) include(LLRender) -include(LLFileSystem) +include(LLVFS) include(LLWindow) include(LLXML) include(Linking) @@ -23,7 +23,7 @@ include_directories( ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) @@ -83,7 +83,7 @@ target_link_libraries(llappearance ${LLINVENTORY_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLRENDER_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -100,7 +100,7 @@ if (BUILD_HEADLESS) ${LLINVENTORY_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLRENDERHEADLESS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -109,3 +109,15 @@ if (BUILD_HEADLESS) ${LLCOMMON_LIBRARIES} ) endif (BUILD_HEADLESS) + +#add unit tests +#if (LL_TESTS) +# INCLUDE(LLAddBuildTest) +# SET(llappearance_TEST_SOURCE_FILES +# # no real unit tests yet! +# ) +# LL_ADD_PROJECT_UNIT_TESTS(llappearance "${llappearance_TEST_SOURCE_FILES}") + + #set(TEST_DEBUG on) +# set(test_libs llappearance ${LLCOMMON_LIBRARIES}) +#endif (LL_TESTS) diff --git a/indra/llappearance/lltexlayer.cpp b/indra/llappearance/lltexlayer.cpp index a4600069ce..e5039141de 100644 --- a/indra/llappearance/lltexlayer.cpp +++ b/indra/llappearance/lltexlayer.cpp @@ -33,6 +33,8 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "lldir.h" +#include "llvfile.h" +#include "llvfs.h" #include "lltexlayerparams.h" #include "lltexturemanagerbridge.h" #include "lllocaltextureobject.h" diff --git a/indra/llaudio/CMakeLists.txt b/indra/llaudio/CMakeLists.txt index 92a5cfe22f..558ede7bf6 100644 --- a/indra/llaudio/CMakeLists.txt +++ b/indra/llaudio/CMakeLists.txt @@ -9,14 +9,14 @@ include(OPENAL) include(LLCommon) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include_directories( ${LLAUDIO_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${OGG_INCLUDE_DIRS} ${VORBISENC_INCLUDE_DIRS} ${VORBISFILE_INCLUDE_DIRS} @@ -86,7 +86,7 @@ target_link_libraries( ${LLCOMMON_LIBRARIES} ${LLMATH_LIBRARIES} ${LLMESSAGE_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${VORBISENC_LIBRARIES} ${VORBISFILE_LIBRARIES} ${VORBIS_LIBRARIES} diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index ff0aa6e76e..e7db84f6ab 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -29,7 +29,7 @@ #include "llaudioengine.h" #include "lllfsthread.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llstring.h" #include "lldir.h" #include "llendianswizzle.h" @@ -90,17 +90,19 @@ protected: LLUUID mUUID; std::vector mWAVBuffer; +#if !defined(USE_WAV_VFILE) std::string mOutFilename; LLLFSThread::handle_t mFileHandle; +#endif - LLFileSystem *mInFilep; + LLVFile *mInFilep; OggVorbis_File mVF; S32 mCurrentSection; }; -size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource) +size_t vfs_read(void *ptr, size_t size, size_t nmemb, void *datasource) { - LLFileSystem *file = (LLFileSystem *)datasource; + LLVFile *file = (LLVFile *)datasource; if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/ { @@ -113,11 +115,11 @@ size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource) } } -S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence) +S32 vfs_seek(void *datasource, ogg_int64_t offset, S32 whence) { - LLFileSystem *file = (LLFileSystem *)datasource; + LLVFile *file = (LLVFile *)datasource; - // cache has 31-bit files + // vfs has 31-bit files if (offset > S32_MAX) { return -1; @@ -135,7 +137,7 @@ S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence) origin = -1; break; default: - LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL; + LL_ERRS("AudioEngine") << "Invalid whence argument to vfs_seek" << LL_ENDL; return -1; } @@ -149,16 +151,16 @@ S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence) } } -S32 cache_close (void *datasource) +S32 vfs_close (void *datasource) { - LLFileSystem *file = (LLFileSystem *)datasource; + LLVFile *file = (LLVFile *)datasource; delete file; return 0; } -long cache_tell (void *datasource) +long vfs_tell (void *datasource) { - LLFileSystem *file = (LLFileSystem *)datasource; + LLVFile *file = (LLVFile *)datasource; return file->tell(); } @@ -170,10 +172,11 @@ LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string & mUUID = uuid; mInFilep = NULL; mCurrentSection = 0; +#if !defined(USE_WAV_VFILE) mOutFilename = out_filename; mFileHandle = LLLFSThread::nullHandle(); - - // No default value for mVF, it's an ogg structure? +#endif + // No default value for mVF, it's an ogg structure? // Hey, let's zero it anyway, for predictability. memset(&mVF, 0, sizeof(mVF)); } @@ -190,15 +193,15 @@ LLVorbisDecodeState::~LLVorbisDecodeState() BOOL LLVorbisDecodeState::initDecode() { - ov_callbacks cache_callbacks; - cache_callbacks.read_func = cache_read; - cache_callbacks.seek_func = cache_seek; - cache_callbacks.close_func = cache_close; - cache_callbacks.tell_func = cache_tell; + ov_callbacks vfs_callbacks; + vfs_callbacks.read_func = vfs_read; + vfs_callbacks.seek_func = vfs_seek; + vfs_callbacks.close_func = vfs_close; + vfs_callbacks.tell_func = vfs_tell; LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL; - mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND); + mInFilep = new LLVFile(gVFS, mUUID, LLAssetType::AT_SOUND); if (!mInFilep || !mInFilep->getSize()) { LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL; @@ -207,7 +210,7 @@ BOOL LLVorbisDecodeState::initDecode() return FALSE; } - S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks); + S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, vfs_callbacks); if(r < 0) { LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL; @@ -367,7 +370,7 @@ BOOL LLVorbisDecodeState::decodeSection() { if (!mInFilep) { - LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL; + LL_WARNS("AudioEngine") << "No VFS file to decode in vorbis!" << LL_ENDL; return TRUE; } if (mDone) @@ -417,7 +420,9 @@ BOOL LLVorbisDecodeState::finishDecode() return TRUE; // We've finished } +#if !defined(USE_WAV_VFILE) if (mFileHandle == LLLFSThread::nullHandle()) +#endif { ov_clear(&mVF); @@ -490,9 +495,11 @@ BOOL LLVorbisDecodeState::finishDecode() mValid = FALSE; return TRUE; // we've finished } +#if !defined(USE_WAV_VFILE) mBytesRead = -1; mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, mWAVBuffer.size(), new WriteResponder(this)); +#endif } if (mFileHandle != LLLFSThread::nullHandle()) @@ -514,6 +521,11 @@ BOOL LLVorbisDecodeState::finishDecode() mDone = TRUE; +#if defined(USE_WAV_VFILE) + // write the data. + LLVFile output(gVFS, mUUID, LLAssetType::AT_SOUND_WAV); + output.write(&mWAVBuffer[0], mWAVBuffer.size()); +#endif LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL; return TRUE; @@ -523,7 +535,7 @@ void LLVorbisDecodeState::flushBadFile() { if (mInFilep) { - LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL; + LL_WARNS("AudioEngine") << "Flushing bad vorbis file from VFS for " << mUUID << LL_ENDL; mInFilep->remove(); } } diff --git a/indra/llaudio/llaudiodecodemgr.h b/indra/llaudio/llaudiodecodemgr.h index ceaff3f2d8..8228e20e8c 100644 --- a/indra/llaudio/llaudiodecodemgr.h +++ b/indra/llaudio/llaudiodecodemgr.h @@ -33,6 +33,7 @@ #include "llassettype.h" #include "llframetimer.h" +class LLVFS; class LLVorbisDecodeState; class LLAudioDecodeMgr diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index d35f249973..1d447f32ae 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -35,7 +35,7 @@ #include "sound_ids.h" // temporary hack for min/max distances -#include "llfilesystem.h" +#include "llvfs.h" #include "lldir.h" #include "llaudiodecodemgr.h" #include "llassetstorage.h" @@ -684,9 +684,13 @@ bool LLAudioEngine::preloadSound(const LLUUID &uuid) return true; } + // At some point we need to have the audio/asset system check the static VFS + // before it goes off and fetches stuff from the server. + //LL_WARNS() << "Used internal preload for non-local sound" << LL_ENDL; return false; } + bool LLAudioEngine::isWindEnabled() { return mEnableWind; @@ -1014,12 +1018,13 @@ bool LLAudioEngine::hasDecodedFile(const LLUUID &uuid) bool LLAudioEngine::hasLocalFile(const LLUUID &uuid) { - // See if it's in the cache. - bool have_local = LLFileSystem::getExists(uuid, LLAssetType::AT_SOUND); - LL_DEBUGS("AudioEngine") << "sound uuid " << uuid << " exists in cache" << LL_ENDL; + // See if it's in the VFS. + bool have_local = gVFS->getExists(uuid, LLAssetType::AT_SOUND); + LL_DEBUGS("AudioEngine") << "sound uuid "<getNumJointMotions(); jm++) { if (!mJointStates[jm]->getJoint()) diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index fe9de30f0a..cde38c8091 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -39,13 +39,14 @@ #include "llendianswizzle.h" #include "llkeyframemotion.h" #include "llquantize.h" +#include "llvfile.h" #include "m3math.h" #include "message.h" -#include "llfilesystem.h" //----------------------------------------------------------------------------- // Static Definitions //----------------------------------------------------------------------------- +LLVFS* LLKeyframeMotion::sVFS = NULL; LLKeyframeDataCache::keyframe_data_map_t LLKeyframeDataCache::sKeyframeDataMap; //----------------------------------------------------------------------------- @@ -514,7 +515,7 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact return STATUS_SUCCESS; default: // we don't know what state the asset is in yet, so keep going - // check keyframe cache first then file cache then asset request + // check keyframe cache first then static vfs then asset request break; } @@ -558,8 +559,13 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact U8 *anim_data; S32 anim_file_size; + if (!sVFS) + { + LL_ERRS() << "Must call LLKeyframeMotion::setVFS() first before loading a keyframe file!" << LL_ENDL; + } + BOOL success = FALSE; - LLFileSystem* anim_file = new LLFileSystem(mID, LLAssetType::AT_ANIMATION); + LLVFile* anim_file = new LLVFile(sVFS, mID, LLAssetType::AT_ANIMATION); if (!anim_file || !anim_file->getSize()) { delete anim_file; @@ -2290,9 +2296,10 @@ void LLKeyframeMotion::setLoopOut(F32 out_point) //----------------------------------------------------------------------------- // onLoadComplete() //----------------------------------------------------------------------------- -void LLKeyframeMotion::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLUUID* id = (LLUUID*)user_data; @@ -2324,7 +2331,7 @@ void LLKeyframeMotion::onLoadComplete(const LLUUID& asset_uuid, // asset already loaded return; } - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 size = file.getSize(); U8* buffer = new U8[size]; diff --git a/indra/llcharacter/llkeyframemotion.h b/indra/llcharacter/llkeyframemotion.h index d640556090..15c5c7c6c0 100644 --- a/indra/llcharacter/llkeyframemotion.h +++ b/indra/llcharacter/llkeyframemotion.h @@ -44,6 +44,7 @@ #include "llbvhconsts.h" class LLKeyframeDataCache; +class LLVFS; class LLDataPacker; #define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f) @@ -140,7 +141,10 @@ public: virtual void setStopTime(F32 time); - static void onLoadComplete(const LLUUID& asset_uuid, + static void setVFS(LLVFS* vfs) { sVFS = vfs; } + + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); @@ -412,7 +416,13 @@ public: U32 getNumJointMotions() const { return mJointMotionArray.size(); } }; + protected: + static LLVFS* sVFS; + + //------------------------------------------------------------------------- + // Member Data + //------------------------------------------------------------------------- JointMotionList* mJointMotionList; std::vector > mJointStates; LLJoint* mPelvisp; diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 6e8b9efaf7..f876b8ee4a 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -194,64 +194,23 @@ namespace { { return LLError::getEnabledLogTypesMask() & 0x04; } - - LL_FORCE_INLINE std::string createBoldANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "1"; - ansi_code += "m"; - - return ansi_code; - } - - LL_FORCE_INLINE std::string createResetANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "0"; - ansi_code += "m"; - - return ansi_code; - } - + LL_FORCE_INLINE std::string createANSI(const std::string& color) { std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "38;5;"; - ansi_code += color; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += color; ansi_code += "m"; - return ansi_code; } virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - // The default colors for error, warn and debug are now a bit more pastel - // and easier to read on the default (black) terminal background but you - // now have the option to set the color of each via an environment variables: - // LL_ANSI_ERROR_COLOR_CODE (default is red) - // LL_ANSI_WARN_COLOR_CODE (default is blue) - // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) - // The list of color codes can be found in many places but I used this page: - // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors - // (Note: you may need to restart Visual Studio to pick environment changes) - char* val = nullptr; - std::string s_ansi_error_code = "160"; - if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); - std::string s_ansi_warn_code = "33"; - if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); - std::string s_ansi_debug_code = "177"; - if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); - - static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red - static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue - static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta + static std::string s_ansi_error = createANSI("31"); // red + static std::string s_ansi_warn = createANSI("34"); // blue + static std::string s_ansi_debug = createANSI("35"); // magenta if (mUseANSI) { @@ -270,11 +229,11 @@ namespace { LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) { - static std::string s_ansi_bold = createBoldANSI(); // bold text - static std::string s_ansi_reset = createResetANSI(); // reset + static std::string s_ansi_bold = createANSI("1"); // bold + static std::string s_ansi_reset = createANSI("0"); // reset // ANSI color code escape sequence, message, and reset in one fprintf call // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. - fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); + fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); } static bool checkANSI(void) diff --git a/indra/llcrashlogger/CMakeLists.txt b/indra/llcrashlogger/CMakeLists.txt index d70a1e0fb0..da23b46b7b 100644 --- a/indra/llcrashlogger/CMakeLists.txt +++ b/indra/llcrashlogger/CMakeLists.txt @@ -7,7 +7,7 @@ include(LLCoreHttp) include(LLCommon) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLXML) include_directories( @@ -15,7 +15,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) include_directories(SYSTEM diff --git a/indra/llcrashlogger/llcrashlock.h b/indra/llcrashlogger/llcrashlock.h index 60b060b736..cde183272f 100644 --- a/indra/llcrashlogger/llcrashlock.h +++ b/indra/llcrashlogger/llcrashlock.h @@ -1,5 +1,5 @@ /** - * @file llcrashlock.h + * @file llpidlock.h * @brief Maintainence of disk locking files for crash reporting * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp deleted file mode 100644 index c9f7684b5a..0000000000 --- a/indra/llfilesystem/lldiskcache.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/** - * @file lldiskcache.cpp - * @brief The disk cache implementation. - * - * Note: Rather than keep the top level function comments up - * to date in both the source and header files, I elected to - * only have explicit comments about each function and variable - * in the header - look there for details. The same is true for - * description of how this code is supposed to work. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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 "llassettype.h" -#include "lldir.h" -#include -#include -#include - -#include "lldiskcache.h" - -LLDiskCache::LLDiskCache(const std::string cache_dir, - const int max_size_bytes, - const bool enable_cache_debug_info) : - mCacheDir(cache_dir), - mMaxSizeBytes(max_size_bytes), - mEnableCacheDebugInfo(enable_cache_debug_info) -{ - mCacheFilenamePrefix = "sl_cache"; - - LLFile::mkdir(cache_dir); -} - -void LLDiskCache::purge() -{ - if (mEnableCacheDebugInfo) - { - LL_INFOS() << "Total dir size before purge is " << dirFileSize(mCacheDir) << LL_ENDL; - } - - auto start_time = std::chrono::high_resolution_clock::now(); - - typedef std::pair> file_info_t; - std::vector file_info; - -#if LL_WINDOWS - std::wstring cache_path(utf8str_to_utf16str(mCacheDir)); -#else - std::string cache_path(mCacheDir); -#endif - if (boost::filesystem::is_directory(cache_path)) - { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) - { - if (boost::filesystem::is_regular_file(entry)) - { - if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) - { - uintmax_t file_size = boost::filesystem::file_size(entry); - const std::string file_path = entry.path().string(); - const std::time_t file_time = boost::filesystem::last_write_time(entry); - - file_info.push_back(file_info_t(file_time, { file_size, file_path })); - } - } - } - } - - std::sort(file_info.begin(), file_info.end(), [](file_info_t& x, file_info_t& y) - { - return x.first > y.first; - }); - - LL_INFOS() << "Purging cache to a maximum of " << mMaxSizeBytes << " bytes" << LL_ENDL; - - uintmax_t file_size_total = 0; - for (file_info_t& entry : file_info) - { - file_size_total += entry.second.first; - - std::string action = ""; - if (file_size_total > mMaxSizeBytes) - { - action = "DELETE:"; - boost::filesystem::remove(entry.second.second); - } - else - { - action = " KEEP:"; - } - - if (mEnableCacheDebugInfo) - { - // have to do this because of LL_INFO/LL_END weirdness - std::ostringstream line; - - line << action << " "; - line << entry.first << " "; - line << entry.second.first << " "; - line << entry.second.second; - line << " (" << file_size_total << "/" << mMaxSizeBytes << ")"; - LL_INFOS() << line.str() << LL_ENDL; - } - } - - if (mEnableCacheDebugInfo) - { - auto end_time = std::chrono::high_resolution_clock::now(); - auto execute_time = std::chrono::duration_cast(end_time - start_time).count(); - LL_INFOS() << "Total dir size after purge is " << dirFileSize(mCacheDir) << LL_ENDL; - LL_INFOS() << "Cache purge took " << execute_time << " ms to execute for " << file_info.size() << " files" << LL_ENDL; - } -} - -const std::string LLDiskCache::assetTypeToString(LLAssetType::EType at) -{ - /** - * Make use of the handy C++17 feature that allows - * for inline initialization of an std::map<> - */ - typedef std::map asset_type_to_name_t; - asset_type_to_name_t asset_type_to_name = - { - { LLAssetType::AT_TEXTURE, "TEXTURE" }, - { LLAssetType::AT_SOUND, "SOUND" }, - { LLAssetType::AT_CALLINGCARD, "CALLINGCARD" }, - { LLAssetType::AT_LANDMARK, "LANDMARK" }, - { LLAssetType::AT_SCRIPT, "SCRIPT" }, - { LLAssetType::AT_CLOTHING, "CLOTHING" }, - { LLAssetType::AT_OBJECT, "OBJECT" }, - { LLAssetType::AT_NOTECARD, "NOTECARD" }, - { LLAssetType::AT_CATEGORY, "CATEGORY" }, - { LLAssetType::AT_LSL_TEXT, "LSL_TEXT" }, - { LLAssetType::AT_LSL_BYTECODE, "LSL_BYTECODE" }, - { LLAssetType::AT_TEXTURE_TGA, "TEXTURE_TGA" }, - { LLAssetType::AT_BODYPART, "BODYPART" }, - { LLAssetType::AT_SOUND_WAV, "SOUND_WAV" }, - { LLAssetType::AT_IMAGE_TGA, "IMAGE_TGA" }, - { LLAssetType::AT_IMAGE_JPEG, "IMAGE_JPEG" }, - { LLAssetType::AT_ANIMATION, "ANIMATION" }, - { LLAssetType::AT_GESTURE, "GESTURE" }, - { LLAssetType::AT_SIMSTATE, "SIMSTATE" }, - { LLAssetType::AT_LINK, "LINK" }, - { LLAssetType::AT_LINK_FOLDER, "LINK_FOLDER" }, - { LLAssetType::AT_MARKETPLACE_FOLDER, "MARKETPLACE_FOLDER" }, - { LLAssetType::AT_WIDGET, "WIDGET" }, - { LLAssetType::AT_PERSON, "PERSON" }, - { LLAssetType::AT_MESH, "MESH" }, - { LLAssetType::AT_SETTINGS, "SETTINGS" }, - { LLAssetType::AT_UNKNOWN, "UNKNOWN" } - }; - - asset_type_to_name_t::iterator iter = asset_type_to_name.find(at); - if (iter != asset_type_to_name.end()) - { - return iter->second; - } - - return std::string("UNKNOWN"); -} - -const std::string LLDiskCache::metaDataToFilepath(const std::string id, - LLAssetType::EType at, - const std::string extra_info) -{ - std::ostringstream file_path; - - file_path << mCacheDir; - file_path << gDirUtilp->getDirDelimiter(); - file_path << mCacheFilenamePrefix; - file_path << "_"; - file_path << id; - file_path << "_"; - file_path << (extra_info.empty() ? "0" : extra_info); - //file_path << "_"; - //file_path << assetTypeToString(at); // see SL-14210 Prune descriptive tag from new cache filenames - // for details of why it was removed. Note that if you put it - // back or change the format of the filename, the cache files - // files will be invalidated (and perhaps, more importantly, - // never deleted unless you delete them manually). - file_path << ".asset"; - - return file_path.str(); -} - -void LLDiskCache::updateFileAccessTime(const std::string file_path) -{ - /** - * Threshold in time_t units that is used to decide if the last access time - * time of the file is updated or not. Added as a precaution for the concern - * outlined in SL-14582 about frequent writes on older SSDs reducing their - * lifespan. I think this is the right place for the threshold value - rather - * than it being a pref - do comment on that Jira if you disagree... - * - * Let's start with 1 hour in time_t units and see how that unfolds - */ - const std::time_t time_threshold = 1 * 60 * 60; - - // current time - const std::time_t cur_time = std::time(nullptr); - -#if LL_WINDOWS - // file last write time - const std::time_t last_write_time = boost::filesystem::last_write_time(utf8str_to_utf16str(file_path)); - - // delta between cur time and last time the file was written - const std::time_t delta_time = cur_time - last_write_time; - - // we only write the new value if the time in time_threshold has elapsed - // before the last one - if (delta_time > time_threshold) - { - boost::filesystem::last_write_time(utf8str_to_utf16str(file_path), cur_time); - } -#else - // file last write time - const std::time_t last_write_time = boost::filesystem::last_write_time(file_path); - - // delta between cur time and last time the file was written - const std::time_t delta_time = cur_time - last_write_time; - - // we only write the new value if the time in time_threshold has elapsed - // before the last one - if (delta_time > time_threshold) - { - boost::filesystem::last_write_time(file_path, cur_time); - } -#endif -} - -const std::string LLDiskCache::getCacheInfo() -{ - std::ostringstream cache_info; - - F32 max_in_mb = (F32)mMaxSizeBytes / (1024.0 * 1024.0); - F32 percent_used = ((F32)dirFileSize(mCacheDir) / (F32)mMaxSizeBytes) * 100.0; - - cache_info << std::fixed; - cache_info << std::setprecision(1); - cache_info << "Max size " << max_in_mb << " MB "; - cache_info << "(" << percent_used << "% used)"; - - return cache_info.str(); -} - -void LLDiskCache::clearCache() -{ - /** - * See notes on performance in dirFileSize(..) - there may be - * a quicker way to do this by operating on the parent dir vs - * the component files but it's called infrequently so it's - * likely just fine - */ -#if LL_WINDOWS - std::wstring cache_path(utf8str_to_utf16str(mCacheDir)); -#else - std::string cache_path(mCacheDir); -#endif - if (boost::filesystem::is_directory(cache_path)) - { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) - { - if (boost::filesystem::is_regular_file(entry)) - { - if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) - { - boost::filesystem::remove(entry); - } - } - } - } -} - -uintmax_t LLDiskCache::dirFileSize(const std::string dir) -{ - uintmax_t total_file_size = 0; - - /** - * There may be a better way that works directly on the folder (similar to - * right clicking on a folder in the OS and asking for size vs right clicking - * on all files and adding up manually) but this is very fast - less than 100ms - * for 10,000 files in my testing so, so long as it's not called frequently, - * it should be okay. Note that's it's only currently used for logging/debugging - * so if performance is ever an issue, optimizing this or removing it altogether, - * is an easy win. - */ -#if LL_WINDOWS - std::wstring dir_path(utf8str_to_utf16str(dir)); -#else - std::string dir_path(dir); -#endif - if (boost::filesystem::is_directory(dir_path)) - { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path), {})) - { - if (boost::filesystem::is_regular_file(entry)) - { - if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) - { - total_file_size += boost::filesystem::file_size(entry); - } - } - } - } - - return total_file_size; -} diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h deleted file mode 100644 index 997884da31..0000000000 --- a/indra/llfilesystem/lldiskcache.h +++ /dev/null @@ -1,183 +0,0 @@ -/** - * @file lldiskcache.h - * @brief The disk cache implementation declarations. - * - * @Description: - * This code implements a disk cache using the following ideas: - * 1/ The metadata for a file can be encapsulated in the filename. - The filenames will be composed of the following fields: - Prefix: Used to identify the file as a part of the cache. - An additional reason for using a prefix is that it - might be possible, either accidentally or maliciously - to end up with the cache dir set to a non-cache - location such as your OS system dir or a work folder. - Purging files from that would obviously be a disaster - so this is an extra step to help avoid that scenario. - ID: Typically the asset ID (UUID) of the asset being - saved but can be anything valid for a filename - Extra Info: A field for use in the future that can be used - to store extra identifiers - e.g. the discard - level of a JPEG2000 file - Asset Type: A text string created from the LLAssetType enum - that identifies the type of asset being stored. - .asset A file extension of .asset is used to help - identify this as a Viewer asset file - * 2/ The time of last access for a file can be updated instantly - * for file reads and automatically as part of the file writes. - * 3/ The purge algorithm collects a list of all files in the - * directory, sorts them by date of last access (write) and then - * deletes any files based on age until the total size of all - * the files is less than the maximum size specified. - * 4/ An LLSingleton idiom is used since there will only ever be - * a single cache and we want to access it from numerous places. - * 5/ Performance on my modest system seems very acceptable. For - * example, in testing, I was able to purge a directory of - * 10,000 files, deleting about half of them in ~ 1700ms. For - * the same sized directory of files, writing the last updated - * time to each took less than 600ms indicating that this - * important part of the mechanism has almost no overhead. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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 _LLDISKCACHE -#define _LLDISKCACHE - -#include "llsingleton.h" - -class LLDiskCache : - public LLParamSingleton -{ - public: - /** - * Since this is using the LLSingleton pattern but we - * want to allow the constructor to be called first - * with various parameters, we also invoke the - * LLParamSingleton idiom and use it to initialize - * the class via a call in LLAppViewer. - */ - LLSINGLETON(LLDiskCache, - /** - * The full name of the cache folder - typically a - * a child of the main Viewer cache directory. Defined - * by the setting at 'DiskCacheDirName' - */ - const std::string cache_dir, - /** - * The maximum size of the cache in bytes - Based on the - * setting at 'CacheSize' and 'DiskCachePercentOfTotal' - */ - const int max_size_bytes, - /** - * A flag that enables extra cache debugging so that - * if there are bugs, we can ask uses to enable this - * setting and send us their logs - */ - const bool enable_cache_debug_info); - - virtual ~LLDiskCache() = default; - - public: - /** - * Construct a filename and path to it based on the file meta data - * (id, asset type, additional 'extra' info like discard level perhaps) - * Worth pointing out that this function used to be in LLFileSystem but - * so many things had to be pushed back there to accomodate it, that I - * decided to move it here. Still not sure that's completely right. - */ - const std::string metaDataToFilepath(const std::string id, - LLAssetType::EType at, - const std::string extra_info); - - /** - * Update the "last write time" of a file to "now". This must be called whenever a - * file in the cache is read (not written) so that the last time the file was - * accessed is up to date (This is used in the mechanism for purging the cache) - */ - void updateFileAccessTime(const std::string file_path); - - /** - * Purge the oldest items in the cache so that the combined size of all files - * is no bigger than mMaxSizeBytes. - */ - void purge(); - - /** - * Clear the cache by removing all the files in the specified cache - * directory individually. Only the files that contain a prefix defined - * by mCacheFilenamePrefix will be removed. - */ - void clearCache(); - - /** - * Return some information about the cache for use in About Box etc. - */ - const std::string getCacheInfo(); - - private: - /** - * Utility function to gather the total size the files in a given - * directory. Primarily used here to determine the directory size - * before and after the cache purge - */ - uintmax_t dirFileSize(const std::string dir); - - /** - * Utility function to convert an LLAssetType enum into a - * string that we use as part of the cache file filename - */ - const std::string assetTypeToString(LLAssetType::EType at); - - private: - /** - * The maximum size of the cache in bytes. After purge is called, the - * total size of the cache files in the cache directory will be - * less than this value - */ - uintmax_t mMaxSizeBytes; - - /** - * The folder that holds the cached files. The consumer of this - * class must avoid letting the user set this location as a malicious - * setting could potentially point it at a non-cache directory (for example, - * the Windows System dir) with disastrous results. - */ - std::string mCacheDir; - - /** - * The prefix inserted at the start of a cache file filename to - * help identify it as a cache file. It's probably not required - * (just the presence in the cache folder is enough) but I am - * paranoid about the cache folder being set to something bad - * like the users' OS system dir by mistake or maliciously and - * this will help to offset any damage if that happens. - */ - std::string mCacheFilenamePrefix; - - /** - * When enabled, displays additional debugging information in - * various parts of the code - */ - bool mEnableCacheDebugInfo; -}; - -#endif // _LLDISKCACHE diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp deleted file mode 100644 index 64e0b9f193..0000000000 --- a/indra/llfilesystem/llfilesystem.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/** - * @file filesystem.h - * @brief Simulate local file system operations. - * @Note The initial implementation does actually use standard C++ - * file operations but eventually, there will be another - * layer that caches and manages file meta data too. - * - * $LicenseInfo:firstyear=2002&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 "lldir.h" -#include "llfilesystem.h" -#include "llfasttimer.h" -#include "lldiskcache.h" - -const S32 LLFileSystem::READ = 0x00000001; -const S32 LLFileSystem::WRITE = 0x00000002; -const S32 LLFileSystem::READ_WRITE = 0x00000003; // LLFileSystem::READ & LLFileSystem::WRITE -const S32 LLFileSystem::APPEND = 0x00000006; // 0x00000004 & LLFileSystem::WRITE - -static LLTrace::BlockTimerStatHandle FTM_VFILE_WAIT("VFile Wait"); - -LLFileSystem::LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_type, S32 mode) -{ - mFileType = file_type; - mFileID = file_id; - mPosition = 0; - mBytesRead = 0; - mMode = mode; -} - -LLFileSystem::~LLFileSystem() -{ -} - -// static -bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType file_type) -{ - std::string id_str; - file_id.toString(id_str); - const std::string extra_info = ""; - const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); - - llifstream file(filename, std::ios::binary); - if (file.is_open()) - { - file.seekg(0, std::ios::end); - return file.tellg() > 0; - } - return false; -} - -// static -bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type) -{ - std::string id_str; - file_id.toString(id_str); - const std::string extra_info = ""; - const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); - - LLFile::remove(filename.c_str()); - - return true; -} - -// static -bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, - const LLUUID& new_file_id, const LLAssetType::EType new_file_type) -{ - std::string old_id_str; - old_file_id.toString(old_id_str); - const std::string extra_info = ""; - const std::string old_filename = LLDiskCache::getInstance()->metaDataToFilepath(old_id_str, old_file_type, extra_info); - - std::string new_id_str; - new_file_id.toString(new_id_str); - const std::string new_filename = LLDiskCache::getInstance()->metaDataToFilepath(new_id_str, new_file_type, extra_info); - - // Rename needs the new file to not exist. - LLFileSystem::removeFile(new_file_id, new_file_type); - - if (LLFile::rename(old_filename, new_filename) != 0) - { - // We would like to return FALSE here indicating the operation - // failed but the original code does not and doing so seems to - // break a lot of things so we go with the flow... - //return FALSE; - LL_WARNS() << "Failed to rename " << old_file_id << " to " << new_id_str << " reason: " << strerror(errno) << LL_ENDL; - } - - return TRUE; -} - -// static -S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type) -{ - std::string id_str; - file_id.toString(id_str); - const std::string extra_info = ""; - const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); - - S32 file_size = 0; - llifstream file(filename, std::ios::binary); - if (file.is_open()) - { - file.seekg(0, std::ios::end); - file_size = file.tellg(); - } - - return file_size; -} - -BOOL LLFileSystem::read(U8* buffer, S32 bytes) -{ - BOOL success = TRUE; - - std::string id; - mFileID.toString(id); - const std::string extra_info = ""; - const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id, mFileType, extra_info); - - llifstream file(filename, std::ios::binary); - if (file.is_open()) - { - file.seekg(mPosition, std::ios::beg); - - file.read((char*)buffer, bytes); - - if (file) - { - mBytesRead = bytes; - } - else - { - mBytesRead = file.gcount(); - } - - file.close(); - - mPosition += mBytesRead; - if (!mBytesRead) - { - success = FALSE; - } - } - - // update the last access time for the file - this is required - // even though we are reading and not writing because this is the - // way the cache works - it relies on a valid "last accessed time" for - // each file so it knows how to remove the oldest, unused files - LLDiskCache::getInstance()->updateFileAccessTime(filename); - - return success; -} - -S32 LLFileSystem::getLastBytesRead() -{ - return mBytesRead; -} - -BOOL LLFileSystem::eof() -{ - return mPosition >= getSize(); -} - -BOOL LLFileSystem::write(const U8* buffer, S32 bytes) -{ - std::string id_str; - mFileID.toString(id_str); - const std::string extra_info = ""; - const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, mFileType, extra_info); - - BOOL success = FALSE; - - if (mMode == APPEND) - { - llofstream ofs(filename, std::ios::app | std::ios::binary); - if (ofs) - { - ofs.write((const char*)buffer, bytes); - - success = TRUE; - } - } - else - { - llofstream ofs(filename, std::ios::binary); - if (ofs) - { - ofs.write((const char*)buffer, bytes); - - mPosition += bytes; - - success = TRUE; - } - } - - return success; -} - -BOOL LLFileSystem::seek(S32 offset, S32 origin) -{ - if (-1 == origin) - { - origin = mPosition; - } - - S32 new_pos = origin + offset; - - S32 size = getSize(); - - if (new_pos > size) - { - LL_WARNS() << "Attempt to seek past end of file" << LL_ENDL; - - mPosition = size; - return FALSE; - } - else if (new_pos < 0) - { - LL_WARNS() << "Attempt to seek past beginning of file" << LL_ENDL; - - mPosition = 0; - return FALSE; - } - - mPosition = new_pos; - return TRUE; -} - -S32 LLFileSystem::tell() const -{ - return mPosition; -} - -S32 LLFileSystem::getSize() -{ - return LLFileSystem::getFileSize(mFileID, mFileType); -} - -S32 LLFileSystem::getMaxSize() -{ - // offer up a huge size since we don't care what the max is - return INT_MAX; -} - -BOOL LLFileSystem::rename(const LLUUID& new_id, const LLAssetType::EType new_type) -{ - LLFileSystem::renameFile(mFileID, mFileType, new_id, new_type); - - mFileID = new_id; - mFileType = new_type; - - return TRUE; -} - -BOOL LLFileSystem::remove() -{ - LLFileSystem::removeFile(mFileID, mFileType); - - return TRUE; -} diff --git a/indra/llfilesystem/llfilesystem.h b/indra/llfilesystem/llfilesystem.h deleted file mode 100644 index 89bfff5798..0000000000 --- a/indra/llfilesystem/llfilesystem.h +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @file filesystem.h - * @brief Simulate local file system operations. - * @Note The initial implementation does actually use standard C++ - * file operations but eventually, there will be another - * layer that caches and manages file meta data too. - * - * $LicenseInfo:firstyear=2002&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_FILESYSTEM_H -#define LL_FILESYSTEM_H - -#include "lluuid.h" -#include "llassettype.h" -#include "lldiskcache.h" - -class LLFileSystem -{ - public: - LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_type, S32 mode = LLFileSystem::READ); - ~LLFileSystem(); - - BOOL read(U8* buffer, S32 bytes); - S32 getLastBytesRead(); - BOOL eof(); - - BOOL write(const U8* buffer, S32 bytes); - BOOL seek(S32 offset, S32 origin = -1); - S32 tell() const; - - S32 getSize(); - S32 getMaxSize(); - BOOL rename(const LLUUID& new_id, const LLAssetType::EType new_type); - BOOL remove(); - - static bool getExists(const LLUUID& file_id, const LLAssetType::EType file_type); - static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type); - static bool renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, - const LLUUID& new_file_id, const LLAssetType::EType new_file_type); - static S32 getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type); - - public: - static const S32 READ; - static const S32 WRITE; - static const S32 READ_WRITE; - static const S32 APPEND; - - protected: - LLAssetType::EType mFileType; - LLUUID mFileID; - S32 mPosition; - S32 mMode; - S32 mBytesRead; -//private: -// static const std::string idToFilepath(const std::string id, LLAssetType::EType at); -}; - -#endif // LL_FILESYSTEM_H diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index dc8e9f7c2f..293ada7548 100644 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -6,7 +6,7 @@ include(00-Common) include(LLCommon) include(LLImage) include(LLMath) -include(LLFileSystem) +include(LLVFS) include(LLKDU) include(LLImageJ2COJ) include(ZLIB) @@ -17,7 +17,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCOMMON_SYSTEM_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ) @@ -68,7 +68,7 @@ else (USE_KDU) endif (USE_KDU) target_link_libraries(llimage - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${JPEG_LIBRARIES} diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 350a8eb120..aed8943439 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -2219,11 +2219,20 @@ bool LLImageFormatted::save(const std::string &filename) return true; } +// bool LLImageFormatted::save(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +// Depricated to remove VFS dependency. +// Use: +// LLVFile::writeFile(image->getData(), image->getDataSize(), vfs, uuid, type); + +//---------------------------------------------------------------------------- + S8 LLImageFormatted::getCodec() const { return mCodec; } +//============================================================================ + static void avg4_colors4(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst) { dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2); diff --git a/indra/llinventory/CMakeLists.txt b/indra/llinventory/CMakeLists.txt index 04975940aa..e829788c91 100644 --- a/indra/llinventory/CMakeLists.txt +++ b/indra/llinventory/CMakeLists.txt @@ -7,7 +7,7 @@ include(LLCommon) include(LLCoreHttp) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLXML) include_directories( @@ -81,7 +81,7 @@ if (LL_TESTS) LL_ADD_PROJECT_UNIT_TESTS(llinventory "${llinventory_TEST_SOURCE_FILES}") #set(TEST_DEBUG on) - set(test_libs llinventory ${LLMESSAGE_LIBRARIES} ${LLFILESYSTEM_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) + set(test_libs llinventory ${LLMESSAGE_LIBRARIES} ${LLVFS_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) LL_ADD_INTEGRATION_TEST(inventorymisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llparcel "" "${test_libs}") endif (LL_TESTS) diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index f0a1dfe940..2f99ca069e 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -9,7 +9,7 @@ include(LLCommon) include(LLCoreHttp) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLAddBuildTest) include(Python) include(Tut) @@ -23,7 +23,7 @@ include_directories( ${LLCOREHTTP_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIR} ) @@ -209,7 +209,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -227,7 +227,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -257,7 +257,7 @@ if (LL_TESTS) if (LINUX) set(test_libs ${WINDOWS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${CURL_LIBRARIES} ${NGHTTP2_LIBRARIES} @@ -273,7 +273,7 @@ if (LINUX) else (LINUX) set(test_libs ${WINDOWS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${CURL_LIBRARIES} ${NGHTTP2_LIBRARIES} diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index f38a5e663e..d7801b6ddc 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -42,7 +42,8 @@ // this library includes #include "message.h" #include "llxfermanager.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "lldbstrings.h" #include "lltransfersourceasset.h" @@ -201,7 +202,7 @@ LLBaseDownloadRequest::LLBaseDownloadRequest(const LLUUID &uuid, const LLAssetTy mIsTemp(FALSE), mIsPriority(FALSE), mDataSentInFirstPacket(FALSE), - mDataIsInCache(FALSE) + mDataIsInVFS(FALSE) { // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been // running a message system loop. @@ -265,8 +266,7 @@ LLSD LLAssetRequest::getFullDetails() const sd["is_local"] = mIsLocal; sd["is_priority"] = mIsPriority; sd["data_send_in_first_packet"] = mDataSentInFirstPacket; - // Note: cannot change this (easily) since it is consumed by server - sd["data_is_in_vfs"] = mDataIsInCache; + sd["data_is_in_vfs"] = mDataIsInVFS; return sd; } @@ -330,23 +330,28 @@ LLBaseDownloadRequest* LLEstateAssetRequest::getCopy() // TODO: rework tempfile handling? -LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host) { - _init(msg, xfer, upstream_host); + _init(msg, xfer, vfs, static_vfs, upstream_host); } -LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs) { - _init(msg, xfer, LLHost()); + _init(msg, xfer, vfs, static_vfs, LLHost()); } void LLAssetStorage::_init(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, + LLVFS *static_vfs, const LLHost &upstream_host) { mShutDown = FALSE; mMessageSys = msg; mXferManager = xfer; + mVFS = vfs; + mStaticVFS = static_vfs; setUpstream(upstream_host); msg->setHandlerFuncFast(_PREHASH_AssetUploadComplete, processUploadComplete, (void **)this); @@ -425,7 +430,7 @@ void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) } if (tmp->mDownCallback) { - tmp->mDownCallback(tmp->getUUID(), tmp->getType(), tmp->mUserData, error, LLExtStat::NONE); + tmp->mDownCallback(mVFS, tmp->getUUID(), tmp->getType(), tmp->mUserData, error, LLExtStat::NONE); } if (tmp->mInfoCallback) { @@ -438,10 +443,10 @@ void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) BOOL LLAssetStorage::hasLocalAsset(const LLUUID &uuid, const LLAssetType::EType type) { - return LLFileSystem::getExists(uuid, type); + return mStaticVFS->getExists(uuid, type) || mVFS->getExists(uuid, type); } -bool LLAssetStorage::findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, +bool LLAssetStorage::findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, void *user_data) { if (user_data) @@ -450,17 +455,17 @@ bool LLAssetStorage::findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetTyp llassert(callback != NULL); } - BOOL exists = LLFileSystem::getExists(uuid, type); + BOOL exists = mStaticVFS->getExists(uuid, type); if (exists) { - LLFileSystem file(uuid, type); + LLVFile file(mStaticVFS, uuid, type); U32 size = file.getSize(); if (size > 0) { // we've already got the file if (callback) { - callback(uuid, type, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); + callback(mStaticVFS, uuid, type, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); } return true; } @@ -501,7 +506,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, if (callback) { add(sFailedDownloadCount, 1); - callback(uuid, type, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::NONE); + callback(mVFS, uuid, type, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::NONE); } return; } @@ -512,19 +517,20 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, if (callback) { add(sFailedDownloadCount, 1); - callback(uuid, type, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); + callback(mVFS, uuid, type, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); } return; } - if (findInCacheAndInvokeCallback(uuid,type,callback,user_data)) + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback(uuid,type,callback,user_data)) { - LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in cache" << LL_ENDL; + LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in static VFS" << LL_ENDL; return; } - BOOL exists = LLFileSystem::getExists(uuid, type); - LLFileSystem file(uuid, type); + BOOL exists = mVFS->getExists(uuid, type); + LLVFile file(mVFS, uuid, type); U32 size = exists ? file.getSize() : 0; if (size > 0) @@ -534,10 +540,10 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, // unless there's a weird error if (callback) { - callback(uuid, type, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); + callback(mVFS, uuid, type, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); } - LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in cache" << LL_ENDL; + LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in VFS" << LL_ENDL; } else { @@ -610,7 +616,7 @@ void LLAssetStorage::removeAndCallbackPendingDownloads(const LLUUID& file_id, LL { add(sFailedDownloadCount, 1); } - tmp->mDownCallback(callback_id, callback_type, tmp->mUserData, result_code, ext_status); + tmp->mDownCallback(gAssetStorage->mVFS, callback_id, callback_type, tmp->mUserData, result_code, ext_status); } delete tmp; } @@ -664,7 +670,7 @@ void LLAssetStorage::downloadCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLFileSystem vfile(callback_id, callback_type); + LLVFile vfile(gAssetStorage->mVFS, callback_id, callback_type); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset " << callback_id << LL_ENDL; @@ -713,19 +719,19 @@ void LLAssetStorage::getEstateAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); + callback(mVFS, asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); } return; } - // Try static first. - if (findInCacheAndInvokeCallback(asset_id,atype,callback,user_data)) + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback(asset_id,atype,callback,user_data)) { return; } - BOOL exists = LLFileSystem::getExists(asset_id, atype); - LLFileSystem file(asset_id, atype); + BOOL exists = mVFS->getExists(asset_id, atype); + LLVFile file(mVFS, asset_id, atype); U32 size = exists ? file.getSize() : 0; if (size > 0) @@ -735,7 +741,7 @@ void LLAssetStorage::getEstateAsset( // unless there's a weird error if (callback) { - callback(asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); } } else @@ -786,7 +792,7 @@ void LLAssetStorage::getEstateAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); + callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); } } } @@ -818,7 +824,7 @@ void LLAssetStorage::downloadEstateAssetCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLFileSystem vfile(req->getUUID(), req->getAType()); + LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getAType()); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset!" << LL_ENDL; @@ -832,7 +838,7 @@ void LLAssetStorage::downloadEstateAssetCompleteCallback( { add(sFailedDownloadCount, 1); } - req->mDownCallback(req->getUUID(), req->getAType(), req->mUserData, result, ext_status); + req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getAType(), req->mUserData, result, ext_status); } void LLAssetStorage::getInvItemAsset( @@ -855,13 +861,14 @@ void LLAssetStorage::getInvItemAsset( if(asset_id.notNull()) { - if (findInCacheAndInvokeCallback( asset_id, atype, callback, user_data)) + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback( asset_id, atype, callback, user_data)) { return; } - exists = LLFileSystem::getExists(asset_id, atype); - LLFileSystem file(asset_id, atype); + exists = mVFS->getExists(asset_id, atype); + LLVFile file(mVFS, asset_id, atype); size = exists ? file.getSize() : 0; if(exists && size < 1) { @@ -878,7 +885,7 @@ void LLAssetStorage::getInvItemAsset( // unless there's a weird error if (callback) { - callback(asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); } } else @@ -929,7 +936,7 @@ void LLAssetStorage::getInvItemAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); + callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); } } } @@ -961,7 +968,7 @@ void LLAssetStorage::downloadInvItemCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLFileSystem vfile(req->getUUID(), req->getType()); + LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getType()); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset!" << LL_ENDL; @@ -975,7 +982,7 @@ void LLAssetStorage::downloadInvItemCompleteCallback( { add(sFailedDownloadCount, 1); } - req->mDownCallback(req->getUUID(), req->getType(), req->mUserData, result, ext_status); + req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getType(), req->mUserData, result, ext_status); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1286,7 +1293,7 @@ bool LLAssetStorage::deletePendingRequestImpl(LLAssetStorage::request_list_t* re if (req->mDownCallback) { add(sFailedDownloadCount, 1); - req->mDownCallback(req->getUUID(), req->getType(), req->mUserData, error, LLExtStat::REQUEST_DROPPED); + req->mDownCallback(mVFS, req->getUUID(), req->getType(), req->mUserData, error, LLExtStat::REQUEST_DROPPED); } if (req->mInfoCallback) { @@ -1356,7 +1363,8 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, { LLAssetRequest* tmp = *iter++; - auto cbptr = tmp->mDownCallback.target(); + //void(*const* cbptr)(LLVFS *, const LLUUID &, LLAssetType::EType, void *, S32, LLExtStat) + auto cbptr = tmp->mDownCallback.target(); if (type == tmp->getType() && uuid == tmp->getUUID() && @@ -1381,7 +1389,8 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, } // static -void LLAssetStorage::legacyGetDataCallback(const LLUUID &uuid, +void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, + const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 status, @@ -1396,7 +1405,7 @@ void LLAssetStorage::legacyGetDataCallback(const LLUUID &uuid, if ( !status && !toxic ) { - LLFileSystem file(uuid, type); + LLVFile file(vfs, uuid, type); std::string uuid_str; diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h index e0f22f1160..c799d8eefc 100644 --- a/indra/llmessage/llassetstorage.h +++ b/indra/llmessage/llassetstorage.h @@ -44,6 +44,7 @@ class LLMessageSystem; class LLXferManager; class LLAssetStorage; +class LLVFS; class LLSD; // anything that takes longer than this to download will abort. @@ -59,11 +60,11 @@ const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5; const int LL_ERR_PRICE_MISMATCH = -23018; -// *TODO: these typedefs are passed into the cache via a legacy C function pointer +// *TODO: these typedefs are passed into the VFS via a legacy C function pointer // future project would be to convert these to C++ callables (std::function<>) so that // we can use bind and remove the userData parameter. // -typedef std::function LLGetAssetCallback; +typedef std::function LLGetAssetCallback; typedef std::function LLStoreAssetCallback; @@ -119,6 +120,7 @@ protected: public: LLGetAssetCallback mDownCallback; +// void(*mDownCallback)(LLVFS*, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat); void *mUserData; LLHost mHost; @@ -126,7 +128,7 @@ public: F64Seconds mTime; // Message system time BOOL mIsPriority; BOOL mDataSentInFirstPacket; - BOOL mDataIsInCache; + BOOL mDataIsInVFS; }; class LLAssetRequest : public LLBaseDownloadRequest @@ -196,6 +198,9 @@ typedef std::map toxic_asset_map_t; class LLAssetStorage { public: + // VFS member is public because static child methods need it :( + LLVFS *mVFS; + LLVFS *mStaticVFS; typedef ::LLStoreAssetCallback LLStoreAssetCallback; typedef ::LLGetAssetCallback LLGetAssetCallback; @@ -225,9 +230,11 @@ protected: toxic_asset_map_t mToxicAssetMap; // Objects in this list are known to cause problems and are not loaded public: - LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host); - LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs); virtual ~LLAssetStorage(); void setUpstream(const LLHost &upstream_host); @@ -277,7 +284,7 @@ public: void markAssetToxic( const LLUUID& uuid ); protected: - bool findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, + bool findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, void *user_data); LLSD getPendingDetailsImpl(const request_list_t* requests, @@ -368,7 +375,7 @@ public: bool user_waiting = false, F64Seconds timeout = LL_ASSET_STORAGE_TIMEOUT) = 0; - static void legacyGetDataCallback(const LLUUID &uuid, LLAssetType::EType, void *user_data, S32 status, LLExtStat ext_status); + static void legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType, void *user_data, S32 status, LLExtStat ext_status); static void legacyStoreDataCallback(const LLUUID &uuid, void *user_data, S32 status, LLExtStat ext_status); // add extra methods to handle metadata @@ -378,12 +385,15 @@ protected: void _callUploadCallbacks(const LLUUID &uuid, const LLAssetType::EType asset_type, BOOL success, LLExtStat ext_status); virtual void _queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, +// void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), void *user_data, BOOL duplicate, BOOL is_priority) = 0; private: void _init(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, + LLVFS *static_vfs, const LLHost &upstream_host); protected: @@ -398,7 +408,7 @@ protected: MR_FILE_NONEXIST = 3, // Old format store call - source file does not exist MR_NO_FILENAME = 4, // Old format store call - source filename is NULL/0-length MR_NO_UPSTREAM = 5, // Upstream provider is missing - MR_CACHE_CORRUPTION = 6 // cache is corrupt - too-large or mismatched stated/returned sizes + MR_VFS_CORRUPTION = 6 // VFS is corrupt - too-large or mismatched stated/returned sizes }; static class LLMetrics *metric_recipient; diff --git a/indra/llmessage/llcorehttputil.cpp b/indra/llmessage/llcorehttputil.cpp index 7031f1aa8c..0eae6d9826 100644 --- a/indra/llmessage/llcorehttputil.cpp +++ b/indra/llmessage/llcorehttputil.cpp @@ -37,7 +37,7 @@ #include "llsdserialize.h" #include "reader.h" // JSON #include "writer.h" // JSON -#include "llfilesystem.h" +#include "llvfile.h" #include "message.h" // for getting the port @@ -784,7 +784,7 @@ LLSD HttpCoroutineAdapter::postFileAndSuspend(LLCore::HttpRequest::ptr_t request // scoping for our streams so that they go away when we no longer need them. { LLCore::BufferArrayStream outs(fileData.get()); - LLFileSystem vfile(assetId, assetType, LLFileSystem::READ); + LLVFile vfile(gVFS, assetId, assetType, LLVFile::READ); S32 fileSize = vfile.getSize(); U8* fileBuffer; diff --git a/indra/llmessage/llextendedstatus.h b/indra/llmessage/llextendedstatus.h index 2a53dced80..9923d73c1a 100644 --- a/indra/llmessage/llextendedstatus.h +++ b/indra/llmessage/llextendedstatus.h @@ -1,7 +1,7 @@ /** * @file llextendedstatus.h * @date August 2007 - * @brief extended status codes for curl/resident asset storage and delivery + * @brief extended status codes for curl/vfs/resident asset storage and delivery * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code @@ -32,9 +32,9 @@ enum class LLExtStat: uint32_t { // Status provider groups - Top bits indicate which status type it is // Zero is common status code (next section) - CURL_RESULT = 1UL<<30, // serviced by curl - use 1L if we really implement the below - RES_RESULT = 2UL<<30, // serviced by resident copy - CACHE_RESULT = 3UL<<30, // serviced by cache + CURL_RESULT = 1UL<<30, // serviced by curl - use 1L if we really implement the below + RES_RESULT = 2UL<<30, // serviced by resident copy + VFS_RESULT = 3UL<<30, // serviced by vfs // Common Status Codes @@ -54,9 +54,9 @@ enum class LLExtStat: uint32_t // Memory-Resident status codes: // None at present - // CACHE status codes: - CACHE_CACHED = CACHE_RESULT | 0x0001, - CACHE_CORRUPT = CACHE_RESULT | 0x0002, + // VFS status codes: + VFS_CACHED = VFS_RESULT | 0x0001, + VFS_CORRUPT = VFS_RESULT | 0x0002, }; diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index 027283232d..80ed3340c6 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -32,7 +32,7 @@ #include "message.h" #include "lldatapacker.h" #include "lldir.h" -#include "llfilesystem.h" +#include "llvfile.h" LLTransferSourceAsset::LLTransferSourceAsset(const LLUUID &request_id, const F32 priority) : LLTransferSource(LLTST_ASSET, request_id, priority), @@ -99,7 +99,7 @@ LLTSCode LLTransferSourceAsset::dataCallback(const S32 packet_id, return LLTS_SKIP; } - LLFileSystem vf(mParams.getAssetID(), mParams.getAssetType(), LLFileSystem::READ); + LLVFile vf(gAssetStorage->mVFS, mParams.getAssetID(), mParams.getAssetType(), LLVFile::READ); if (!vf.getSize()) { @@ -171,7 +171,7 @@ BOOL LLTransferSourceAsset::unpackParams(LLDataPacker &dp) } -void LLTransferSourceAsset::responderCallback(const LLUUID& uuid, LLAssetType::EType type, +void LLTransferSourceAsset::responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void *user_data, S32 result, LLExtStat ext_status ) { LLUUID *tidp = ((LLUUID*) user_data); @@ -198,7 +198,7 @@ void LLTransferSourceAsset::responderCallback(const LLUUID& uuid, LLAssetType::E if (LL_ERR_NOERR == result) { // Everything's OK. - LLFileSystem vf(uuid, type, LLFileSystem::READ); + LLVFile vf(gAssetStorage->mVFS, uuid, type, LLVFile::READ); tsap->mSize = vf.getSize(); status = LLTS_OK; } diff --git a/indra/llmessage/lltransfersourceasset.h b/indra/llmessage/lltransfersourceasset.h index 585e683cb3..3abda83cf8 100644 --- a/indra/llmessage/lltransfersourceasset.h +++ b/indra/llmessage/lltransfersourceasset.h @@ -30,7 +30,7 @@ #include "lltransfermanager.h" #include "llassetstorage.h" -class LLFileSystem; +class LLVFile; class LLTransferSourceParamsAsset : public LLTransferSourceParams { @@ -56,7 +56,7 @@ public: LLTransferSourceAsset(const LLUUID &request_id, const F32 priority); virtual ~LLTransferSourceAsset(); - static void responderCallback(const LLUUID& uuid, LLAssetType::EType type, + static void responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void *user_data, S32 result, LLExtStat ext_status ); protected: /*virtual*/ void initTransfer(); diff --git a/indra/llmessage/lltransfertargetvfile.cpp b/indra/llmessage/lltransfertargetvfile.cpp index f6faadf87f..b27f0881e0 100644 --- a/indra/llmessage/lltransfertargetvfile.cpp +++ b/indra/llmessage/lltransfertargetvfile.cpp @@ -30,7 +30,7 @@ #include "lldatapacker.h" #include "llerror.h" -#include "llfilesystem.h" +#include "llvfile.h" //static void LLTransferTargetVFile::updateQueue(bool shutdown) @@ -138,9 +138,10 @@ LLTSCode LLTransferTargetVFile::dataCallback(const S32 packet_id, U8 *in_datap, //LL_INFOS() << "LLTransferTargetFile::dataCallback" << LL_ENDL; //LL_INFOS() << "Packet: " << packet_id << LL_ENDL; - LLFileSystem vf(mTempID, mParams.getAssetType(), LLFileSystem::APPEND); + LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); if (mNeedsCreate) { + vf.setMaxSize(mSize); mNeedsCreate = FALSE; } @@ -175,7 +176,7 @@ void LLTransferTargetVFile::completionCallback(const LLTSCode status) case LLTS_DONE: if (!mNeedsCreate) { - LLFileSystem file(mTempID, mParams.getAssetType(), LLFileSystem::WRITE); + LLVFile file(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::WRITE); if (!file.rename(mParams.getAssetID(), mParams.getAssetType())) { LL_ERRS() << "LLTransferTargetVFile: rename failed" << LL_ENDL; @@ -194,7 +195,7 @@ void LLTransferTargetVFile::completionCallback(const LLTSCode status) { // We're aborting this transfer, we don't want to keep this file. LL_WARNS() << "Aborting vfile transfer for " << mParams.getAssetID() << LL_ENDL; - LLFileSystem vf(mTempID, mParams.getAssetType(), LLFileSystem::APPEND); + LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); vf.remove(); } break; diff --git a/indra/llmessage/lltransfertargetvfile.h b/indra/llmessage/lltransfertargetvfile.h index 39a9125f1b..c819c1e2f2 100644 --- a/indra/llmessage/lltransfertargetvfile.h +++ b/indra/llmessage/lltransfertargetvfile.h @@ -29,9 +29,9 @@ #include "lltransfermanager.h" #include "llassetstorage.h" -#include "llfilesystem.h" +#include "llvfile.h" -class LLFileSystem; +class LLVFile; // Lame, an S32 for now until I figure out the deal with how we want to do // error codes. diff --git a/indra/llmessage/llxfer_vfile.cpp b/indra/llmessage/llxfer_vfile.cpp index 12419b342d..ddc24342f6 100644 --- a/indra/llmessage/llxfer_vfile.cpp +++ b/indra/llmessage/llxfer_vfile.cpp @@ -30,7 +30,8 @@ #include "lluuid.h" #include "llerror.h" #include "llmath.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "lldir.h" // size of chunks read from/written to disk @@ -41,13 +42,13 @@ const U32 LL_MAX_XFER_FILE_BUFFER = 65536; LLXfer_VFile::LLXfer_VFile () : LLXfer(-1) { - init(LLUUID::null, LLAssetType::AT_NONE); + init(NULL, LLUUID::null, LLAssetType::AT_NONE); } -LLXfer_VFile::LLXfer_VFile (const LLUUID &local_id, LLAssetType::EType type) +LLXfer_VFile::LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) : LLXfer(-1) { - init(local_id, type); + init(vfs, local_id, type); } /////////////////////////////////////////////////////////// @@ -59,8 +60,10 @@ LLXfer_VFile::~LLXfer_VFile () /////////////////////////////////////////////////////////// -void LLXfer_VFile::init (const LLUUID &local_id, LLAssetType::EType type) +void LLXfer_VFile::init (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) { + + mVFS = vfs; mLocalID = local_id; mType = type; @@ -79,14 +82,14 @@ void LLXfer_VFile::cleanup () if (mTempID.notNull() && mDeleteTempFile) { - if (LLFileSystem::getExists(mTempID, mType)) + if (mVFS->getExists(mTempID, mType)) { - LLFileSystem file(mTempID, mType, LLFileSystem::WRITE); + LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); file.remove(); } else { - LL_WARNS("Xfer") << "LLXfer_VFile::cleanup() can't open to delete cache file " << mTempID << "." << LLAssetType::lookup(mType) + LL_WARNS("Xfer") << "LLXfer_VFile::cleanup() can't open to delete VFS file " << mTempID << "." << LLAssetType::lookup(mType) << ", mRemoteID is " << mRemoteID << LL_ENDL; } } @@ -100,6 +103,7 @@ void LLXfer_VFile::cleanup () /////////////////////////////////////////////////////////// S32 LLXfer_VFile::initializeRequest(U64 xfer_id, + LLVFS* vfs, const LLUUID& local_id, const LLUUID& remote_id, LLAssetType::EType type, @@ -111,6 +115,7 @@ S32 LLXfer_VFile::initializeRequest(U64 xfer_id, mRemoteHost = remote_host; + mVFS = vfs; mLocalID = local_id; mRemoteID = remote_id; mType = type; @@ -187,13 +192,13 @@ S32 LLXfer_VFile::startSend (U64 xfer_id, const LLHost &remote_host) delete mVFile; mVFile = NULL; - if(LLFileSystem::getExists(mLocalID, mType)) + if(mVFS->getExists(mLocalID, mType)) { - mVFile = new LLFileSystem(mLocalID, mType, LLFileSystem::READ); + mVFile = new LLVFile(mVFS, mLocalID, mType, LLVFile::READ); if (mVFile->getSize() <= 0) { - LL_WARNS("Xfer") << "LLXfer_VFile::startSend() cache file " << mLocalID << "." << LLAssetType::lookup(mType) + LL_WARNS("Xfer") << "LLXfer_VFile::startSend() VFS file " << mLocalID << "." << LLAssetType::lookup(mType) << " has unexpected file size of " << mVFile->getSize() << LL_ENDL; delete mVFile; mVFile = NULL; @@ -209,7 +214,7 @@ S32 LLXfer_VFile::startSend (U64 xfer_id, const LLHost &remote_host) } else { - LL_WARNS("Xfer") << "LLXfer_VFile::startSend() can't read cache file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::startSend() can't read VFS file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; retval = LL_ERR_FILE_NOT_FOUND; } @@ -235,13 +240,13 @@ S32 LLXfer_VFile::reopenFileHandle() if (mVFile == NULL) { - if (LLFileSystem::getExists(mLocalID, mType)) + if (mVFS->getExists(mLocalID, mType)) { - mVFile = new LLFileSystem(mLocalID, mType, LLFileSystem::READ); + mVFile = new LLVFile(mVFS, mLocalID, mType, LLVFile::READ); } else { - LL_WARNS("Xfer") << "LLXfer_VFile::reopenFileHandle() can't read cache file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::reopenFileHandle() can't read VFS file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; retval = LL_ERR_FILE_NOT_FOUND; } } @@ -260,7 +265,8 @@ void LLXfer_VFile::setXferSize (S32 xfer_size) // It would be nice if LLXFers could tell which end of the pipe they were if (! mVFile) { - LLFileSystem file(mTempID, mType, LLFileSystem::APPEND); + LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); + file.setMaxSize(xfer_size); } } @@ -314,7 +320,7 @@ S32 LLXfer_VFile::flush() S32 retval = 0; if (mBufferLength) { - LLFileSystem file(mTempID, mType, LLFileSystem::APPEND); + LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); file.write((U8*)mBuffer, mBufferLength); @@ -334,15 +340,22 @@ S32 LLXfer_VFile::processEOF() if (!mCallbackResult) { - if (LLFileSystem::getExists(mTempID, mType)) + if (mVFS->getExists(mTempID, mType)) { - LLFileSystem file(mTempID, mType, LLFileSystem::WRITE); + LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); if (!file.rename(mLocalID, mType)) { - LL_WARNS("Xfer") << "Cache rename of temp file failed: unable to rename " << mTempID << " to " << mLocalID << LL_ENDL; + LL_WARNS("Xfer") << "VFS rename of temp file failed: unable to rename " << mTempID << " to " << mLocalID << LL_ENDL; } else - { + { + #ifdef VFS_SPAM + // Debugging spam + LL_INFOS("Xfer") << "VFS rename of temp file done: renamed " << mTempID << " to " << mLocalID + << " LLVFile size is " << file.getSize() + << LL_ENDL; + #endif + // Rename worked: the original file is gone. Clear mDeleteTempFile // so we don't attempt to delete the file in cleanup() mDeleteTempFile = FALSE; @@ -350,7 +363,7 @@ S32 LLXfer_VFile::processEOF() } else { - LL_WARNS("Xfer") << "LLXfer_VFile::processEOF() can't open for renaming cache file " << mTempID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::processEOF() can't open for renaming VFS file " << mTempID << "." << LLAssetType::lookup(mType) << LL_ENDL; } } diff --git a/indra/llmessage/llxfer_vfile.h b/indra/llmessage/llxfer_vfile.h index d82bab5f6c..5bf9a5cfba 100644 --- a/indra/llmessage/llxfer_vfile.h +++ b/indra/llmessage/llxfer_vfile.h @@ -30,7 +30,8 @@ #include "llxfer.h" #include "llassetstorage.h" -class LLFileSystem; +class LLVFS; +class LLVFile; class LLXfer_VFile : public LLXfer { @@ -40,7 +41,9 @@ class LLXfer_VFile : public LLXfer LLUUID mTempID; LLAssetType::EType mType; - LLFileSystem *mVFile; + LLVFile *mVFile; + + LLVFS *mVFS; std::string mName; @@ -48,13 +51,14 @@ class LLXfer_VFile : public LLXfer public: LLXfer_VFile (); - LLXfer_VFile (const LLUUID &local_id, LLAssetType::EType type); + LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); virtual ~LLXfer_VFile(); - virtual void init(const LLUUID &local_id, LLAssetType::EType type); + virtual void init(LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); virtual void cleanup(); virtual S32 initializeRequest(U64 xfer_id, + LLVFS *vfs, const LLUUID &local_id, const LLUUID &remote_id, const LLAssetType::EType type, diff --git a/indra/llmessage/llxfermanager.cpp b/indra/llmessage/llxfermanager.cpp index f9b59d7e42..4cea886c8a 100644 --- a/indra/llmessage/llxfermanager.cpp +++ b/indra/llmessage/llxfermanager.cpp @@ -56,9 +56,9 @@ const S32 LL_DEFAULT_MAX_HARD_LIMIT_SIMULTANEOUS_XFERS = 500; /////////////////////////////////////////////////////////// -LLXferManager::LLXferManager () +LLXferManager::LLXferManager (LLVFS *vfs) { - init(); + init(vfs); } /////////////////////////////////////////////////////////// @@ -70,7 +70,7 @@ LLXferManager::~LLXferManager () /////////////////////////////////////////////////////////// -void LLXferManager::init() +void LLXferManager::init (LLVFS *vfs) { cleanup(); @@ -78,6 +78,8 @@ void LLXferManager::init() setHardLimitOutgoingXfersPerCircuit(LL_DEFAULT_MAX_HARD_LIMIT_SIMULTANEOUS_XFERS); setMaxIncomingXfers(LL_DEFAULT_MAX_REQUEST_FIFO_XFERS); + mVFS = vfs; + // Turn on or off ack throttling mUseAckThrottling = FALSE; setAckThrottleBPS(100000); @@ -460,7 +462,7 @@ U64 LLXferManager::requestFile(const std::string& local_filename, void LLXferManager::requestVFile(const LLUUID& local_id, const LLUUID& remote_id, - LLAssetType::EType type, + LLAssetType::EType type, LLVFS* vfs, const LLHost& remote_host, void (*callback)(void**,S32,LLExtStat), void** user_data, @@ -506,6 +508,7 @@ void LLXferManager::requestVFile(const LLUUID& local_id, addToList(xfer_p, mReceiveList, is_priority); ((LLXfer_VFile *)xfer_p)->initializeRequest(getNextID(), + vfs, local_id, remote_id, type, @@ -781,17 +784,33 @@ void LLXferManager::processFileRequest (LLMessageSystem *mesgsys, void ** /*user LLXfer *xferp; if (uuid != LLUUID::null) - { // Request for an asset - use a cache file + { // Request for an asset - use a VFS file if(NULL == LLAssetType::lookup(type)) { LL_WARNS("Xfer") << "Invalid type for xfer request: " << uuid << ":" << type_s16 << " to " << mesgsys->getSender() << LL_ENDL; return; } + + if (! mVFS) + { + LL_WARNS("Xfer") << "Attempt to send VFile w/o available VFS" << LL_ENDL; + return; + } + + /* Present in fireengine, not used by viewer + if (!validateVFileForTransfer(uuid.asString())) + { + // it is up to the app sending the file to mark it for expected + // transfer before the request arrives or it will be dropped + LL_WARNS("Xfer") << "SECURITY: Unapproved VFile '" << uuid << "'" << LL_ENDL; + return; + } + */ LL_INFOS("Xfer") << "starting vfile transfer: " << uuid << "," << LLAssetType::lookup(type) << " to " << mesgsys->getSender() << LL_ENDL; - xferp = (LLXfer *)new LLXfer_VFile(uuid, type); + xferp = (LLXfer *)new LLXfer_VFile(mVFS, uuid, type); if (xferp) { mSendList.push_front(xferp); @@ -1254,9 +1273,9 @@ void LLXferManager::addToList(LLXfer* xferp, xfer_list_t & xfer_list, BOOL is_pr LLXferManager *gXferManager = NULL; -void start_xfer_manager() +void start_xfer_manager(LLVFS *vfs) { - gXferManager = new LLXferManager(); + gXferManager = new LLXferManager(vfs); } void cleanup_xfer_manager() diff --git a/indra/llmessage/llxfermanager.h b/indra/llmessage/llxfermanager.h index f49209bed0..45ae2ffdd3 100644 --- a/indra/llmessage/llxfermanager.h +++ b/indra/llmessage/llxfermanager.h @@ -35,6 +35,7 @@ //Forward declaration to avoid circular dependencies class LLXfer; +class LLVFS; #include "llxfer.h" #include "message.h" @@ -71,6 +72,9 @@ public: class LLXferManager { + private: + LLVFS *mVFS; + protected: S32 mMaxOutgoingXfersPerCircuit; S32 mHardLimitOutgoingXfersPerCircuit; // At this limit, kill off the connection @@ -107,10 +111,10 @@ class LLXferManager std::multiset mExpectedVFileRequests; // files that are authorized to be downloaded on top of public: - LLXferManager(); + LLXferManager(LLVFS *vfs); virtual ~LLXferManager(); - virtual void init(); + virtual void init(LLVFS *vfs); virtual void cleanup(); void setUseAckThrottling(const BOOL use); @@ -162,7 +166,7 @@ class LLXferManager // vfile requesting // .. to vfile virtual void requestVFile(const LLUUID &local_id, const LLUUID& remote_id, - LLAssetType::EType type, + LLAssetType::EType type, LLVFS* vfs, const LLHost& remote_host, void (*callback)(void**,S32,LLExtStat), void** user_data, BOOL is_priority = FALSE); @@ -209,7 +213,7 @@ class LLXferManager extern LLXferManager* gXferManager; // initialization and garbage collection -void start_xfer_manager(); +void start_xfer_manager(LLVFS *vfs); void cleanup_xfer_manager(); // message system callbacks diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index baab09a104..47e7ad915b 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -9,9 +9,10 @@ include(LLCommon) include(LLImage) include(LLMath) include(LLRender) +include(LLVFS) include(LLWindow) include(LLXML) -include(LLFileSystem) +include(LLVFS) include_directories( ${FREETYPE_INCLUDE_DIRS} @@ -19,9 +20,10 @@ include_directories( ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ) include_directories(SYSTEM ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -102,8 +104,9 @@ if (BUILD_HEADLESS) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_HEADLESS_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLWINDOW_HEADLESS_LIBRARIES} ${OPENGL_HEADLESS_LIBRARIES}) @@ -123,8 +126,9 @@ target_link_libraries(llrender ${LLCOMMON_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLWINDOW_LIBRARIES} ${FREETYPE_LIBRARIES} ${OPENGL_LIBRARIES}) diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 7401e6130a..cce618487b 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -13,7 +13,7 @@ include(LLCoreHttp) include(LLRender) include(LLWindow) include(LLCoreHttp) -include(LLFileSystem) +include(LLVFS) include(LLXML) include_directories( @@ -25,7 +25,7 @@ include_directories( ${LLMESSAGE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LIBS_PREBUILD_DIR}/include/hunspell ) @@ -283,7 +283,7 @@ target_link_libraries(llui ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLCOREHTTP_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} # ugh, just for LLDir ${LLXUIXML_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} diff --git a/indra/llui/llviewereventrecorder.h b/indra/llui/llviewereventrecorder.h index 94e66f5dc4..d1059d55de 100644 --- a/indra/llui/llviewereventrecorder.h +++ b/indra/llui/llviewereventrecorder.h @@ -32,6 +32,7 @@ #include "lldir.h" #include "llsd.h" #include "llfile.h" +#include "llvfile.h" #include "lldate.h" #include "llsdserialize.h" #include "llkeyboard.h" diff --git a/indra/llfilesystem/CMakeLists.txt b/indra/llvfs/CMakeLists.txt similarity index 51% rename from indra/llfilesystem/CMakeLists.txt rename to indra/llvfs/CMakeLists.txt index 09c4c33ebf..67dce8c073 100644 --- a/indra/llfilesystem/CMakeLists.txt +++ b/indra/llvfs/CMakeLists.txt @@ -1,6 +1,6 @@ # -*- cmake -*- -project(llfilesystem) +project(llvfs) include(00-Common) include(LLCommon) @@ -11,34 +11,39 @@ include_directories( ${LLCOMMON_SYSTEM_INCLUDE_DIRS} ) -set(llfilesystem_SOURCE_FILES +set(llvfs_SOURCE_FILES lldir.cpp lldiriterator.cpp lllfsthread.cpp - lldiskcache.cpp - llfilesystem.cpp + llpidlock.cpp + llvfile.cpp + llvfs.cpp + llvfsthread.cpp ) -set(llfilesystem_HEADER_FILES +set(llvfs_HEADER_FILES CMakeLists.txt + lldir.h lldirguard.h lldiriterator.h lllfsthread.h - lldiskcache.h - llfilesystem.h + llpidlock.h + llvfile.h + llvfs.h + llvfsthread.h ) if (DARWIN) - LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.mm) - LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.h) - LIST(APPEND llfilesystem_SOURCE_FILES lldir_mac.cpp) - LIST(APPEND llfilesystem_HEADER_FILES lldir_mac.h) + LIST(APPEND llvfs_SOURCE_FILES lldir_mac.cpp) + LIST(APPEND llvfs_HEADER_FILES lldir_mac.h) + LIST(APPEND llvfs_SOURCE_FILES llvfs_objc.mm) + LIST(APPEND llvfs_HEADER_FILES llvfs_objc.h) endif (DARWIN) if (LINUX) - LIST(APPEND llfilesystem_SOURCE_FILES lldir_linux.cpp) - LIST(APPEND llfilesystem_HEADER_FILES lldir_linux.h) + LIST(APPEND llvfs_SOURCE_FILES lldir_linux.cpp) + LIST(APPEND llvfs_HEADER_FILES lldir_linux.h) if (INSTALL) set_source_files_properties(lldir_linux.cpp @@ -49,31 +54,31 @@ if (LINUX) endif (LINUX) if (WINDOWS) - LIST(APPEND llfilesystem_SOURCE_FILES lldir_win32.cpp) - LIST(APPEND llfilesystem_HEADER_FILES lldir_win32.h) + LIST(APPEND llvfs_SOURCE_FILES lldir_win32.cpp) + LIST(APPEND llvfs_HEADER_FILES lldir_win32.h) endif (WINDOWS) -set_source_files_properties(${llfilesystem_HEADER_FILES} +set_source_files_properties(${llvfs_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) -list(APPEND llfilesystem_SOURCE_FILES ${llfilesystem_HEADER_FILES}) +list(APPEND llvfs_SOURCE_FILES ${llvfs_HEADER_FILES}) -add_library (llfilesystem ${llfilesystem_SOURCE_FILES}) +add_library (llvfs ${llvfs_SOURCE_FILES}) -set(cache_BOOST_LIBRARIES +set(vfs_BOOST_LIBRARIES ${BOOST_FILESYSTEM_LIBRARY} ${BOOST_SYSTEM_LIBRARY} ) -target_link_libraries(llfilesystem +target_link_libraries(llvfs ${LLCOMMON_LIBRARIES} - ${cache_BOOST_LIBRARIES} + ${vfs_BOOST_LIBRARIES} ) if (DARWIN) include(CMakeFindFrameworks) find_library(COCOA_LIBRARY Cocoa) - target_link_libraries(llfilesystem ${COCOA_LIBRARY}) + target_link_libraries(llvfs ${COCOA_LIBRARY}) endif (DARWIN) @@ -81,18 +86,18 @@ endif (DARWIN) if (LL_TESTS) include(LLAddBuildTest) # UNIT TESTS - SET(llfilesystem_TEST_SOURCE_FILES + SET(llvfs_TEST_SOURCE_FILES lldiriterator.cpp ) set_source_files_properties(lldiriterator.cpp PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES "${cache_BOOST_LIBRARIES}" + LL_TEST_ADDITIONAL_LIBRARIES "${vfs_BOOST_LIBRARIES}" ) - LL_ADD_PROJECT_UNIT_TESTS(llfilesystem "${llfilesystem_TEST_SOURCE_FILES}") + LL_ADD_PROJECT_UNIT_TESTS(llvfs "${llvfs_TEST_SOURCE_FILES}") # INTEGRATION TESTS - set(test_libs llmath llcommon llfilesystem ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) + set(test_libs llmath llcommon llvfs ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) # TODO: Some of these need refactoring to be proper Unit tests rather than Integration tests. LL_ADD_INTEGRATION_TEST(lldir "" "${test_libs}") diff --git a/indra/llfilesystem/lldir.cpp b/indra/llvfs/lldir.cpp similarity index 100% rename from indra/llfilesystem/lldir.cpp rename to indra/llvfs/lldir.cpp diff --git a/indra/llfilesystem/lldir.h b/indra/llvfs/lldir.h similarity index 100% rename from indra/llfilesystem/lldir.h rename to indra/llvfs/lldir.h diff --git a/indra/llfilesystem/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp similarity index 100% rename from indra/llfilesystem/lldir_linux.cpp rename to indra/llvfs/lldir_linux.cpp diff --git a/indra/llfilesystem/lldir_linux.h b/indra/llvfs/lldir_linux.h similarity index 100% rename from indra/llfilesystem/lldir_linux.h rename to indra/llvfs/lldir_linux.h diff --git a/indra/llfilesystem/lldir_mac.cpp b/indra/llvfs/lldir_mac.cpp similarity index 99% rename from indra/llfilesystem/lldir_mac.cpp rename to indra/llvfs/lldir_mac.cpp index 3bc4ee844e..87dc1b9795 100644 --- a/indra/llfilesystem/lldir_mac.cpp +++ b/indra/llvfs/lldir_mac.cpp @@ -36,7 +36,7 @@ #include #include #include -#include "lldir_utils_objc.h" +#include "llvfs_objc.h" // -------------------------------------------------------------------------------- diff --git a/indra/llfilesystem/lldir_mac.h b/indra/llvfs/lldir_mac.h similarity index 100% rename from indra/llfilesystem/lldir_mac.h rename to indra/llvfs/lldir_mac.h diff --git a/indra/llfilesystem/lldir_solaris.cpp b/indra/llvfs/lldir_solaris.cpp similarity index 100% rename from indra/llfilesystem/lldir_solaris.cpp rename to indra/llvfs/lldir_solaris.cpp diff --git a/indra/llfilesystem/lldir_solaris.h b/indra/llvfs/lldir_solaris.h similarity index 100% rename from indra/llfilesystem/lldir_solaris.h rename to indra/llvfs/lldir_solaris.h diff --git a/indra/llfilesystem/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp similarity index 100% rename from indra/llfilesystem/lldir_win32.cpp rename to indra/llvfs/lldir_win32.cpp diff --git a/indra/llfilesystem/lldir_win32.h b/indra/llvfs/lldir_win32.h similarity index 100% rename from indra/llfilesystem/lldir_win32.h rename to indra/llvfs/lldir_win32.h diff --git a/indra/llfilesystem/lldirguard.h b/indra/llvfs/lldirguard.h similarity index 100% rename from indra/llfilesystem/lldirguard.h rename to indra/llvfs/lldirguard.h diff --git a/indra/llfilesystem/lldiriterator.cpp b/indra/llvfs/lldiriterator.cpp similarity index 100% rename from indra/llfilesystem/lldiriterator.cpp rename to indra/llvfs/lldiriterator.cpp diff --git a/indra/llfilesystem/lldiriterator.h b/indra/llvfs/lldiriterator.h similarity index 100% rename from indra/llfilesystem/lldiriterator.h rename to indra/llvfs/lldiriterator.h diff --git a/indra/llfilesystem/lllfsthread.cpp b/indra/llvfs/lllfsthread.cpp similarity index 100% rename from indra/llfilesystem/lllfsthread.cpp rename to indra/llvfs/lllfsthread.cpp diff --git a/indra/llfilesystem/lllfsthread.h b/indra/llvfs/lllfsthread.h similarity index 100% rename from indra/llfilesystem/lllfsthread.h rename to indra/llvfs/lllfsthread.h diff --git a/indra/llvfs/llpidlock.cpp b/indra/llvfs/llpidlock.cpp new file mode 100644 index 0000000000..f770e93d45 --- /dev/null +++ b/indra/llvfs/llpidlock.cpp @@ -0,0 +1,276 @@ +/** + * @file llformat.cpp + * @date January 2007 + * @brief string formatting utility + * + * $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$ + */ + +#include "linden_common.h" + +#include "llapr.h" // thread-related functions +#include "llpidlock.h" +#include "lldir.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llnametable.h" +#include "llframetimer.h" + +#if LL_WINDOWS //For windows platform. + +#include + +bool isProcessAlive(U32 pid) +{ + return (bool) GetProcessVersion((DWORD)pid); +} + +#else //Everyone Else +bool isProcessAlive(U32 pid) +{ + return (bool) kill( (pid_t)pid, 0); +} +#endif //Everyone else. + + + +class LLPidLockFile +{ + public: + LLPidLockFile( ) : + mAutosave(false), + mSaving(false), + mWaiting(false), + mPID(getpid()), + mNameTable(NULL), + mClean(true) + { + mLockName = gDirUtilp->getTempDir() + gDirUtilp->getDirDelimiter() + "savelock"; + } + bool requestLock(LLNameTable *name_table, bool autosave, + bool force_immediate=FALSE, F32 timeout=300.0); + bool checkLock(); + void releaseLock(); + + private: + void writeLockFile(LLSD pids); + public: + static LLPidLockFile& instance(); // return the singleton black list file + + bool mAutosave; + bool mSaving; + bool mWaiting; + LLFrameTimer mTimer; + U32 mPID; + std::string mLockName; + std::string mSaveName; + LLSD mPIDS_sd; + LLNameTable *mNameTable; + bool mClean; +}; + +LLPidLockFile& LLPidLockFile::instance() +{ + static LLPidLockFile the_file; + return the_file; +} + +void LLPidLockFile::writeLockFile(LLSD pids) +{ + llofstream ofile(mLockName.c_str()); + + if (!LLSDSerialize::toXML(pids,ofile)) + { + LL_WARNS() << "Unable to write concurrent save lock file." << LL_ENDL; + } + ofile.close(); +} + +bool LLPidLockFile::requestLock(LLNameTable *name_table, bool autosave, + bool force_immediate, F32 timeout) +{ + bool readyToSave = FALSE; + + if (mSaving) return FALSE; //Bail out if we're currently saving. Will not queue another save. + + if (!mWaiting){ + mNameTable=name_table; + mAutosave = autosave; + } + + LLSD out_pids; + out_pids.append( (LLSD::Integer)mPID ); + + llifstream ifile(mLockName.c_str()); + + if (ifile.is_open()) + { //If file exists, we need to decide whether or not to continue. + if ( force_immediate + || mTimer.hasExpired() ) //Only deserialize if we REALLY need to. + { + + LLSD in_pids; + + LLSDSerialize::fromXML(in_pids, ifile); + + //Clean up any dead PIDS that might be in there. + for (LLSD::array_iterator i=in_pids.beginArray(); + i !=in_pids.endArray(); + ++i) + { + U32 stored_pid=(*i).asInteger(); + + if (isProcessAlive(stored_pid)) + { + out_pids.append( (*i) ); + } + } + + readyToSave=TRUE; + } + ifile.close(); + } + else + { + readyToSave=TRUE; + } + + if (!mWaiting) //Not presently waiting to save. Queue up. + { + mTimer.resetWithExpiry(timeout); + mWaiting=TRUE; + } + + if (readyToSave) + { //Potential race condition won't kill us. Ignore it. + writeLockFile(out_pids); + mSaving=TRUE; + } + + return readyToSave; +} + +bool LLPidLockFile::checkLock() +{ + return mWaiting; +} + +void LLPidLockFile::releaseLock() +{ + llifstream ifile(mLockName.c_str()); + LLSD in_pids; + LLSD out_pids; + bool write_file=FALSE; + + LLSDSerialize::fromXML(in_pids, ifile); + + //Clean up this PID and any dead ones. + for (LLSD::array_iterator i=in_pids.beginArray(); + i !=in_pids.endArray(); + ++i) + { + U32 stored_pid=(*i).asInteger(); + + if (stored_pid != mPID && isProcessAlive(stored_pid)) + { + out_pids.append( (*i) ); + write_file=TRUE; + } + } + ifile.close(); + + if (write_file) + { + writeLockFile(out_pids); + } + else + { + unlink(mLockName.c_str()); + } + + mSaving=FALSE; + mWaiting=FALSE; +} + +//LLPidLock + +void LLPidLock::initClass() { + (void) LLPidLockFile::instance(); +} + +bool LLPidLock::checkLock() +{ + return LLPidLockFile::instance().checkLock(); +} + +bool LLPidLock::requestLock(LLNameTable *name_table, bool autosave, + bool force_immediate, F32 timeout) +{ + return LLPidLockFile::instance().requestLock(name_table,autosave,force_immediate,timeout); +} + +void LLPidLock::releaseLock() +{ + return LLPidLockFile::instance().releaseLock(); +} + +bool LLPidLock::isClean() +{ + return LLPidLockFile::instance().mClean; +} + +//getters +LLNameTable * LLPidLock::getNameTable() +{ + return LLPidLockFile::instance().mNameTable; +} + +bool LLPidLock::getAutosave() +{ + return LLPidLockFile::instance().mAutosave; +} + +bool LLPidLock::getClean() +{ + return LLPidLockFile::instance().mClean; +} + +std::string LLPidLock::getSaveName() +{ + return LLPidLockFile::instance().mSaveName; +} + +//setters +void LLPidLock::setClean(bool clean) +{ + LLPidLockFile::instance().mClean=clean; +} + +void LLPidLock::setSaveName(std::string savename) +{ + LLPidLockFile::instance().mSaveName=savename; +} + +S32 LLPidLock::getPID() +{ + return (S32)getpid(); +} diff --git a/indra/llvfs/llpidlock.h b/indra/llvfs/llpidlock.h new file mode 100644 index 0000000000..334f26bb29 --- /dev/null +++ b/indra/llvfs/llpidlock.h @@ -0,0 +1,60 @@ +/** + * @file llpidlock.h + * @brief System information debugging classes. + * + * $LicenseInfo:firstyear=2001&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_PIDLOCK_H +#define LL_PIDLOCK_H +#include "llnametable.h" + +class LLSD; +class LLFrameTimer; + +#if !LL_WINDOWS //For non-windows platforms. +#include +#endif + +namespace LLPidLock +{ + void initClass(); // { (void) LLPidLockFile::instance(); } + + bool requestLock( LLNameTable *name_table=NULL, bool autosave=TRUE, + bool force_immediate=FALSE, F32 timeout=300.0); + bool checkLock(); + void releaseLock(); + bool isClean(); + + //getters + LLNameTable * getNameTable(); + bool getAutosave(); + bool getClean(); + std::string getSaveName(); + S32 getPID(); + + //setters + void setClean(bool clean); + void setSaveName(std::string savename); +}; + +#endif // LL_PIDLOCK_H diff --git a/indra/llvfs/llvfile.cpp b/indra/llvfs/llvfile.cpp new file mode 100644 index 0000000000..b8588e99f4 --- /dev/null +++ b/indra/llvfs/llvfile.cpp @@ -0,0 +1,437 @@ +/** + * @file llvfile.cpp + * @brief Implementation of virtual file + * + * $LicenseInfo:firstyear=2002&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 "llvfile.h" + +#include "llerror.h" +#include "llthread.h" +#include "lltimer.h" +#include "llfasttimer.h" +#include "llmemory.h" +#include "llvfs.h" + +const S32 LLVFile::READ = 0x00000001; +const S32 LLVFile::WRITE = 0x00000002; +const S32 LLVFile::READ_WRITE = 0x00000003; // LLVFile::READ & LLVFile::WRITE +const S32 LLVFile::APPEND = 0x00000006; // 0x00000004 & LLVFile::WRITE + +static LLTrace::BlockTimerStatHandle FTM_VFILE_WAIT("VFile Wait"); + +//---------------------------------------------------------------------------- +LLVFSThread* LLVFile::sVFSThread = NULL; +BOOL LLVFile::sAllocdVFSThread = FALSE; +//---------------------------------------------------------------------------- + +//============================================================================ + +LLVFile::LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode) +{ + mFileType = file_type; + + mFileID = file_id; + mPosition = 0; + mMode = mode; + mVFS = vfs; + + mBytesRead = 0; + mHandle = LLVFSThread::nullHandle(); + mPriority = 128.f; + + mVFS->incLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +LLVFile::~LLVFile() +{ + if (!isReadComplete()) + { + if (mHandle != LLVFSThread::nullHandle()) + { + if (!(mMode & LLVFile::WRITE)) + { + //LL_WARNS() << "Destroying LLVFile with pending async read/write, aborting..." << LL_ENDL; + sVFSThread->setFlags(mHandle, LLVFSThread::FLAG_AUTO_COMPLETE | LLVFSThread::FLAG_ABORT); + } + else // WRITE + { + sVFSThread->setFlags(mHandle, LLVFSThread::FLAG_AUTO_COMPLETE); + } + } + } + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +BOOL LLVFile::read(U8 *buffer, S32 bytes, BOOL async, F32 priority) +{ + if (! (mMode & READ)) + { + LL_WARNS() << "Attempt to read from file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + LL_WARNS() << "Attempt to read from vfile object " << mFileID << " with pending async operation" << LL_ENDL; + return FALSE; + } + mPriority = priority; + + BOOL success = TRUE; + + // We can't do a read while there are pending async writes + waitForLock(VFSLOCK_APPEND); + + // *FIX: (?) + if (async) + { + mHandle = sVFSThread->read(mVFS, mFileID, mFileType, buffer, mPosition, bytes, threadPri()); + } + else + { + // We can't do a read while there are pending async writes on this file + mBytesRead = sVFSThread->readImmediate(mVFS, mFileID, mFileType, buffer, mPosition, bytes); + mPosition += mBytesRead; + if (! mBytesRead) + { + success = FALSE; + } + } + + return success; +} + +//static +U8* LLVFile::readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read) +{ + U8 *data; + LLVFile file(vfs, uuid, type, LLVFile::READ); + S32 file_size = file.getSize(); + if (file_size == 0) + { + // File is empty. + data = NULL; + } + else + { + data = (U8*) ll_aligned_malloc<16>(file_size); + file.read(data, file_size); /* Flawfinder: ignore */ + + if (file.getLastBytesRead() != (S32)file_size) + { + ll_aligned_free<16>(data); + data = NULL; + file_size = 0; + } + } + if (bytes_read) + { + *bytes_read = file_size; + } + return data; +} + +void LLVFile::setReadPriority(const F32 priority) +{ + mPriority = priority; + if (mHandle != LLVFSThread::nullHandle()) + { + sVFSThread->setPriority(mHandle, threadPri()); + } +} + +BOOL LLVFile::isReadComplete() +{ + BOOL res = TRUE; + if (mHandle != LLVFSThread::nullHandle()) + { + LLVFSThread::Request* req = (LLVFSThread::Request*)sVFSThread->getRequest(mHandle); + LLVFSThread::status_t status = req->getStatus(); + if (status == LLVFSThread::STATUS_COMPLETE) + { + mBytesRead = req->getBytesRead(); + mPosition += mBytesRead; + sVFSThread->completeRequest(mHandle); + mHandle = LLVFSThread::nullHandle(); + } + else + { + res = FALSE; + } + } + return res; +} + +S32 LLVFile::getLastBytesRead() +{ + return mBytesRead; +} + +BOOL LLVFile::eof() +{ + return mPosition >= getSize(); +} + +BOOL LLVFile::write(const U8 *buffer, S32 bytes) +{ + if (! (mMode & WRITE)) + { + LL_WARNS() << "Attempt to write to file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; + } + if (mHandle != LLVFSThread::nullHandle()) + { + LL_ERRS() << "Attempt to write to vfile object " << mFileID << " with pending async operation" << LL_ENDL; + return FALSE; + } + BOOL success = TRUE; + + // *FIX: allow async writes? potential problem wit mPosition... + if (mMode == APPEND) // all appends are async (but WRITEs are not) + { + U8* writebuf = new U8[bytes]; + memcpy(writebuf, buffer, bytes); + S32 offset = -1; + mHandle = sVFSThread->write(mVFS, mFileID, mFileType, + writebuf, offset, bytes, + LLVFSThread::FLAG_AUTO_COMPLETE | LLVFSThread::FLAG_AUTO_DELETE); + mHandle = LLVFSThread::nullHandle(); // FLAG_AUTO_COMPLETE means we don't track this + } + else + { + // We can't do a write while there are pending reads or writes on this file + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + S32 pos = (mMode & APPEND) == APPEND ? -1 : mPosition; + + S32 wrote = sVFSThread->writeImmediate(mVFS, mFileID, mFileType, (U8*)buffer, pos, bytes); + + mPosition += wrote; + + if (wrote < bytes) + { + LL_WARNS() << "Tried to write " << bytes << " bytes, actually wrote " << wrote << LL_ENDL; + + success = FALSE; + } + } + return success; +} + +//static +BOOL LLVFile::writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +{ + LLVFile file(vfs, uuid, type, LLVFile::WRITE); + file.setMaxSize(bytes); + return file.write(buffer, bytes); +} + +BOOL LLVFile::seek(S32 offset, S32 origin) +{ + if (mMode == APPEND) + { + LL_WARNS() << "Attempt to seek on append-only file" << LL_ENDL; + return FALSE; + } + + if (-1 == origin) + { + origin = mPosition; + } + + S32 new_pos = origin + offset; + + S32 size = getSize(); // Calls waitForLock(VFSLOCK_APPEND) + + if (new_pos > size) + { + LL_WARNS() << "Attempt to seek past end of file" << LL_ENDL; + + mPosition = size; + return FALSE; + } + else if (new_pos < 0) + { + LL_WARNS() << "Attempt to seek past beginning of file" << LL_ENDL; + + mPosition = 0; + return FALSE; + } + + mPosition = new_pos; + return TRUE; +} + +S32 LLVFile::tell() const +{ + return mPosition; +} + +S32 LLVFile::getSize() +{ + waitForLock(VFSLOCK_APPEND); + S32 size = mVFS->getSize(mFileID, mFileType); + + return size; +} + +S32 LLVFile::getMaxSize() +{ + S32 size = mVFS->getMaxSize(mFileID, mFileType); + + return size; +} + +BOOL LLVFile::setMaxSize(S32 size) +{ + if (! (mMode & WRITE)) + { + LL_WARNS() << "Attempt to change size of file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; + + return FALSE; + } + + if (!mVFS->checkAvailable(size)) + { + //LL_RECORD_BLOCK_TIME(FTM_VFILE_WAIT); + S32 count = 0; + while (sVFSThread->getPending() > 1000) + { + if (count % 100 == 0) + { + LL_INFOS() << "VFS catching up... Pending: " << sVFSThread->getPending() << LL_ENDL; + } + if (sVFSThread->isPaused()) + { + sVFSThread->update(0); + } + ms_sleep(10); + } + } + return mVFS->setMaxSize(mFileID, mFileType, size); +} + +BOOL LLVFile::rename(const LLUUID &new_id, const LLAssetType::EType new_type) +{ + if (! (mMode & WRITE)) + { + LL_WARNS() << "Attempt to rename file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; + + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + LL_WARNS() << "Renaming file with pending async read" << LL_ENDL; + } + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + // we need to release / replace our own lock + // since the renamed file will inherit locks from the new name + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); + mVFS->renameFile(mFileID, mFileType, new_id, new_type); + mVFS->incLock(new_id, new_type, VFSLOCK_OPEN); + + mFileID = new_id; + mFileType = new_type; + + return TRUE; +} + +BOOL LLVFile::remove() +{ +// LL_INFOS() << "Removing file " << mFileID << LL_ENDL; + + if (! (mMode & WRITE)) + { + // Leaving paranoia warning just because this should be a very infrequent + // operation. + LL_WARNS() << "Remove file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + LL_WARNS() << "Removing file with pending async read" << LL_ENDL; + } + + // why not seek back to the beginning of the file too? + mPosition = 0; + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + mVFS->removeFile(mFileID, mFileType); + + return TRUE; +} + +// static +void LLVFile::initClass(LLVFSThread* vfsthread) +{ + if (!vfsthread) + { + if (LLVFSThread::sLocal != NULL) + { + vfsthread = LLVFSThread::sLocal; + } + else + { + vfsthread = new LLVFSThread(); + sAllocdVFSThread = TRUE; + } + } + sVFSThread = vfsthread; +} + +// static +void LLVFile::cleanupClass() +{ + if (sAllocdVFSThread) + { + delete sVFSThread; + } + sVFSThread = NULL; +} + +bool LLVFile::isLocked(EVFSLock lock) +{ + return mVFS->isLocked(mFileID, mFileType, lock) ? true : false; +} + +void LLVFile::waitForLock(EVFSLock lock) +{ + //LL_RECORD_BLOCK_TIME(FTM_VFILE_WAIT); + // spin until the lock clears + while (isLocked(lock)) + { + if (sVFSThread->isPaused()) + { + sVFSThread->update(0); + } + ms_sleep(1); + } +} diff --git a/indra/llvfs/llvfile.h b/indra/llvfs/llvfile.h new file mode 100644 index 0000000000..7e9d9f73e5 --- /dev/null +++ b/indra/llvfs/llvfile.h @@ -0,0 +1,90 @@ +/** + * @file llvfile.h + * @brief Definition of virtual file + * + * $LicenseInfo:firstyear=2002&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_LLVFILE_H +#define LL_LLVFILE_H + +#include "lluuid.h" +#include "llassettype.h" +#include "llvfs.h" +#include "llvfsthread.h" + +class LLVFile +{ +public: + LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode = LLVFile::READ); + ~LLVFile(); + + BOOL read(U8 *buffer, S32 bytes, BOOL async = FALSE, F32 priority = 128.f); /* Flawfinder: ignore */ + static U8* readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read = 0); + void setReadPriority(const F32 priority); + BOOL isReadComplete(); + S32 getLastBytesRead(); + BOOL eof(); + + BOOL write(const U8 *buffer, S32 bytes); + static BOOL writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); + BOOL seek(S32 offset, S32 origin = -1); + S32 tell() const; + + S32 getSize(); + S32 getMaxSize(); + BOOL setMaxSize(S32 size); + BOOL rename(const LLUUID &new_id, const LLAssetType::EType new_type); + BOOL remove(); + + bool isLocked(EVFSLock lock); + void waitForLock(EVFSLock lock); + + static void initClass(LLVFSThread* vfsthread = NULL); + static void cleanupClass(); + static LLVFSThread* getVFSThread() { return sVFSThread; } + +protected: + static LLVFSThread* sVFSThread; + static BOOL sAllocdVFSThread; + U32 threadPri() { return LLVFSThread::PRIORITY_NORMAL + llmin((U32)mPriority,(U32)0xfff); } + +public: + static const S32 READ; + static const S32 WRITE; + static const S32 READ_WRITE; + static const S32 APPEND; + +protected: + LLAssetType::EType mFileType; + + LLUUID mFileID; + S32 mPosition; + S32 mMode; + LLVFS *mVFS; + F32 mPriority; + + S32 mBytesRead; + LLVFSThread::handle_t mHandle; +}; + +#endif diff --git a/indra/llvfs/llvfs.cpp b/indra/llvfs/llvfs.cpp new file mode 100644 index 0000000000..617056d94d --- /dev/null +++ b/indra/llvfs/llvfs.cpp @@ -0,0 +1,2220 @@ +/** + * @file llvfs.cpp + * @brief Implementation of virtual file system + * + * $LicenseInfo:firstyear=2002&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 "llvfs.h" + +#include +#include +#include +#if LL_WINDOWS +#include +#elif LL_SOLARIS +#include +#include +#include +#else +#include +#endif + +#include "llstl.h" +#include "lltimer.h" + +const S32 FILE_BLOCK_MASK = 0x000003FF; // 1024-byte blocks +const S32 VFS_CLEANUP_SIZE = 5242880; // how much space we free up in a single stroke +const S32 BLOCK_LENGTH_INVALID = -1; // mLength for invalid LLVFSFileBlocks + +LLVFS *gVFS = NULL; + +// internal class definitions +class LLVFSBlock +{ +public: + LLVFSBlock() + { + mLocation = 0; + mLength = 0; + } + + LLVFSBlock(U32 loc, S32 size) + { + mLocation = loc; + mLength = size; + } + + static bool locationSortPredicate( + const LLVFSBlock* lhs, + const LLVFSBlock* rhs) + { + return lhs->mLocation < rhs->mLocation; + } + +public: + U32 mLocation; + S32 mLength; // allocated block size +}; + +LLVFSFileSpecifier::LLVFSFileSpecifier() +: mFileID(), + mFileType( LLAssetType::AT_NONE ) +{ +} + +LLVFSFileSpecifier::LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + mFileID = file_id; + mFileType = file_type; +} + +bool LLVFSFileSpecifier::operator<(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID) + ? mFileType < rhs.mFileType + : mFileID < rhs.mFileID; +} + +bool LLVFSFileSpecifier::operator==(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID && + mFileType == rhs.mFileType); +} + + +class LLVFSFileBlock : public LLVFSBlock, public LLVFSFileSpecifier +{ +public: + LLVFSFileBlock() : LLVFSBlock(), LLVFSFileSpecifier() + { + init(); + } + + LLVFSFileBlock(const LLUUID &file_id, LLAssetType::EType file_type, U32 loc = 0, S32 size = 0) + : LLVFSBlock(loc, size), LLVFSFileSpecifier( file_id, file_type ) + { + init(); + } + + void init() + { + mSize = 0; + mIndexLocation = -1; + mAccessTime = (U32)time(NULL); + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + mLocks[(EVFSLock)i] = 0; + } + } + + #ifdef LL_LITTLE_ENDIAN + inline void swizzleCopy(void *dst, void *src, int size) { memcpy(dst, src, size); /* Flawfinder: ignore */} + + #else + + inline U32 swizzle32(U32 x) + { + return(((x >> 24) & 0x000000FF) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) |((x << 24) & 0xFF000000)); + } + + inline U16 swizzle16(U16 x) + { + return( ((x >> 8) & 0x000000FF) | ((x << 8) & 0x0000FF00) ); + } + + inline void swizzleCopy(void *dst, void *src, int size) + { + if(size == 4) + { + ((U32*)dst)[0] = swizzle32(((U32*)src)[0]); + } + else if(size == 2) + { + ((U16*)dst)[0] = swizzle16(((U16*)src)[0]); + } + else + { + // Perhaps this should assert... + memcpy(dst, src, size); /* Flawfinder: ignore */ + } + } + + #endif + + void serialize(U8 *buffer) + { + swizzleCopy(buffer, &mLocation, 4); + buffer += 4; + swizzleCopy(buffer, &mLength, 4); + buffer +=4; + swizzleCopy(buffer, &mAccessTime, 4); + buffer +=4; + memcpy(buffer, &mFileID.mData, 16); /* Flawfinder: ignore */ + buffer += 16; + S16 temp_type = mFileType; + swizzleCopy(buffer, &temp_type, 2); + buffer += 2; + swizzleCopy(buffer, &mSize, 4); + } + + void deserialize(U8 *buffer, const S32 index_loc) + { + mIndexLocation = index_loc; + + swizzleCopy(&mLocation, buffer, 4); + buffer += 4; + swizzleCopy(&mLength, buffer, 4); + buffer += 4; + swizzleCopy(&mAccessTime, buffer, 4); + buffer += 4; + memcpy(&mFileID.mData, buffer, 16); + buffer += 16; + S16 temp_type; + swizzleCopy(&temp_type, buffer, 2); + mFileType = (LLAssetType::EType)temp_type; + buffer += 2; + swizzleCopy(&mSize, buffer, 4); + } + + static BOOL insertLRU(LLVFSFileBlock* const& first, + LLVFSFileBlock* const& second) + { + return (first->mAccessTime == second->mAccessTime) + ? *first < *second + : first->mAccessTime < second->mAccessTime; + } + +public: + S32 mSize; + S32 mIndexLocation; // location of index entry + U32 mAccessTime; + BOOL mLocks[VFSLOCK_COUNT]; // number of outstanding locks of each type + + static const S32 SERIAL_SIZE; +}; + +// Helper structure for doing lru w/ stl... is there a simpler way? +struct LLVFSFileBlock_less +{ + bool operator()(LLVFSFileBlock* const& lhs, LLVFSFileBlock* const& rhs) const + { + return (LLVFSFileBlock::insertLRU(lhs, rhs)) ? true : false; + } +}; + + +const S32 LLVFSFileBlock::SERIAL_SIZE = 34; + + +LLVFS::LLVFS(const std::string& index_filename, const std::string& data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash) +: mRemoveAfterCrash(remove_after_crash), + mDataFP(NULL), + mIndexFP(NULL) +{ + mDataMutex = new LLMutex(); + + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + mLockCounts[i] = 0; + } + mValid = VFSVALID_OK; + mReadOnly = read_only; + mIndexFilename = index_filename; + mDataFilename = data_filename; + + const char *file_mode = mReadOnly ? "rb" : "r+b"; + + LL_INFOS("VFS") << "Attempting to open VFS index file " << mIndexFilename << LL_ENDL; + LL_INFOS("VFS") << "Attempting to open VFS data file " << mDataFilename << LL_ENDL; + + mDataFP = openAndLock(mDataFilename, file_mode, mReadOnly); + if (!mDataFP) + { + if (mReadOnly) + { + LL_WARNS("VFS") << "Can't find " << mDataFilename << " to open read-only VFS" << LL_ENDL; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + mDataFP = openAndLock(mDataFilename, "w+b", FALSE); + if (mDataFP) + { + // Since we're creating this data file, assume any index file is bogus + // remove the index, since this vfs is now blank + LLFile::remove(mIndexFilename); + } + else + { + LL_WARNS("VFS") << "Couldn't open vfs data file " + << mDataFilename << LL_ENDL; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + if (presize) + { + presizeDataFile(presize); + } + } + + // Did we leave this file open for writing last time? + // If so, close it and start over. + if (!mReadOnly && mRemoveAfterCrash) + { + llstat marker_info; + std::string marker = mDataFilename + ".open"; + if (!LLFile::stat(marker, &marker_info)) + { + // marker exists, kill the lock and the VFS files + unlockAndClose(mDataFP); + mDataFP = NULL; + + LL_WARNS("VFS") << "VFS: File left open on last run, removing old VFS file " << mDataFilename << LL_ENDL; + LLFile::remove(mIndexFilename); + LLFile::remove(mDataFilename); + LLFile::remove(marker); + + mDataFP = openAndLock(mDataFilename, "w+b", FALSE); + if (!mDataFP) + { + LL_WARNS("VFS") << "Can't open VFS data file in crash recovery" << LL_ENDL; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + if (presize) + { + presizeDataFile(presize); + } + } + } + + // determine the real file size + fseek(mDataFP, 0, SEEK_END); + U32 data_size = ftell(mDataFP); + + // read the index file + // make sure there's at least one file in it too + // if not, we'll treat this as a new vfs + llstat fbuf; + if (! LLFile::stat(mIndexFilename, &fbuf) && + fbuf.st_size >= LLVFSFileBlock::SERIAL_SIZE && + (mIndexFP = openAndLock(mIndexFilename, file_mode, mReadOnly)) // Yes, this is an assignment and not '==' + ) + { + std::vector buffer(fbuf.st_size); + size_t buf_offset = 0; + size_t nread = fread(&buffer[0], 1, fbuf.st_size, mIndexFP); + + std::vector files_by_loc; + + while (buf_offset < nread) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + + block->deserialize(&buffer[buf_offset], (S32)buf_offset); + + // Do sanity check on this block. + // Note that this skips zero size blocks, which helps VFS + // to heal after some errors. JC + if (block->mLength > 0 && + (U32)block->mLength <= data_size && + block->mLocation < data_size && + block->mSize > 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT) + { + mFileBlocks.insert(fileblock_map::value_type(*block, block)); + files_by_loc.push_back(block); + } + else + if (block->mLength && block->mSize > 0) + { + // this is corrupt, not empty + LL_WARNS("VFS") << "VFS corruption: " << block->mFileID << " (" << block->mFileType << ") at index " << block->mIndexLocation << " DS: " << data_size << LL_ENDL; + LL_WARNS("VFS") << "Length: " << block->mLength << "\tLocation: " << block->mLocation << "\tSize: " << block->mSize << LL_ENDL; + LL_WARNS("VFS") << "File has bad data - VFS removed" << LL_ENDL; + + delete block; + + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + LL_WARNS("VFS") << "Deleted corrupt VFS files " + << mDataFilename + << " and " + << mIndexFilename + << LL_ENDL; + + mValid = VFSVALID_BAD_CORRUPT; + return; + } + else + { + // this is a null or bad entry, skip it + mIndexHoles.push_back(buf_offset); + + delete block; + } + + buf_offset += LLVFSFileBlock::SERIAL_SIZE; + } + + std::sort( + files_by_loc.begin(), + files_by_loc.end(), + LLVFSFileBlock::locationSortPredicate); + + // There are 3 cases that have to be considered. + // 1. No blocks + // 2. One block. + // 3. Two or more blocks. + if (!files_by_loc.empty()) + { + // cur walks through the list. + std::vector::iterator cur = files_by_loc.begin(); + std::vector::iterator end = files_by_loc.end(); + LLVFSFileBlock* last_file_block = *cur; + + // Check to see if there is an empty space before the first file. + if (last_file_block->mLocation > 0) + { + // If so, create a free block. + addFreeBlock(new LLVFSBlock(0, last_file_block->mLocation)); + } + + // Walk through the 2nd+ block. If there is a free space + // between cur_file_block and last_file_block, add it to + // the free space collection. This block will not need to + // run in the case there is only one entry in the VFS. + ++cur; + while( cur != end ) + { + LLVFSFileBlock* cur_file_block = *cur; + + // Dupe check on the block + if (cur_file_block->mLocation == last_file_block->mLocation + && cur_file_block->mLength == last_file_block->mLength) + { + LL_WARNS("VFS") << "VFS: removing duplicate entry" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " size " << cur_file_block->mSize + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << LL_ENDL; + + // Duplicate entries. Nuke them both for safety. + mFileBlocks.erase(*cur_file_block); // remove ID/type entry + if (cur_file_block->mLength > 0) + { + // convert to hole + addFreeBlock( + new LLVFSBlock( + cur_file_block->mLocation, + cur_file_block->mLength)); + } + lockData(); // needed for sync() + sync(cur_file_block, TRUE); // remove first on disk + sync(last_file_block, TRUE); // remove last on disk + unlockData(); // needed for sync() + last_file_block = cur_file_block; + ++cur; + continue; + } + + // Figure out where the last block ended. + S32 loc = last_file_block->mLocation+last_file_block->mLength; + + // Figure out how much space there is between where + // the last block ended and this block begins. + S32 length = cur_file_block->mLocation - loc; + + // Check for more errors... Seeing if the current + // entry and the last entry make sense together. + if (length < 0 || loc < 0 || (U32)loc > data_size) + { + // Invalid VFS + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + LL_WARNS("VFS") << "VFS: overlapping entries" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << LL_ENDL; + + LL_WARNS("VFS") << "Deleted corrupt VFS files " + << mDataFilename + << " and " + << mIndexFilename + << LL_ENDL; + + mValid = VFSVALID_BAD_CORRUPT; + return; + } + + // we don't want to add empty blocks to the list... + if (length > 0) + { + addFreeBlock(new LLVFSBlock(loc, length)); + } + last_file_block = cur_file_block; + ++cur; + } + + // also note any empty space at the end + U32 loc = last_file_block->mLocation + last_file_block->mLength; + if (loc < data_size) + { + addFreeBlock(new LLVFSBlock(loc, data_size - loc)); + } + } + else // There where no blocks in the file. + { + addFreeBlock(new LLVFSBlock(0, data_size)); + } + } + else // Pre-existing index file wasn't opened + { + if (mReadOnly) + { + LL_WARNS("VFS") << "Can't find " << mIndexFilename << " to open read-only VFS" << LL_ENDL; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + + mIndexFP = openAndLock(mIndexFilename, "w+b", FALSE); + if (!mIndexFP) + { + LL_WARNS("VFS") << "Couldn't open an index file for the VFS, probably a sharing violation!" << LL_ENDL; + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + // no index file, start from scratch w/ 1GB allocation + LLVFSBlock *first_block = new LLVFSBlock(0, data_size ? data_size : 0x40000000); + addFreeBlock(first_block); + } + + // Open marker file to look for bad shutdowns + if (!mReadOnly && mRemoveAfterCrash) + { + std::string marker = mDataFilename + ".open"; + LLFILE* marker_fp = LLFile::fopen(marker, "w"); /* Flawfinder: ignore */ + if (marker_fp) + { + fclose(marker_fp); + marker_fp = NULL; + } + } + + LL_INFOS("VFS") << "Using VFS index file " << mIndexFilename << LL_ENDL; + LL_INFOS("VFS") << "Using VFS data file " << mDataFilename << LL_ENDL; + + mValid = VFSVALID_OK; +} + +LLVFS::~LLVFS() +{ + if (mDataMutex->isLocked()) + { + LL_ERRS("VFS") << "LLVFS destroyed with mutex locked" << LL_ENDL; + } + + unlockAndClose(mIndexFP); + mIndexFP = NULL; + + fileblock_map::const_iterator it; + for (it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + delete (*it).second; + } + mFileBlocks.clear(); + + mFreeBlocksByLength.clear(); + + for_each(mFreeBlocksByLocation.begin(), mFreeBlocksByLocation.end(), DeletePairedPointer()); + mFreeBlocksByLocation.clear(); + + unlockAndClose(mDataFP); + mDataFP = NULL; + + // Remove marker file + if (!mReadOnly && mRemoveAfterCrash) + { + std::string marker = mDataFilename + ".open"; + LLFile::remove(marker); + } + + delete mDataMutex; +} + + +// Use this function normally to create LLVFS files. +// Will append digits to the end of the filename with multiple re-trys +// static +LLVFS * LLVFS::createLLVFS(const std::string& index_filename, + const std::string& data_filename, + const BOOL read_only, + const U32 presize, + const BOOL remove_after_crash) +{ + LLVFS * new_vfs = new LLVFS(index_filename, data_filename, read_only, presize, remove_after_crash); + + if( !new_vfs->isValid() ) + { // First name failed, retry with new names + std::string retry_vfs_index_name; + std::string retry_vfs_data_name; + S32 count = 0; + while (!new_vfs->isValid() && + count < 256) + { // Append '.' to end of filenames + retry_vfs_index_name = index_filename + llformat(".%u",count); + retry_vfs_data_name = data_filename + llformat(".%u", count); + + delete new_vfs; // Delete bad VFS and try again + new_vfs = new LLVFS(retry_vfs_index_name, retry_vfs_data_name, read_only, presize, remove_after_crash); + + count++; + } + } + + if( !new_vfs->isValid() ) + { + delete new_vfs; // Delete bad VFS + new_vfs = NULL; // Total failure + } + + return new_vfs; +} + + + +void LLVFS::presizeDataFile(const U32 size) +{ + if (!mDataFP) + { + LL_ERRS() << "LLVFS::presizeDataFile() with no data file open" << LL_ENDL; + return; + } + + // we're creating this file for the first time, size it + fseek(mDataFP, size-1, SEEK_SET); + S32 tmp = 0; + tmp = (S32)fwrite(&tmp, 1, 1, mDataFP); + // fflush(mDataFP); + + // also remove any index, since this vfs is now blank + LLFile::remove(mIndexFilename); + + if (tmp) + { + LL_INFOS() << "Pre-sized VFS data file to " << ftell(mDataFP) << " bytes" << LL_ENDL; + } + else + { + LL_WARNS() << "Failed to pre-size VFS data file" << LL_ENDL; + } +} + +BOOL LLVFS::getExists(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + LLVFSFileBlock *block = NULL; + + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + block->mAccessTime = (U32)time(NULL); + } + + BOOL res = (block && block->mLength > 0) ? TRUE : FALSE; + + unlockData(); + + return res; +} + +S32 LLVFS::getSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mSize; + } + + unlockData(); + + return size; +} + +S32 LLVFS::getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mLength; + } + + unlockData(); + + return size; +} + +BOOL LLVFS::checkAvailable(S32 max_size) +{ + lockData(); + + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(max_size); // first entry >= size + const BOOL res(iter == mFreeBlocksByLength.end() ? FALSE : TRUE); + + unlockData(); + + return res; +} + +BOOL LLVFS::setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + if (mReadOnly) + { + LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; + } + if (max_size <= 0) + { + LL_WARNS() << "VFS: Attempt to assign size " << max_size << " to vfile " << file_id << LL_ENDL; + return FALSE; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block = NULL; + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + + // round all sizes upward to KB increments + // SJB: Need to not round for the new texture-pipeline code so we know the correct + // max file size. Need to investigate the potential problems with this... + if (file_type != LLAssetType::AT_TEXTURE) + { + if (max_size & FILE_BLOCK_MASK) + { + max_size += FILE_BLOCK_MASK; + max_size &= ~FILE_BLOCK_MASK; + } + } + + if (block && block->mLength > 0) + { + block->mAccessTime = (U32)time(NULL); + + if (max_size == block->mLength) + { + unlockData(); + return TRUE; + } + else if (max_size < block->mLength) + { + // this file is shrinking + LLVFSBlock *free_block = new LLVFSBlock(block->mLocation + max_size, block->mLength - max_size); + + addFreeBlock(free_block); + + block->mLength = max_size; + + if (block->mLength < block->mSize) + { + // JC: Was a warning, but Ian says it's bad. + LL_ERRS() << "Truncating virtual file " << file_id << " to " << block->mLength << " bytes" << LL_ENDL; + block->mSize = block->mLength; + } + + sync(block); + //mergeFreeBlocks(); + + unlockData(); + return TRUE; + } + else if (max_size > block->mLength) + { + // this file is growing + // first check for an adjacent free block to grow into + S32 size_increase = max_size - block->mLength; + + // Find the first free block with and addres > block->mLocation + LLVFSBlock *free_block; + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.upper_bound(block->mLocation); + if (iter != mFreeBlocksByLocation.end()) + { + free_block = iter->second; + + if (free_block->mLocation == block->mLocation + block->mLength && + free_block->mLength >= size_increase) + { + // this free block is at the end of the file and is large enough + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, size_increase); + block->mLength += size_increase; + sync(block); + + unlockData(); + return TRUE; + } + } + + // no adjecent free block, find one in the list + free_block = findFreeBlock(max_size, block); + + if (free_block) + { + // Save location where data is going, useFreeSpace will move free_block->mLocation; + U32 new_data_location = free_block->mLocation; + + //mark the free block as used so it does not + //interfere with other operations such as addFreeBlock + useFreeSpace(free_block, max_size); // useFreeSpace takes ownership (and may delete) free_block + + if (block->mLength > 0) + { + // create a new free block where this file used to be + LLVFSBlock *new_free_block = new LLVFSBlock(block->mLocation, block->mLength); + + addFreeBlock(new_free_block); + + if (block->mSize > 0) + { + // move the file into the new block + std::vector buffer(block->mSize); + fseek(mDataFP, block->mLocation, SEEK_SET); + if (fread(&buffer[0], block->mSize, 1, mDataFP) == 1) + { + fseek(mDataFP, new_data_location, SEEK_SET); + if (fwrite(&buffer[0], block->mSize, 1, mDataFP) != 1) + { + LL_WARNS() << "Short write" << LL_ENDL; + } + } else { + LL_WARNS() << "Short read" << LL_ENDL; + } + } + } + + block->mLocation = new_data_location; + + block->mLength = max_size; + + + sync(block); + + unlockData(); + return TRUE; + } + else + { + LL_WARNS() << "VFS: No space (" << max_size << ") to resize existing vfile " << file_id << LL_ENDL; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + } + else + { + // find a free block in the list + LLVFSBlock *free_block = findFreeBlock(max_size); + + if (free_block) + { + if (block) + { + block->mLocation = free_block->mLocation; + block->mLength = max_size; + } + else + { + // this file doesn't exist, create it + block = new LLVFSFileBlock(file_id, file_type, free_block->mLocation, max_size); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, max_size); + block->mAccessTime = (U32)time(NULL); + + sync(block); + } + else + { + LL_WARNS() << "VFS: No space (" << max_size << ") for new virtual file " << file_id << LL_ENDL; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + unlockData(); + return TRUE; +} + + +// WARNING: HERE BE DRAGONS! +// rename is the weirdest VFS op, because the file moves but the locks don't! +void LLVFS::renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + if (mReadOnly) + { + LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; + } + + lockData(); + + LLVFSFileSpecifier new_spec(new_id, new_type); + LLVFSFileSpecifier old_spec(file_id, file_type); + + fileblock_map::iterator it = mFileBlocks.find(old_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *src_block = (*it).second; + + // this will purge the data but leave the file block in place, w/ locks, if any + // WAS: removeFile(new_id, new_type); NOW uses removeFileBlock() to avoid mutex lock recursion + fileblock_map::iterator new_it = mFileBlocks.find(new_spec); + if (new_it != mFileBlocks.end()) + { + LLVFSFileBlock *new_block = (*new_it).second; + removeFileBlock(new_block); + } + + // if there's something in the target location, remove it but inherit its locks + it = mFileBlocks.find(new_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *dest_block = (*it).second; + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + if(dest_block->mLocks[i]) + { + LL_ERRS() << "Renaming VFS block to a locked file." << LL_ENDL; + } + dest_block->mLocks[i] = src_block->mLocks[i]; + } + + mFileBlocks.erase(new_spec); + delete dest_block; + } + + src_block->mFileID = new_id; + src_block->mFileType = new_type; + src_block->mAccessTime = (U32)time(NULL); + + mFileBlocks.erase(old_spec); + mFileBlocks.insert(fileblock_map::value_type(new_spec, src_block)); + + sync(src_block); + } + else + { + LL_WARNS() << "VFS: Attempt to rename nonexistent vfile " << file_id << ":" << file_type << LL_ENDL; + } + unlockData(); +} + +// mDataMutex must be LOCKED before calling this +void LLVFS::removeFileBlock(LLVFSFileBlock *fileblock) +{ + // convert this into an unsaved, dummy fileblock to preserve locks + // a more rubust solution would store the locks in a seperate data structure + sync(fileblock, TRUE); + + if (fileblock->mLength > 0) + { + // turn this file into an empty block + LLVFSBlock *free_block = new LLVFSBlock(fileblock->mLocation, fileblock->mLength); + + addFreeBlock(free_block); + } + + fileblock->mLocation = 0; + fileblock->mSize = 0; + fileblock->mLength = BLOCK_LENGTH_INVALID; + fileblock->mIndexLocation = -1; + + //mergeFreeBlocks(); +} + +void LLVFS::removeFile(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + if (mReadOnly) + { + LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + removeFileBlock(block); + } + else + { + LL_WARNS() << "VFS: attempting to remove nonexistent file " << file_id << " type " << file_type << LL_ENDL; + } + + unlockData(); +} + + +S32 LLVFS::getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length) +{ + S32 bytesread = 0; + + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + llassert(location >= 0); + llassert(length >= 0); + + BOOL do_read = FALSE; + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + + if (location > block->mSize) + { + LL_WARNS() << "VFS: Attempt to read location " << location << " in file " << file_id << " of length " << block->mSize << LL_ENDL; + } + else + { + if (length > block->mSize - location) + { + length = block->mSize - location; + } + location += block->mLocation; + do_read = TRUE; + } + } + + if (do_read) + { + fseek(mDataFP, location, SEEK_SET); + bytesread = (S32)fread(buffer, 1, length, mDataFP); + } + + unlockData(); + + return bytesread; +} + +S32 LLVFS::storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + if (mReadOnly) + { + LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; + } + + llassert(length > 0); + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + S32 in_loc = location; + if (location == -1) + { + location = block->mSize; + } + llassert(location >= 0); + + block->mAccessTime = (U32)time(NULL); + + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // Block was removed, ignore write + LL_WARNS() << "VFS: Attempt to write to invalid block" + << " in file " << file_id + << " location: " << in_loc + << " bytes: " << length + << LL_ENDL; + unlockData(); + return length; + } + else if (location > block->mLength) + { + LL_WARNS() << "VFS: Attempt to write to location " << location + << " in file " << file_id + << " type " << S32(file_type) + << " of size " << block->mSize + << " block length " << block->mLength + << LL_ENDL; + unlockData(); + return length; + } + else + { + if (length > block->mLength - location ) + { + LL_WARNS() << "VFS: Truncating write to virtual file " << file_id << " type " << S32(file_type) << LL_ENDL; + length = block->mLength - location; + } + U32 file_location = location + block->mLocation; + + fseek(mDataFP, file_location, SEEK_SET); + S32 write_len = (S32)fwrite(buffer, 1, length, mDataFP); + if (write_len != length) + { + LL_WARNS() << llformat("VFS Write Error: %d != %d",write_len,length) << LL_ENDL; + } + // fflush(mDataFP); + + if (location + length > block->mSize) + { + block->mSize = location + write_len; + sync(block); + } + unlockData(); + + return write_len; + } + } + else + { + unlockData(); + return 0; + } +} + +void LLVFS::incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block; + + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + else + { + // Create a dummy block which isn't saved + block = new LLVFSFileBlock(file_id, file_type, 0, BLOCK_LENGTH_INVALID); + block->mAccessTime = (U32)time(NULL); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + block->mLocks[lock]++; + mLockCounts[lock]++; + + unlockData(); +} + +void LLVFS::decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + if (block->mLocks[lock] > 0) + { + block->mLocks[lock]--; + } + else + { + LL_WARNS() << "VFS: Decrementing zero-value lock " << lock << LL_ENDL; + } + mLockCounts[lock]--; + } + + unlockData(); +} + +BOOL LLVFS::isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + BOOL res = FALSE; + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + res = (block->mLocks[lock] > 0); + } + + unlockData(); + + return res; +} + +//============================================================================ +// protected +//============================================================================ + +void LLVFS::eraseBlockLength(LLVFSBlock *block) +{ + // find the corresponding map entry in the length map and erase it + S32 length = block->mLength; + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(length); + blocks_length_map_t::iterator end = mFreeBlocksByLength.end(); + bool found_block = false; + while(iter != end) + { + LLVFSBlock *tblock = iter->second; + llassert(tblock->mLength == length); // there had -better- be an entry with our length! + if (tblock == block) + { + mFreeBlocksByLength.erase(iter); + found_block = true; + break; + } + ++iter; + } + if(!found_block) + { + LL_ERRS() << "eraseBlock could not find block" << LL_ENDL; + } +} + + +// Remove block from both free lists (by location and by length). +void LLVFS::eraseBlock(LLVFSBlock *block) +{ + eraseBlockLength(block); + // find the corresponding map entry in the location map and erase it + U32 location = block->mLocation; + llverify(mFreeBlocksByLocation.erase(location) == 1); // we should only have one entry per location. +} + + +// Add the region specified by block location and length to the free lists. +// Also incrementally defragment by merging with previous and next free blocks. +void LLVFS::addFreeBlock(LLVFSBlock *block) +{ +#if LL_DEBUG + size_t dbgcount = mFreeBlocksByLocation.count(block->mLocation); + if(dbgcount > 0) + { + LL_ERRS() << "addFreeBlock called with block already in list" << LL_ENDL; + } +#endif + + // Get a pointer to the next free block (by location). + blocks_location_map_t::iterator next_free_it = mFreeBlocksByLocation.lower_bound(block->mLocation); + + // We can merge with previous if it ends at our requested location. + LLVFSBlock* prev_block = NULL; + bool merge_prev = false; + if (next_free_it != mFreeBlocksByLocation.begin()) + { + blocks_location_map_t::iterator prev_free_it = next_free_it; + --prev_free_it; + prev_block = prev_free_it->second; + merge_prev = (prev_block->mLocation + prev_block->mLength == block->mLocation); + } + + // We can merge with next if our block ends at the next block's location. + LLVFSBlock* next_block = NULL; + bool merge_next = false; + if (next_free_it != mFreeBlocksByLocation.end()) + { + next_block = next_free_it->second; + merge_next = (block->mLocation + block->mLength == next_block->mLocation); + } + + if (merge_prev && merge_next) + { + // LL_INFOS() << "VFS merge BOTH" << LL_ENDL; + // Previous block is changing length (a lot), so only need to update length map. + // Next block is going away completely. JC + eraseBlockLength(prev_block); + eraseBlock(next_block); + prev_block->mLength += block->mLength + next_block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); + delete block; + block = NULL; + delete next_block; + next_block = NULL; + } + else if (merge_prev) + { + // LL_INFOS() << "VFS merge previous" << LL_ENDL; + // Previous block is maintaining location, only changing length, + // therefore only need to update the length map. JC + eraseBlockLength(prev_block); + prev_block->mLength += block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); // multimap insert + delete block; + block = NULL; + } + else if (merge_next) + { + // LL_INFOS() << "VFS merge next" << LL_ENDL; + // Next block is changing both location and length, + // so both free lists must update. JC + eraseBlock(next_block); + next_block->mLocation = block->mLocation; + next_block->mLength += block->mLength; + // Don't hint here, next_free_it iterator may be invalid. + mFreeBlocksByLocation.insert(blocks_location_map_t::value_type(next_block->mLocation, next_block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(next_block->mLength, next_block)); // multimap insert + delete block; + block = NULL; + } + else + { + // Can't merge with other free blocks. + // Hint that insert should go near next_free_it. + mFreeBlocksByLocation.insert(next_free_it, blocks_location_map_t::value_type(block->mLocation, block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(block->mLength, block)); // multimap insert + } +} + +// Superceeded by new addFreeBlock which does incremental free space merging. +// Incremental is faster overall. +//void LLVFS::mergeFreeBlocks() +//{ +// if (!isValid()) +// { +// LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; +// } +// // TODO: could we optimize this with hints from the calling code? +// blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); +// blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); +// LLVFSBlock *first_block = iter->second; +// while(iter != end) +// { +// blocks_location_map_t::iterator first_iter = iter; // save for if we do a merge +// if (++iter == end) +// break; +// LLVFSBlock *second_block = iter->second; +// if (first_block->mLocation + first_block->mLength == second_block->mLocation) +// { +// // remove the first block from the length map +// eraseBlockLength(first_block); +// // merge first_block with second_block, since they're adjacent +// first_block->mLength += second_block->mLength; +// // add the first block to the length map (with the new size) +// mFreeBlocksByLength.insert(blocks_length_map_t::value_type(first_block->mLength, first_block)); // multimap insert +// +// // erase and delete the second block +// eraseBlock(second_block); +// delete second_block; +// +// // reset iterator +// iter = first_iter; // haven't changed first_block, so corresponding iterator is still valid +// end = mFreeBlocksByLocation.end(); +// } +// first_block = second_block; +// } +//} + +// length bytes from free_block are going to be used (so they are no longer free) +void LLVFS::useFreeSpace(LLVFSBlock *free_block, S32 length) +{ + if (free_block->mLength == length) + { + eraseBlock(free_block); + delete free_block; + } + else + { + eraseBlock(free_block); + + free_block->mLocation += length; + free_block->mLength -= length; + + addFreeBlock(free_block); + } +} + +// NOTE! mDataMutex must be LOCKED before calling this +// sync this index entry out to the index file +// we need to do this constantly to avoid corruption on viewer crash +void LLVFS::sync(LLVFSFileBlock *block, BOOL remove) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + if (mReadOnly) + { + LL_WARNS() << "Attempt to sync read-only VFS" << LL_ENDL; + return; + } + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // This is a dummy file, don't save + return; + } + if (block->mLength == 0) + { + LL_ERRS() << "VFS syncing zero-length block" << LL_ENDL; + } + + BOOL set_index_to_end = FALSE; + long seek_pos = block->mIndexLocation; + + if (-1 == seek_pos) + { + if (!mIndexHoles.empty()) + { + seek_pos = mIndexHoles.front(); + mIndexHoles.pop_front(); + } + else + { + set_index_to_end = TRUE; + } + } + + if (set_index_to_end) + { + // Need fseek/ftell to update the seek_pos and hence data + // structures, so can't unlockData() before this. + fseek(mIndexFP, 0, SEEK_END); + seek_pos = ftell(mIndexFP); + } + + block->mIndexLocation = seek_pos; + if (remove) + { + mIndexHoles.push_back(seek_pos); + } + + U8 buffer[LLVFSFileBlock::SERIAL_SIZE]; + if (remove) + { + memset(buffer, 0, LLVFSFileBlock::SERIAL_SIZE); + } + else + { + block->serialize(buffer); + } + + // If set_index_to_end, file pointer is already at seek_pos + // and we don't need to do anything. Only seek if not at end. + if (!set_index_to_end) + { + fseek(mIndexFP, seek_pos, SEEK_SET); + } + + if (fwrite(buffer, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP) != 1) + { + LL_WARNS() << "Short write" << LL_ENDL; + } + + // *NOTE: Why was this commented out? + // fflush(mIndexFP); + + return; +} + +// mDataMutex must be LOCKED before calling this +// Can initiate LRU-based file removal to make space. +// The immune file block will not be removed. +LLVFSBlock *LLVFS::findFreeBlock(S32 size, LLVFSFileBlock *immune) +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + + LLVFSBlock *block = NULL; + BOOL have_lru_list = FALSE; + + typedef std::set lru_set; + lru_set lru_list; + + LLTimer timer; + + while (! block) + { + // look for a suitable free block + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(size); // first entry >= size + if (iter != mFreeBlocksByLength.end()) + block = iter->second; + + // no large enough free blocks, time to clean out some junk + if (! block) + { + // create a list of files sorted by usage time + // this is far faster than sorting a linked list + if (! have_lru_list) + { + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *tmp = (*it).second; + + if (tmp != immune && + tmp->mLength > 0 && + ! tmp->mLocks[VFSLOCK_READ] && + ! tmp->mLocks[VFSLOCK_APPEND] && + ! tmp->mLocks[VFSLOCK_OPEN]) + { + lru_list.insert(tmp); + } + } + + have_lru_list = TRUE; + } + + if (lru_list.size() == 0) + { + // No more files to delete, and still not enough room! + LL_WARNS() << "VFS: Can't make " << size << " bytes of free space in VFS, giving up" << LL_ENDL; + break; + } + + // is the oldest file big enough? (Should be about half the time) + lru_set::iterator it = lru_list.begin(); + LLVFSFileBlock *file_block = *it; + if (file_block->mLength >= size && file_block != immune) + { + // ditch this file and look again for a free block - should find it + // TODO: it'll be faster just to assign the free block and break + LL_INFOS() << "LRU: Removing " << file_block->mFileID << ":" << file_block->mFileType << LL_ENDL; + lru_list.erase(it); + removeFileBlock(file_block); + file_block = NULL; + continue; + } + + + LL_INFOS() << "VFS: LRU: Aggressive: " << (S32)lru_list.size() << " files remain" << LL_ENDL; + dumpLockCounts(); + + // Now it's time to aggressively make more space + // Delete the oldest 5MB of the vfs or enough to hold the file, which ever is larger + // This may yield too much free space, but we'll use it up soon enough + U32 cleanup_target = (size > VFS_CLEANUP_SIZE) ? size : VFS_CLEANUP_SIZE; + U32 cleaned_up = 0; + for (it = lru_list.begin(); + it != lru_list.end() && cleaned_up < cleanup_target; + ) + { + file_block = *it; + + // TODO: it would be great to be able to batch all these sync() calls + // LL_INFOS() << "LRU2: Removing " << file_block->mFileID << ":" << file_block->mFileType << " last accessed" << file_block->mAccessTime << LL_ENDL; + + cleaned_up += file_block->mLength; + lru_list.erase(it++); + removeFileBlock(file_block); + file_block = NULL; + } + //mergeFreeBlocks(); + } + } + + F32 time = timer.getElapsedTimeF32(); + if (time > 0.5f) + { + LL_WARNS() << "VFS: Spent " << time << " seconds in findFreeBlock!" << LL_ENDL; + } + + return block; +} + +//============================================================================ +// public +//============================================================================ + +void LLVFS::pokeFiles() +{ + if (!isValid()) + { + LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; + } + U32 word; + + // only write data if we actually read 4 bytes + // otherwise we're writing garbage and screwing up the file + fseek(mDataFP, 0, SEEK_SET); + if (fread(&word, sizeof(word), 1, mDataFP) == 1) + { + fseek(mDataFP, 0, SEEK_SET); + if (fwrite(&word, sizeof(word), 1, mDataFP) != 1) + { + LL_WARNS() << "Could not write to data file" << LL_ENDL; + } + fflush(mDataFP); + } + + fseek(mIndexFP, 0, SEEK_SET); + if (fread(&word, sizeof(word), 1, mIndexFP) == 1) + { + fseek(mIndexFP, 0, SEEK_SET); + if (fwrite(&word, sizeof(word), 1, mIndexFP) != 1) + { + LL_WARNS() << "Could not write to index file" << LL_ENDL; + } + fflush(mIndexFP); + } +} + + +void LLVFS::dumpMap() +{ + LL_INFOS() << "Files:" << LL_ENDL; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + LL_INFOS() << "Location: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << LL_ENDL; + } + + LL_INFOS() << "Free Blocks:" << LL_ENDL; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + LL_INFOS() << "Location: " << free_block->mLocation << "\tLength: " << free_block->mLength << LL_ENDL; + } +} + +// verify that the index file contents match the in-memory file structure +// Very slow, do not call routinely. JC +void LLVFS::audit() +{ + // Lock the mutex through this whole function. + LLMutexLock lock_data(mDataMutex); + + fflush(mIndexFP); + + fseek(mIndexFP, 0, SEEK_END); + size_t index_size = ftell(mIndexFP); + fseek(mIndexFP, 0, SEEK_SET); + + BOOL vfs_corrupt = FALSE; + + // since we take the address of element 0, we need to have at least one element. + std::vector buffer(llmax(index_size,1U)); + + if (fread(&buffer[0], 1, index_size, mIndexFP) != index_size) + { + LL_WARNS() << "Index truncated" << LL_ENDL; + vfs_corrupt = TRUE; + } + + size_t buf_offset = 0; + + std::map found_files; + U32 cur_time = (U32)time(NULL); + + std::vector audit_blocks; + while (!vfs_corrupt && buf_offset < index_size) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + audit_blocks.push_back(block); + + block->deserialize(&buffer[buf_offset], (S32)buf_offset); + buf_offset += block->SERIAL_SIZE; + + // do sanity check on this block + if (block->mLength >= 0 && + block->mSize >= 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mAccessTime <= cur_time && + block->mFileID != LLUUID::null) + { + if (mFileBlocks.find(*block) == mFileBlocks.end()) + { + LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " on disk, not in memory, loc " << block->mIndexLocation << LL_ENDL; + } + else if (found_files.find(*block) != found_files.end()) + { + std::map::iterator it; + it = found_files.find(*block); + LLVFSFileBlock* dupe = it->second; + // try to keep data from being lost + unlockAndClose(mIndexFP); + mIndexFP = NULL; + unlockAndClose(mDataFP); + mDataFP = NULL; + LL_WARNS() << "VFS: Original block index " << block->mIndexLocation + << " location " << block->mLocation + << " length " << block->mLength + << " size " << block->mSize + << " id " << block->mFileID + << " type " << block->mFileType + << LL_ENDL; + LL_WARNS() << "VFS: Duplicate block index " << dupe->mIndexLocation + << " location " << dupe->mLocation + << " length " << dupe->mLength + << " size " << dupe->mSize + << " id " << dupe->mFileID + << " type " << dupe->mFileType + << LL_ENDL; + LL_WARNS() << "VFS: Index size " << index_size << LL_ENDL; + LL_WARNS() << "VFS: INDEX CORRUPT" << LL_ENDL; + vfs_corrupt = TRUE; + break; + } + else + { + found_files[*block] = block; + } + } + else + { + if (block->mLength) + { + LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " corrupt on disk" << LL_ENDL; + } + // else this is just a hole + } + } + + if (!vfs_corrupt) + { + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock* block = (*it).second; + + if (block->mSize > 0) + { + if (! found_files.count(*block)) + { + LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " in memory, not on disk, loc " << block->mIndexLocation<< LL_ENDL; + fseek(mIndexFP, block->mIndexLocation, SEEK_SET); + U8 buf[LLVFSFileBlock::SERIAL_SIZE]; + if (fread(buf, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP) != 1) + { + LL_WARNS() << "VFile " << block->mFileID + << " gave short read" << LL_ENDL; + } + + LLVFSFileBlock disk_block; + disk_block.deserialize(buf, block->mIndexLocation); + + LL_WARNS() << "Instead found " << disk_block.mFileID << ":" << block->mFileType << LL_ENDL; + } + else + { + block = found_files.find(*block)->second; + found_files.erase(*block); + } + } + } + + for (std::map::iterator iter = found_files.begin(); + iter != found_files.end(); iter++) + { + LLVFSFileBlock* block = iter->second; + LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " szie:" << block->mSize << " leftover" << LL_ENDL; + } + + LL_INFOS() << "VFS: audit OK" << LL_ENDL; + // mutex released by LLMutexLock() destructor. + } + + for_each(audit_blocks.begin(), audit_blocks.end(), DeletePointer()); + audit_blocks.clear(); +} + + +// quick check for uninitialized blocks +// Slow, do not call in release. +void LLVFS::checkMem() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *block = (*it).second; + llassert(block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mFileID != LLUUID::null); + + for (std::deque::iterator iter = mIndexHoles.begin(); + iter != mIndexHoles.end(); ++iter) + { + S32 index_loc = *iter; + if (index_loc == block->mIndexLocation) + { + LL_WARNS() << "VFile block " << block->mFileID << ":" << block->mFileType << " is marked as a hole" << LL_ENDL; + } + } + } + + LL_INFOS() << "VFS: mem check OK" << LL_ENDL; + + unlockData(); +} + +void LLVFS::dumpLockCounts() +{ + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + LL_INFOS() << "LockType: " << i << ": " << mLockCounts[i] << LL_ENDL; + } +} + +void LLVFS::dumpStatistics() +{ + lockData(); + + // Investigate file blocks. + std::map size_counts; + std::map location_counts; + std::map > filetype_counts; + + S32 max_file_size = 0; + S32 total_file_size = 0; + S32 invalid_file_count = 0; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + if (file_block->mLength == BLOCK_LENGTH_INVALID) + { + invalid_file_count++; + } + else if (file_block->mLength <= 0) + { + LL_INFOS() << "Bad file block at: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << LL_ENDL; + size_counts[file_block->mLength]++; + location_counts[file_block->mLocation]++; + } + else + { + total_file_size += file_block->mLength; + } + + if (file_block->mLength > max_file_size) + { + max_file_size = file_block->mLength; + } + + filetype_counts[file_block->mFileType].first++; + filetype_counts[file_block->mFileType].second += file_block->mLength; + } + + for (std::map::iterator it = size_counts.begin(); it != size_counts.end(); ++it) + { + S32 size = it->first; + S32 size_count = it->second; + LL_INFOS() << "Bad files size " << size << " count " << size_count << LL_ENDL; + } + for (std::map::iterator it = location_counts.begin(); it != location_counts.end(); ++it) + { + U32 location = it->first; + S32 location_count = it->second; + LL_INFOS() << "Bad files location " << location << " count " << location_count << LL_ENDL; + } + + // Investigate free list. + S32 max_free_size = 0; + S32 total_free_size = 0; + std::map free_length_counts; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + if (free_block->mLength <= 0) + { + LL_INFOS() << "Bad free block at: " << free_block->mLocation << "\tLength: " << free_block->mLength << LL_ENDL; + } + else + { + LL_INFOS() << "Block: " << free_block->mLocation + << "\tLength: " << free_block->mLength + << "\tEnd: " << free_block->mLocation + free_block->mLength + << LL_ENDL; + total_free_size += free_block->mLength; + } + + if (free_block->mLength > max_free_size) + { + max_free_size = free_block->mLength; + } + + free_length_counts[free_block->mLength]++; + } + + // Dump histogram of free block sizes + for (std::map::iterator it = free_length_counts.begin(); it != free_length_counts.end(); ++it) + { + LL_INFOS() << "Free length " << it->first << " count " << it->second << LL_ENDL; + } + + LL_INFOS() << "Invalid blocks: " << invalid_file_count << LL_ENDL; + LL_INFOS() << "File blocks: " << mFileBlocks.size() << LL_ENDL; + + S32 length_list_count = (S32)mFreeBlocksByLength.size(); + S32 location_list_count = (S32)mFreeBlocksByLocation.size(); + if (length_list_count == location_list_count) + { + LL_INFOS() << "Free list lengths match, free blocks: " << location_list_count << LL_ENDL; + } + else + { + LL_WARNS() << "Free list lengths do not match!" << LL_ENDL; + LL_WARNS() << "By length: " << length_list_count << LL_ENDL; + LL_WARNS() << "By location: " << location_list_count << LL_ENDL; + } + LL_INFOS() << "Max file: " << max_file_size/1024 << "K" << LL_ENDL; + LL_INFOS() << "Max free: " << max_free_size/1024 << "K" << LL_ENDL; + LL_INFOS() << "Total file size: " << total_file_size/1024 << "K" << LL_ENDL; + LL_INFOS() << "Total free size: " << total_free_size/1024 << "K" << LL_ENDL; + LL_INFOS() << "Sum: " << (total_file_size + total_free_size) << " bytes" << LL_ENDL; + LL_INFOS() << llformat("%.0f%% full",((F32)(total_file_size)/(F32)(total_file_size+total_free_size))*100.f) << LL_ENDL; + + LL_INFOS() << " " << LL_ENDL; + for (std::map >::iterator iter = filetype_counts.begin(); + iter != filetype_counts.end(); ++iter) + { + LL_INFOS() << "Type: " << LLAssetType::getDesc(iter->first) + << " Count: " << iter->second.first + << " Bytes: " << (iter->second.second>>20) << " MB" << LL_ENDL; + } + + // Look for potential merges + { + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); + blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); + LLVFSBlock *first_block = iter->second; + while(iter != end) + { + if (++iter == end) + break; + LLVFSBlock *second_block = iter->second; + if (first_block->mLocation + first_block->mLength == second_block->mLocation) + { + LL_INFOS() << "Potential merge at " << first_block->mLocation << LL_ENDL; + } + first_block = second_block; + } + } + unlockData(); +} + +// Debug Only! +std::string get_extension(LLAssetType::EType type) +{ + std::string extension; + switch(type) + { + case LLAssetType::AT_TEXTURE: + extension = ".jp2"; // formerly ".j2c" + break; + case LLAssetType::AT_SOUND: + extension = ".ogg"; + break; + case LLAssetType::AT_SOUND_WAV: + extension = ".wav"; + break; + case LLAssetType::AT_TEXTURE_TGA: + extension = ".tga"; + break; + case LLAssetType::AT_ANIMATION: + extension = ".lla"; + break; + case LLAssetType::AT_MESH: + extension = ".slm"; + break; + default: + // Just use the asset server filename extension in most cases + extension += "."; + extension += LLAssetType::lookup(type); + break; + } + return extension; +} + +void LLVFS::listFiles() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileSpecifier file_spec = it->first; + LLVFSFileBlock *file_block = it->second; + S32 length = file_block->mLength; + S32 size = file_block->mSize; + if (length != BLOCK_LENGTH_INVALID && size > 0) + { + LLUUID id = file_spec.mFileID; + std::string extension = get_extension(file_spec.mFileType); + LL_INFOS() << " File: " << id + << " Type: " << LLAssetType::getDesc(file_spec.mFileType) + << " Size: " << size + << LL_ENDL; + } + } + + unlockData(); +} + +#include "llapr.h" +void LLVFS::dumpFiles() +{ + lockData(); + + S32 files_extracted = 0; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileSpecifier file_spec = it->first; + LLVFSFileBlock *file_block = it->second; + S32 length = file_block->mLength; + S32 size = file_block->mSize; + if (length != BLOCK_LENGTH_INVALID && size > 0) + { + LLUUID id = file_spec.mFileID; + LLAssetType::EType type = file_spec.mFileType; + std::vector buffer(size); + + unlockData(); + getData(id, type, &buffer[0], 0, size); + lockData(); + + std::string extension = get_extension(type); + std::string filename = id.asString() + extension; + LL_INFOS() << " Writing " << filename << LL_ENDL; + + LLAPRFile outfile; + outfile.open(filename, LL_APR_WB); + outfile.write(&buffer[0], size); + outfile.close(); + + files_extracted++; + } + } + + unlockData(); + + LL_INFOS() << "Extracted " << files_extracted << " files out of " << mFileBlocks.size() << LL_ENDL; +} + +time_t LLVFS::creationTime() +{ + llstat data_file_stat; + int errors = LLFile::stat(mDataFilename, &data_file_stat); + if (0 == errors) + { + time_t creation_time = data_file_stat.st_ctime; +#if LL_DARWIN + creation_time = data_file_stat.st_birthtime; +#endif + return creation_time; + } + return 0; +} + +//============================================================================ +// protected +//============================================================================ + +// static +LLFILE *LLVFS::openAndLock(const std::string& filename, const char* mode, BOOL read_lock) +{ +#if LL_WINDOWS + + return LLFile::_fsopen(filename, mode, (read_lock ? _SH_DENYWR : _SH_DENYRW)); + +#else + + LLFILE *fp; + int fd; + + // first test the lock in a non-destructive way +#if LL_SOLARIS + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; +#else // !LL_SOLARIS + if (strchr(mode, 'w') != NULL) + { + fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ + if (fp) + { + fd = fileno(fp); + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) + { + fclose(fp); + return NULL; + } + + fclose(fp); + } + } +#endif // !LL_SOLARIS + + // now actually open the file for use + fp = LLFile::fopen(filename, mode); /* Flawfinder: ignore */ + if (fp) + { + fd = fileno(fp); +#if LL_SOLARIS + fl.l_type = read_lock ? F_RDLCK : F_WRLCK; + if (fcntl(fd, F_SETLK, &fl) == -1) +#else + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) +#endif + { + fclose(fp); + fp = NULL; + } + } + + return fp; + +#endif +} + +// static +void LLVFS::unlockAndClose(LLFILE *fp) +{ + if (fp) + { + // IW: we don't actually want to unlock on linux + // this is because a forked process can kill the parent's lock + // with an explicit unlock + // however, fclose() will implicitly remove the lock + // but only once both parent and child have closed the file + /* + #if !LL_WINDOWS + int fd = fileno(fp); + flock(fd, LOCK_UN); + #endif + */ +#if LL_SOLARIS + struct flock fl; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; + fl.l_type = F_UNLCK; + fcntl(fileno(fp), F_SETLK, &fl); +#endif + fclose(fp); + } +} diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h new file mode 100644 index 0000000000..42feafe20b --- /dev/null +++ b/indra/llvfs/llvfs.h @@ -0,0 +1,183 @@ +/** + * @file llvfs.h + * @brief Definition of virtual file system + * + * $LicenseInfo:firstyear=2002&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_LLVFS_H +#define LL_LLVFS_H + +#include +#include "lluuid.h" +#include "llassettype.h" +#include "llthread.h" +#include "llmutex.h" + +enum EVFSValid +{ + VFSVALID_UNKNOWN = 0, + VFSVALID_OK = 1, + VFSVALID_BAD_CORRUPT = 2, + VFSVALID_BAD_CANNOT_OPEN_READONLY = 3, + VFSVALID_BAD_CANNOT_CREATE = 4 +}; + +// Lock types for open vfiles, pending async reads, and pending async appends +// (There are no async normal writes, currently) +enum EVFSLock +{ + VFSLOCK_OPEN = 0, + VFSLOCK_READ = 1, + VFSLOCK_APPEND = 2, + + VFSLOCK_COUNT = 3 +}; + +// internal classes +class LLVFSBlock; +class LLVFSFileBlock; +class LLVFSFileSpecifier +{ +public: + LLVFSFileSpecifier(); + LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type); + bool operator<(const LLVFSFileSpecifier &rhs) const; + bool operator==(const LLVFSFileSpecifier &rhs) const; + +public: + LLUUID mFileID; + LLAssetType::EType mFileType; +}; + +class LLVFS +{ +private: + // Use createLLVFS() to open a VFS file + // Pass 0 to not presize + LLVFS(const std::string& index_filename, + const std::string& data_filename, + const BOOL read_only, + const U32 presize, + const BOOL remove_after_crash); +public: + ~LLVFS(); + + // Use this function normally to create LLVFS files + // Pass 0 to not presize + static LLVFS * createLLVFS(const std::string& index_filename, + const std::string& data_filename, + const BOOL read_only, + const U32 presize, + const BOOL remove_after_crash); + + BOOL isValid() const { return (VFSVALID_OK == mValid); } + EVFSValid getValidState() const { return mValid; } + + // ---------- The following fucntions lock/unlock mDataMutex ---------- + BOOL getExists(const LLUUID &file_id, const LLAssetType::EType file_type); + S32 getSize(const LLUUID &file_id, const LLAssetType::EType file_type); + + BOOL checkAvailable(S32 max_size); + + S32 getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type); + BOOL setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size); + + void renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type); + void removeFile(const LLUUID &file_id, const LLAssetType::EType file_type); + + S32 getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length); + S32 storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length); + + void incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + void decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + BOOL isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + // ---------------------------------------------------------------- + + // Used to trigger evil WinXP behavior of "preloading" entire file into memory. + void pokeFiles(); + + // Verify that the index file contents match the in-memory file structure + // Very slow, do not call routinely. JC + void audit(); + // Check for uninitialized blocks. Slow, do not call in release. JC + void checkMem(); + // for debugging, prints a map of the vfs + void dumpMap(); + void dumpLockCounts(); + void dumpStatistics(); + void listFiles(); + void dumpFiles(); + time_t creationTime(); + +protected: + void removeFileBlock(LLVFSFileBlock *fileblock); + + void eraseBlockLength(LLVFSBlock *block); + void eraseBlock(LLVFSBlock *block); + void addFreeBlock(LLVFSBlock *block); + //void mergeFreeBlocks(); + void useFreeSpace(LLVFSBlock *free_block, S32 length); + void sync(LLVFSFileBlock *block, BOOL remove = FALSE); + void presizeDataFile(const U32 size); + + static LLFILE *openAndLock(const std::string& filename, const char* mode, BOOL read_lock); + static void unlockAndClose(FILE *fp); + + // Can initiate LRU-based file removal to make space. + // The immune file block will not be removed. + LLVFSBlock *findFreeBlock(S32 size, LLVFSFileBlock *immune = NULL); + + // lock/unlock data mutex (mDataMutex) + void lockData() { mDataMutex->lock(); } + void unlockData() { mDataMutex->unlock(); } + +protected: + LLMutex* mDataMutex; + + typedef std::map fileblock_map; + fileblock_map mFileBlocks; + + typedef std::multimap blocks_length_map_t; + blocks_length_map_t mFreeBlocksByLength; + typedef std::multimap blocks_location_map_t; + blocks_location_map_t mFreeBlocksByLocation; + + LLFILE *mDataFP; + LLFILE *mIndexFP; + + std::deque mIndexHoles; + + std::string mIndexFilename; + std::string mDataFilename; + BOOL mReadOnly; + + EVFSValid mValid; + + S32 mLockCounts[VFSLOCK_COUNT]; + BOOL mRemoveAfterCrash; +}; + +extern LLVFS *gVFS; + +#endif diff --git a/indra/llfilesystem/lldir_utils_objc.h b/indra/llvfs/llvfs_objc.h similarity index 85% rename from indra/llfilesystem/lldir_utils_objc.h rename to indra/llvfs/llvfs_objc.h index 12019c4284..56cdbebfc5 100644 --- a/indra/llfilesystem/lldir_utils_objc.h +++ b/indra/llvfs/llvfs_objc.h @@ -1,10 +1,10 @@ /** - * @file lldir_utils_objc.h + * @file llvfs_objc.h * @brief Definition of directory utilities class for Mac OS X * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. + * 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 @@ -28,8 +28,8 @@ #error This header must not be included when compiling for any target other than Mac OS. Consider including lldir.h instead. #endif // !LL_DARWIN -#ifndef LL_LLDIR_UTILS_OBJC_H -#define LL_LLDIR_UTILS_OBJC_H +#ifndef LL_LLVFS_OBJC_H +#define LL_LLVFS_OBJC_H #include @@ -40,4 +40,4 @@ std::string* getSystemResourceFolder(); std::string* getSystemExecutableFolder(); -#endif // LL_LLDIR_UTILS_OBJC_H +#endif // LL_LLVFS_OBJC_H diff --git a/indra/llfilesystem/lldir_utils_objc.mm b/indra/llvfs/llvfs_objc.mm similarity index 95% rename from indra/llfilesystem/lldir_utils_objc.mm rename to indra/llvfs/llvfs_objc.mm index da55a2f897..282ea41339 100644 --- a/indra/llfilesystem/lldir_utils_objc.mm +++ b/indra/llvfs/llvfs_objc.mm @@ -1,10 +1,10 @@ /** - * @file lldir_utils_objc.mm + * @file llvfs_objc.cpp * @brief Cocoa implementation of directory utilities for Mac OS X * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. + * 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 @@ -27,7 +27,7 @@ //WARNING: This file CANNOT use standard linden includes due to conflicts between definitions of BOOL -#include "lldir_utils_objc.h" +#include "llvfs_objc.h" #import std::string* getSystemTempFolder() diff --git a/indra/llvfs/llvfsthread.cpp b/indra/llvfs/llvfsthread.cpp new file mode 100644 index 0000000000..8cd85929e2 --- /dev/null +++ b/indra/llvfs/llvfsthread.cpp @@ -0,0 +1,300 @@ +/** + * @file llvfsthread.cpp + * @brief LLVFSThread implementation + * + * $LicenseInfo:firstyear=2001&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 "llvfsthread.h" +#include "llstl.h" + +//============================================================================ + +/*static*/ std::string LLVFSThread::sDataPath = ""; + +/*static*/ LLVFSThread* LLVFSThread::sLocal = NULL; + +//============================================================================ +// Run on MAIN thread +//static +void LLVFSThread::initClass(bool local_is_threaded) +{ + llassert(sLocal == NULL); + sLocal = new LLVFSThread(local_is_threaded); +} + +//static +S32 LLVFSThread::updateClass(U32 ms_elapsed) +{ + sLocal->update((F32)ms_elapsed); + return sLocal->getPending(); +} + +//static +void LLVFSThread::cleanupClass() +{ + sLocal->setQuitting(); + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = 0; +} + +//---------------------------------------------------------------------------- + +LLVFSThread::LLVFSThread(bool threaded) : + LLQueuedThread("VFS", threaded) +{ +} + +LLVFSThread::~LLVFSThread() +{ + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + +LLVFSThread::handle_t LLVFSThread::read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) +{ + handle_t handle = generateHandle(); + + priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW + Request* req = new Request(handle, priority, flags, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + +LLVFSThread::handle_t LLVFSThread::write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + + +// LLVFSThread::handle_t LLVFSThread::rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, +// const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags) +// { +// handle_t handle = generateHandle(); + +// LLUUID* new_idp = new LLUUID(new_id); // deleted with Request +// // new_type is passed as "numbytes" +// Request* req = new Request(handle, 0, flags, FILE_RENAME, vfs, file_id, file_type, +// (U8*)new_idp, 0, (S32)new_type); + +// bool res = addRequest(req); +// if (!res) +// { +// LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; +// req->deleteRequest(); +// handle = nullHandle(); +// } + +// return handle; +// } + +//============================================================================ + +LLVFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) : + QueuedRequest(handle, priority, flags), + mOperation(op), + mVFS(vfs), + mFileID(file_id), + mFileType(file_type), + mBuffer(buffer), + mOffset(offset), + mBytes(numbytes), + mBytesRead(0) +{ + llassert(mBuffer); + + if (numbytes <= 0 && mOperation != FILE_RENAME) + { + LL_WARNS() << "LLVFSThread: Request with numbytes = " << numbytes + << " operation = " << op + << " offset " << offset + << " file_type " << file_type << LL_ENDL; + } + if (mOperation == FILE_WRITE) + { + S32 blocksize = mVFS->getMaxSize(mFileID, mFileType); + if (blocksize < 0) + { + LL_WARNS() << "VFS write to temporary block (shouldn't happen)" << LL_ENDL; + } + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +// dec locks as soon as a request finishes +void LLVFSThread::Request::finishRequest(bool completed) +{ + if (mOperation == FILE_WRITE) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +void LLVFSThread::Request::deleteRequest() +{ + if (getStatus() == STATUS_QUEUED) + { + LL_ERRS() << "Attempt to delete a queued LLVFSThread::Request!" << LL_ENDL; + } + if (mOperation == FILE_WRITE) + { + if (mFlags & FLAG_AUTO_DELETE) + { + delete [] mBuffer; + } + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + delete new_idp; + } + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +bool LLVFSThread::Request::processRequest() +{ + bool complete = false; + if (mOperation == FILE_READ) + { + llassert(mOffset >= 0); + mBytesRead = mVFS->getData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //LL_INFOS() << llformat("LLVFSThread::READ '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; + } + else if (mOperation == FILE_WRITE) + { + mBytesRead = mVFS->storeData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //LL_INFOS() << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + LLAssetType::EType new_type = (LLAssetType::EType)mBytes; + mVFS->renameFile(mFileID, mFileType, *new_idp, new_type); + mFileID = *new_idp; + complete = true; + //LL_INFOS() << llformat("LLVFSThread::RENAME '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; + } + else + { + LL_ERRS() << llformat("LLVFSThread::unknown operation: %d", mOperation) << LL_ENDL; + } + return complete; +} + +//============================================================================ diff --git a/indra/llvfs/llvfsthread.h b/indra/llvfs/llvfsthread.h new file mode 100644 index 0000000000..7814de4a2d --- /dev/null +++ b/indra/llvfs/llvfsthread.h @@ -0,0 +1,140 @@ +/** + * @file llvfsthread.h + * @brief LLVFSThread definition + * + * $LicenseInfo:firstyear=2001&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_LLVFSTHREAD_H +#define LL_LLVFSTHREAD_H + +#include +#include +#include +#include + +#include "llqueuedthread.h" + +#include "llvfs.h" + +//============================================================================ + +class LLVFSThread : public LLQueuedThread +{ + //------------------------------------------------------------------------ +public: + enum operation_t { + FILE_READ, + FILE_WRITE, + FILE_RENAME + }; + + //------------------------------------------------------------------------ +public: + + class Request : public QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + S32 getBytesRead() + { + return mBytesRead; + } + S32 getOperation() + { + return mOperation; + } + U8* getBuffer() + { + return mBuffer; + } + LLVFS* getVFS() + { + return mVFS; + } + std::string getFilename() + { + std::string tstring; + mFileID.toString(tstring); + return tstring; + } + + /*virtual*/ bool processRequest(); + /*virtual*/ void finishRequest(bool completed); + /*virtual*/ void deleteRequest(); + + private: + operation_t mOperation; + + LLVFS* mVFS; + LLUUID mFileID; + LLAssetType::EType mFileType; + + U8* mBuffer; // dest for reads, source for writes, new UUID for rename + S32 mOffset; // offset into file, -1 = append (WRITE only) + S32 mBytes; // bytes to read from file, -1 = all (new mFileType for rename) + S32 mBytesRead; // bytes read from file + }; + + //------------------------------------------------------------------------ +public: + static std::string sDataPath; + static LLVFSThread* sLocal; // Default worker thread + +public: + LLVFSThread(bool threaded = TRUE); + ~LLVFSThread(); + + // Return a Request handle + handle_t read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, /* Flawfinder: ignore */ + U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); + handle_t write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags); + // SJB: rename seems to have issues, especially when threaded +// handle_t rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, +// const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags); + // Return number of bytes read + S32 readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + S32 writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + /*virtual*/ bool processRequest(QueuedRequest* req); + +public: + static void initClass(bool local_is_threaded = TRUE); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static void cleanupClass(); // Delete sLocal + static void setDataPath(const std::string& path) { sDataPath = path; } +}; + +//============================================================================ + + +#endif // LL_LLVFSTHREAD_H diff --git a/indra/llfilesystem/tests/lldir_test.cpp b/indra/llvfs/tests/lldir_test.cpp similarity index 100% rename from indra/llfilesystem/tests/lldir_test.cpp rename to indra/llvfs/tests/lldir_test.cpp diff --git a/indra/llfilesystem/tests/lldiriterator_test.cpp b/indra/llvfs/tests/lldiriterator_test.cpp similarity index 100% rename from indra/llfilesystem/tests/lldiriterator_test.cpp rename to indra/llvfs/tests/lldiriterator_test.cpp diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 70eb99c86c..8bfb23ed64 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -16,7 +16,7 @@ include(LLCommon) include(LLImage) include(LLMath) include(LLRender) -include(LLFileSystem) +include(LLVFS) include(LLWindow) include(LLXML) include(UI) @@ -26,7 +26,7 @@ include_directories( ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) @@ -72,7 +72,7 @@ if (LINUX) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLXML_LIBRARIES} ${UI_LIBRARIES} # for GTK @@ -95,7 +95,7 @@ if (LINUX) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_HEADLESS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLWINDOW_HEADLESS_LIBRARIES} ${LLXML_LIBRARIES} fontconfig # For FCInit and other FC* functions. diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index 3a7a54e51d..013a422d35 100644 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -5,13 +5,13 @@ project(llxml) include(00-Common) include(LLCommon) include(LLMath) -include(LLFileSystem) +include(LLVFS) include(LLXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ) include_directories( ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -42,7 +42,7 @@ add_library (llxml ${llxml_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level target_link_libraries( llxml - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${EXPAT_LIBRARIES} diff --git a/indra/mac_crash_logger/CMakeLists.txt b/indra/mac_crash_logger/CMakeLists.txt index 75621d7b75..95637c9a28 100644 --- a/indra/mac_crash_logger/CMakeLists.txt +++ b/indra/mac_crash_logger/CMakeLists.txt @@ -8,7 +8,7 @@ include(LLCoreHttp) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLFilesystem) +include(LLVFS) include(LLXML) include(Linking) include(LLSharedLibs) @@ -19,7 +19,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCRASHLOGGER_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) include_directories(SYSTEM @@ -68,10 +68,11 @@ find_library(COCOA_LIBRARY Cocoa) target_link_libraries(mac-crash-logger ${LLCRASHLOGGER_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${COCOA_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/mac_crash_logger/mac_crash_logger.cpp b/indra/mac_crash_logger/mac_crash_logger.cpp index 66d8cfa590..54e41a1954 100644 --- a/indra/mac_crash_logger/mac_crash_logger.cpp +++ b/indra/mac_crash_logger/mac_crash_logger.cpp @@ -27,6 +27,7 @@ #include "linden_common.h" #include "llcrashloggermac.h" #include "indra_constants.h" +#include "llpidlock.h" #include diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 82e6cda193..3439951e80 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -39,7 +39,7 @@ include(LLPlugin) include(LLPrimitive) include(LLRender) include(LLUI) -include(LLFileSystem) +include(LLVFS) include(LLWindow) include(LLXML) include(NDOF) @@ -84,7 +84,7 @@ include_directories( ${LLPRIMITIVE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLUI_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LLLOGIN_INCLUDE_DIRS} @@ -2016,7 +2016,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLRENDER_LIBRARIES} ${FREETYPE_LIBRARIES} ${LLUI_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -2492,7 +2492,7 @@ if (LL_TESTS) set(test_libs ${LLMESSAGE_LIBRARIES} ${WINDOWS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${GOOGLEMOCK_LIBRARIES} @@ -2507,7 +2507,7 @@ if (LL_TESTS) set(test_libs ${WINDOWS_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${LLMESSAGE_LIBRARIES} diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 393a38fb9c..c0166f158e 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1362,39 +1362,6 @@ Value 23 - EnableCacheDebugInfo - - Comment - When set, display additional cache debugging information - Persist - 1 - Type - Boolean - Value - 0 - - DiskCachePercentOfTotal - - Comment - The percent of total cache size (defined by CacheSize) to use for the disk cache - Persist - 1 - Type - F32 - Value - 20.0 - - DiskCacheDirName - - Comment - The name of the disk cache (within the standard Viewer disk cache directory) - Persist - 1 - Type - String - Value - cache - CacheLocation Comment @@ -2868,6 +2835,17 @@ Value -1 + DebugStatModeVFSPendingOps + + Comment + Mode of stat in Statistics floater + Persist + 1 + Type + S32 + Value + -1 + DebugStatModeTimeDialation Comment @@ -3660,6 +3638,17 @@ Value 4 + DumpVFSCaches + + Comment + Dump VFS caches on startup. + Persist + 1 + Type + Boolean + Value + 0 + DynamicCameraStrength Comment @@ -11591,7 +11580,7 @@ Boolean Value 0 - + NearbyListShowMap Comment @@ -14269,6 +14258,28 @@ Value + VFSOldSize + + Comment + [DO NOT MODIFY] Controls resizing of local file cache + Persist + 1 + Type + U32 + Value + 0 + + VFSSalt + + Comment + [DO NOT MODIFY] Controls local file caching behavior + Persist + 1 + Type + U32 + Value + 1 + VelocityInterpolate Comment @@ -16610,7 +16621,7 @@ Type Boolean Value - 1 + 1 diff --git a/indra/newview/app_settings/static_data.db2 b/indra/newview/app_settings/static_data.db2 new file mode 100644 index 0000000000000000000000000000000000000000..f85aa81601787e0643162d85f1086f548577809d GIT binary patch literal 576578 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�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

iUO{A+_>y^r$H<>b zz9MFnPNQBbIYGQty_3k!>GsDGJ?FJ$CM;{Zpqcxbo)YyoLTILkuTVRP&rLO)A(|b8 zrzyb)*VYp5#-ZYIx&y=>Dq-s#^`}IE?X7CQ<`S{Tns)%5PoMDC)e~vZ?(f_Ga7Dn= zciWmSXa+x_$9eH9g?nIt89w3Ff-NxKi6`^&W2pRrdnZ@qeL~IjTsygU-gSx|er)QC zxf`h(4sUk9>~F{fA8W{NyB7M&(1?8*N1?YyFP@#7l=)V*M{sg(WX9#1ZVTq*iY<8u zTxYfBJb)WIYub&RFAVgF3lj~w=k+yb9*!-^8)S%Y?j(Mg-w%81Qg5MGp%;Gn8saPO zCW8N9r2!*Ge^!BL=0BlFw(gj9fB4YeACT|Z@kLkMa?ISUBmWp^j?|tw6@jJhB-Z}4s!?{#XP4!Jx_tvdjRojwg5kz3zlC4|j-{6jmb=?BV z5Uq@*t*P zA8Qy~MpS9|ryFV4NXrX?FB*5$=@&5=3sxl_7iW4CJ!>~NCrWBWvo|Wv|IYJ{?cG{; zIRyP(v~+h+TQu_lzi^-S-AnWiVu(*imkQs+Co1?KG8cYGNtL+(hkx=KoTzqA9Zeob zY~zz=74c9oHfGEG!y-@abSRyb3EO$v(RBvwjN|;+&|stIxip?(kd8@z;Y9L$U>!29 zD41(X4=%f0^cv+8+BIhicA_)+9d#FEWymK{;_(X^GUQ|2=H{SeU39NlaelbuKFWx# zyBq^$Y$^>*;g$GA1^;7)gpv^Da(~?jRYpYT)a;nWU@xc6 zpgQgjYR1wri4k~!(MaNPg-C#gpxSrq`Aon-5}&B*D-E?m5u zqKkJcju~8GP8vjfwBj zG3nXFW=cAOU6$MB@ea1vArC#Q#M`fI&RpU&oyeE0P%7tU) zN#_ULeqo)#Fb8j*TD*>la8plQX>#K6^1V|S5mDNAgK3UH2<^)M<~C+!<0{j#lT3-G zVZmee@JDmjVmcbDVsgU>IGj|*zX(c#rp71nWG^gi6PKP>L(k|`x2 zaYGe3pIB3iaI~)I7R#{28=CrKtmp%_XyWn9th57gA@Y0^%lF_INZmAcTcH!|9iD8n z+$=aqo?{nEzo3avRCp^CDmX~pKclhJ1|>ffaOey1;4upjBjzhoN5hfL49(_uPK{x1 z4m{6Kn$;Z@;w6Qn$%ydx&c8_%i)Mx&a!yUHGk6zPy7yXku+i*rb$2J3j!ATQna|Oj z%{J)~YSf8&oz%0D8v?R(>yVGM5dW3Ai7BjzsNmC{>xweMxs;6D;4;JT z$38l_iN{UoMiJ*z73a?~NX|@2P}@^hU$jir(KQ9pqdy@MpQ!MJ_m&UhJ4ntyqOvmv zsnKIgqc1^WS+EzNZcx=cS-}DQ&^12GEOko(TjmdCt=i)N{k+Z5!`;(Box!U}#J>c3 zH;d>XDR!*J&y9fMT<*bS%bQ7SSEiKs+9*rU{l=9rJr zKn(nB0m4{8C~cyoae1NV#N(Mf?iqqXq7`629V4}WP9dSNM;zhcYhc3*k2M$(^!zqP=XP!4#_-1^d)VE zJx_(dLZO0#Wd4*f^Mg)f+@EfX29FuTr8Bx<-y4c+QIgQksj#iRXW@upi(PaCm|~GL z`C%WGTW8QMcuC13gN?d{lE9rj9TVM{jEH-@%{J%bVwn1Zy4{!K&LEBg9nT?Rl#CX1 zQlG?~=T{5rkT)?)2vVRUS{6HBxI)koPmkRc_nx1ak`h}Mf0e&EGbnaDIaZ*^iHr^8 z>=D!z4aBUCULgoB%ZvFvVw_-e^CE$EbPHE;emlR9@erCoeeS8K7qAbi@Q1g94$`5d z^Z(VK@ALniyLYSdf0X|yA1ZTyATU?jA!SJCr(6_eM%VYt04aY>9KNRkD?j<`PG6t<6JQ9Se9dKpP~<;xCO z=e>(`6jWBV7nDTo5mp=_%eY}@V>QodR$dRi6L;rAL3MoS4zcs4`Ysi;dUP!Ga-8Bq zd-wV<5WDLf<=Oo3I?>Hj?|Syq&I+bCYW9OD8*<{o{8t;JUvmTV4d0GrKEvl{H4Rj; z7UJxbogde5ab zYK+9Lfpox{JRExlm_U8nNbEhZ1PxkRFh)i}t>-Pmz5;#7t0mZHFdmgytj9!PFQR2> zfni_?QenLWBS0`>X7ddQz-&~$?Fmr+4HT7g>U`GBF|DB51&1t%P(D5cm3$}|8S zI1mPRc=RJ=nE*IpMu=u*F4&III0e-;U@!KTZP#!MjKc4;;+s1G51Yf9eeOP>U=_^z zu0C)VSTU_0^@H0WlG)z<5p$>R{hM6p%*4`c$kst<_2@j;EOBCXdpDK4DS@8( zuEz`46?>%SzuJT~3DV-420nshGDHyi^(}aeg~5~;Rab^A`q`W>{E20($!CNntg}4n z^kp$im{<5(mMt--8S4ZU)_Y^_Fy8UaY>Y&^nG5*xZHNG8JttjkDZDIB5YcP5lBfYM zahAho{36kSDMy1op#ril2i%Om33OerfTzj5K*>52n7*lGoif1pJaGiZ%9sU5hlyB2 zQYfaDu8Aeayuzf?;TQ!!fa_-81{0Xw+%8eepSDieUT_a(M&fal;wiwB z$xR$M@E%+PZ_>yzHXg&b$=0l#N!*Ne%PpwxA_l~t^Xj`)(CSej(JVeJYwz~q&J*8A zqdc?3r^LC6-}TV3V=*L;?45(H7oEg4`!%uCg3Y+$+lAOm@*UAMFbAt6%wTTBPb~a9 zsH>dZeIF-XCn#$?m-cEX;dGh$G$oA5(}J0e3SeDOVLkYH9vnAed6A6epoC*c)%dC8 zn6i8yRdPuA`#ez>PHJ*dR!Rz{g$-d_=T#EbxW5}&X2$a-W^LE3Wb#fDvup~g6L{T3 z@8IXZ_&r_eNro|uSKupH71+cWUy{= z%_Q<|caU>?ky9OUV=6*M(zH7ed2)9gV^~gwcI%*%;-n~6; zx>q@i@@z%I49}3zcRekV;Q>SZ$lmVQE1`-0n*GKCE85wR{8v*6YQ*?(!?zmPuE?X| zO#?WpNf$)y{5TusGs5YiUroS$!~~`bsU!?p4!h$&U1p^vmJC$|k=3Vd;!KOk<5XD3 zam3*!92eV04i?^yupDAJvEgPMHOCDcS@;pAu4@>(Gdz@5@3xWsM|fA5i$n#VC%Um8 zhTjNhrIfHQhVPARFK|aq!so=1Wel(|JS0`K@&wQcKayVGbr)!d@6WJ$R0=}E7iCbM zanXGdTB%I~r&y2JT5v0o3ZFrI|0kDy^&rN^D{rRUK($6(G(RK`CMriQuzAL-@Gzzgz~ZW9)?kBBG(Cr0*?oc6!vQPGm?C)d92ASzM-oVrao;*{PR5=j&yo~q{F_8 zr8_HM{LBvO{JaM|!=1%QyEaK^>`X?rEE8xhJA-)@*0-a}9ftCt{Ul$y6Fm90%}E1vKE;|^DN#Y>&9@kZA6uytL-L3H(7zk0V? zCa+xLKH&F<`{Y88Jq2MYE`j-99b6=I$6xdK_RkY(&IfWP?_s5|_l0L=>{Cm}JZ7Y< z3Xn<-0O_I>v7^odAQ|mz8I%2!$3mf^^(hz$`mWAtt06+SUS!;VW z-cyY?ZCuBioEb1>HPu>u+Al$GmeIFLr@!(WvgELh?kthlEdzJ!2Cc_?PcEIh(R!Ya zL$%?r=Iz=#I~B%3rbb4k>$w|}mo=~0WV+3YW?;9v&&+pI<&sBh?ydP|Ezl1$n{OIv z6=t9?f4i+Iq8P@@?84&(@l* zaj|8E8?UEt?6vDajJ90d!g5%~pKkB5UCq%UR@Y^^t<n>j% z&z;HlayK&8;)MxH-7QyF2yi~#-FJm)OuL}ToogbG+rtlb?>2UksM>p;=p^L3p{A^q zV1b&;zIAHpjl6_CBP^uSPsBqz>K5H>7WdgU#jbkUYwVo$P*>}`i|AvE7oP0}7{_j1 zi4UcC9Q(aVjnBXV5j$l?uoqc22en#0#7(pES1@hKcE^J1Q7jFE13T;*toLZHezauv zxhp$k*1rC&;{5U*foA%8rkA>Gmz!?;O)fXxuCq?8>vDC$jz}{dz3100xBb4Ze-Z1Z zY+KH{g}Q3D!)++*FD=ZtJ8o;Yx!xk(dj*>ZHXL41-}Tw%jrp8KR*(MN6})!1UVC?+ z4`&6}fbxuB+Zumf@vdjFq`~C$2C{c#<}r)Y+cogd{)RP>;v@9zl05kez!fG)(?g6Wu;W^8UdkNdJ|>vd(yR`ec%@i+OM!DU?^5E`vUQT zzCzy8kS*8%pcLN>5yODzz=52>=bDwff@r82R!}`TSXs}mVF-OqoEn&YuE$?luJ6(^ zQ$njpi=np#>FYL39tPh>dDfgV8q)u+=LBaA^a04;9T5|-*FdxXD02$d5At8(f?1Gn zhHpot%!lQsfyRtQu)Oo*!t}*hFL?Orx1=T5@6h`m8Mh4U1`36W--F`#_f`aD4u@o@ zYSBo2nj_y8s}WXM@8SDm$)a96N4^O*lFxE*ufdrGlxNHM!T1tc(|{A-7Hbsz z9~P+$22nbD@n(t+UO8(0u~d;QTRwJonK_cgaGfxvpoi|q=+zLX9}Pp9tjQFfR=`7! z`n2kZ7nHrYbUHJr%=;G6t}Tz4>9L5=vTPO%^Y{v9aK(IeZ)=X3%^vQGKnsR^+W==w zbUe+)Hki>tZVc+RYmG`vV*9cj3LW}Tq82|-^sTDOzF5dgnSV$xrzJ-%{rO?D zeBX4bR8n`QP(4mJdu+W&@pPy=g8JH$Gj!`b%SPK$e>lbPI<}&;Gn`Vq>v&}09ooQw znxnPp-VtQk*t%SNeS~J^=$ib%Un2{u%PTH;T1443poMClrP1|WRgKfVr_-z+r5;`H zvyj%_?RIRAZzJtp&xhkP!X8Fy_D?$%&5DmUeEUb!8q$yT@ROn?R+0}l=BfBQs1yHM ztW%j1y_jX(feft?>HILppZhCEJt4wFSOsS4I$R$gyHfdBZ=A7rSC#98BK+}A`y;&? z>fnschr_JNO{_30tHbKkF0ema96c_bZpCd|?|o5smL%3>!-ks;T31v0H!iwoJ@0*% zzWWv{ zt$eO9-%{FOzx9R0!m-3;*N5j?mJ<(tbePdsVLi1-V7u>yna#|U6&u(8F5gzinP+^aPrL9u z@tlu|*M-xplvE#e%KIZ~>8ZZX6#ELP)H`IYr)`;Tb|HP~-njC5Sp%oY&Z^uxZz}TJ zX1xRL1LbbEPF*up8s31W@Rs1I`e#aK{X*@O`lb3 z*I-fmbm9J%+2=SXf|o_gE6x|6eYDE)s_CVcCh5xY*W~hx&3&true)50IJI+?){W=a z51zhh($L1bseNL>>UlTSZqGmEvG((=jJpBHQcbMeb?-TyXff5h1EV?(x@*+$SUsw* zu3nka-ril9zuc((8s%9|lFZ2U+`FDbyw|G_){(vasON?m2caG!3Y+}A{8v-ZyAJ)? zhHuwk-fkx{n+6E3o^PUb=f^s7Vd%?@ho8>#gXxDeLcbOZ-*B2T;G)l<9uDg1zZPj# z)q^tk6wz$Rm?$y;hiU~14qeO)&c8-YouJJ=t|j zYH%*JIAbh~FzLG16pzPXe|CFU@d0RJ1!2Ndx#&7LZ_aO-kDh^1wbrIz(M~ukY&|js zoe6EpwgbAT7m!NpWINGhpqsr&N}@HOUN$-H7%9XZ5aY_lT9vy(&pddZnjgiA3os?%ogGOV+v3EVEqQ_#RF|xOV zrGbsbH2YUaPQ%7y`LCY)Yhe>G!?$ldbs%gSDEH8VbnN`N-$x(v@8Kt>z@^x)Sm@Ws zv}N$H4uwL+-$DNWyoE>=zcC?`qdAy-Y&=hw?g52B@>kK)AoH~a)*+`MvmcYw{9Cbh zZ3ps*^pe1QfqD1YhFQWBb#xW~bszK&<#00Hc4Vmj6_k0b2hFL9s?Rr0Gn9U^-R-aI^w?XoD zTNaxC3XNwtA3`D1c&-tlkZJ#E@q97lqh#oAj&Gtf&>FFdgd7qZl9P9PEsDYK<5!EZK;%91zxvve7UoS;U;*-~xk^ zdztT#kA*==Q>MYGe$Ig3pA7n$G8mLR$LMZhaGodjF+$E}!=U6^=DJh8Feq8WY^ryM zLCIfP!8NB47!YQCE&qnp%d%MgrHf!tvXM3Hz$O@!JjJ?EvKa;?vso`n7r>yT4XdK^ zF$_k=GrNxJB27nLFr?=M9J>ZN!jS;RM*$a}z6&J&6A2gF zel#iQ3%A#fhvo(y=Bhc~M=tn&h058DjPq)NN?DIAa?OKEDM9Y;W8kcmEePH76O4@c zb1DMspi=JQh(fjTdRY_4mDY}1=gE-I5k1`Yf;4nh;~pW2zi>7*MM~pUkK-x z7}GmM6yJOd)Ow8&Sv?v9s=Xcw-t{yhBfa0lx#K8A&&P^1e7hZaM2#jMeqy4xLq5Z~ z;eSd3&>Q4}YVlla#1)1X{Tnb--yWGy*N0tqHtkfj8tl3Sq56>{uwIRG!MYJIp%0!C z_)9p%XkIPVBVZ`(!cQoj{_2>-;t$FSN*(OVt(0{>w_u3Yg{n;vz!0s%ce39RIDT#m z*cb2$D&K_Qfq*Qie78a;`(K30XFJhH~E4#pdAM7(t=hd%iJ zhzkx4aNLwe81JfuKKRJU;SMFx2VWfZ5{7rx(qGfhyY7eM<{yl;9xOO+ma{ayw!(4q zD*F$wd9bVRbrKLW;;eAR*!^e(~j{7p@J8p9a}`i?B--Li03?ypetoj*uqg>&!dQJYnR? zoO!tJtXfi>zXi9Rr^j1*umP7?$azsmyr5F{@Z9PVsFWvoT1VWuW;Ppm&nw=*k@Eq0 zyd)cUv8^Xh7lz}#cF)MS#W66fYss5Xei4^CPvdhMqPVb+^S8kxI_ljb_>rd`aR>Y= z`SoWO5iXJk{FUcjiRX#6{DHG$30BH(zWZq_u3Gvv;`HyO8=2iA85p6Wu*j%kooFX_PRu!a6$1 zm%bs)%tj-zpRt)H-?l>XhFKQnV*4SkmeI%TwR4X-7`X+ib)g_Xv=fF%5#Gyy5v0^v zO#1m|kh-oCQlGM$bdl^MGu=}mpEr`Bo@cnMlwV0P>^^Gg>&e4`YN%BDf;0{nuywPe z`IIPBxRb8ge@(Du;{>*-tloyN&A`%8qUtxQ#6hD`o8b zsD){TkA>5RQCI{mj{V={4XAwo{rN8k{=acRx&E*GxkZmw<^L%EQ9k^>*suKm=f!?y zW#w^BB^nfPhy(t*G|}KyQ9x|{e-qYKsN}O#CzjO;Dn%pE3=w`trt87G+Qfd zw7>+N;i&)&z5(J-P5?moo~@C#7t9r@a~evvVv|Kn5RXPCo-RC!8aLbHTEb}1*|r0F z%df{A`~LtMJZG*#5eF`jiwWgIT8Tz1{uLMj_KaBk3-|@tYAfLYs;wW3e};R2-Wx07 zS|GDhKED8QP{I$u%1QZrA~wS_7XJjNOnv^bxN^$XM2p2gfXl$2RDNy)%*3(yH=qSH z(qi$U*bwkU7K;zV{zMx}lu#YSRV$w_1EX7G@n2v#v9nEC9)VTeQ^H9Y@l^SI3dB7Y zSAe%L45n<)5Bzjrf-)VtL}MJj3Qs_6wTBR?zz)&Y*B~m;v&bd00W1t@L}<25>^Zvx zNo5;j%VGJ6>@^_So6xxGsX&*#4fSYDK|iwW(9x}l@T96Jbn%CD>=XC~J%-y@5!U?k zIXv)fgQp9YXe8k$a4Vp#e*%2tI^pk)KY;DTbzElk79AkVxmHePsJ|eA=<}zeOZkrp zf6@|-A7sHfg@h*@M3!hG1W`5}&^l z;0N>7luwj>sYJuV$e)x#3?~~a_!*Jf`U0cvf?uNE8_zXt<)_gvnfWa9A$yq+ko-(U#TsD6=m1cVhAHu5%uyPXN`sAD7KL6>s5HF!Uk@aK0Lq;}~^kXQh2^sqY zdyN2Obn7#02bj(3YL^Y6R%^v#r}-ZNK-I}<4JK(WV-B!*fDWKNgDek z#)yxrzQ{U@$>Kd48B96$UOc+Bim8KL7ggOO7#bMh5l@FRo`W~s#UHw(GC(m$p~#K& z1r(+d-@o)$m87S9piyP*m}F@s*9dNDh0( zvMXYO$=23ibmb~ChVkC`)#bTt5nX2W;7U8=aTLw=+|^uWNVJvHt7|#z*O0e4*C!*( zIDP(=*S%3^w28L*x)F90^CvT}SdcjEYXO&c@aGDj$b^?Ad_>q#vgOJ#K2zw?xc^Eo zv56er>UH@la*fm3_C+pXpJi6vqsWJ_^`MF*%5^zi(ThLOE+4^e!&HTi%d3bj>_Hy? z6jWB0RjO==4vjU5XU|ZeUx=~sw%RP1l$9B$tzQ9;q+JpF-uNik!1+UT$?O=g;;6^S ztQx@t#3inYwh;{=J#tgomysG?d%i#EhjjA03pCQEB38nj{3kMVBv|+~$D@&kX7C>* z88;t8kMgo5qgx-KV|jYC9) z*fR!r3b;$mR=bNBhRwmX^{vUF*ep0spB5egLr^QHW5PXHKHBFm77fSj@l>|Ca0WI( z;7>jiy2JGoGjV}19mAmxTMJO^WK2WJ3+`yl2CBkYt|vw(jhns5ahL%%y7hN*3bq5S zx;K)y6vLoOSo6Ix8>q9Bg}bq7e~r@qVSf)}(;=oM8XDX=0sxWxz)gv9gh)~ed5jgN ztuH5x@J-OD#1kg;BJL%#GVX&QUqWUTz_s*FCTO-??yY^ngq2ecca&=*F~jo)eq`?n zXl!(G#Ki~>Z;vpKebyv2!(f7oFKH%T3xs>GkQ!+==v=4QT@Y<0bLX`Pj&5h_3`y_hxWLB)9w?eQ16 z?~QLFzb4v{m(1QGza-@FWmXDsC0-_^**4+Yl4AlZr%U)A@i8b=b-aMT1n={&#s0w7 zV@)(V))0LGOJxUQ$D<~}BR)T32N*)aOgxr53}ZpzF2q0ay`XT*a8yvoZ74a$eT(Bb zbaIGh))L&K(Vy7P?8JRIiC{(g?pTs#->5x4ktdaI8eA6 z1mGOUlyn9Ey(2|gQmO6*@NHir9BN0HXx*8j>icP3cxjrVig_Kjgd z&k=hX{Dnoc)yB3&p=>Ls&tMa~i9N&fE;xZ^vHSeTVVm$PtR|Ws9)!PSrLwbe0-w${ zgWEK|@rOb0mH_IaF4hxSDeUj&?1mC+B#8`W$5sEyHfIH}JsQWeAB9QT#?40TD}Fw# z(XAKQTfLSuJKIV)8(rHOusOzlJ`>Oi<$LqZ+Dz>_kzL zrxM~&oGSQ?vS%DXw@G?YTkZ2mNXAj+=JL9Gj*^wlf-0JXRsTR zMHtN%;OqI1po6>{*T6Z@Y1@n6!ka;#|7vU{?>ZFXQ)G3#57gZ)9C7*~sJq&n`0N5) zBkc~SE4r?chv(ZL^kG@%u)?MKkY-% z>={q-$;1Put+p|+BU@S8`YEg#tQSy>DU5|td)b%FmPZRB>Cgq7LcbSnj?!!eQ9&Fn z%*tshJ&CmupW!(q`V=z~_R)dp2`pctiFSjfO)n=>*?Pz+S}=5fZ=j{xGKaETec);SFM|m#s8d zro3Tlxfi@!&BznpGPZiTw-IHP^6C$7BdQ@heaOVXc=<|igXKo6Jq=8#@YEqA!zo=fz< z8cDwI{}~ni!F=%Z@BjQ9|5F|Z@csUeq40lYT<7}+z^7Wu#eW4D)vAIDg$gQb{Iq9q zq_R9j_0jjQ7+Y+HdoyN2E9{31gW&?v5U|{RYB!9ejE<144nMC1#Nx!V(=Mzf)M&IE3gj9XUs8VAJ~#$2&bKPz%F4lx;XkNxF`7oPW9K| zhvM0Ara>b&$ND9<2_?KYqPJ;g;(7cikz-~_qCH&ZILYU*(OVbB(Zs{U?%C_a-HJOII>XZ^ zHeI|Stci9kE?ev#wmAA$tXv!xmddUWZH>l!l^)R7UIYvz`yJY52t%w>ZHMT&irbq87mRV_*38K&Ej@~-Eu#m2nMYG+rUlF-5 z{hs}X%o&k~)1ddb|50RQX0_|Qg5mUgxidW999TzBEg>oH<%gnXm-qP}Eq9N)TJ9KP zP!<|>^~*L& zno(YoxU}X$w0b!!v$~=qI-~T7>{j_0#;yZ93T~H&GxSRuN+cC(%=yJ})tu@ImUp47 zrR-=s=*b(^y5@KZR-LC%sGyQFs<7SpE0CWn4~arEozf})R?Q`vr1YYX+R!6RkMnfuqa`;&i` zTBU5b-zIcDSQF>^*i2kqJ}KtU?vh0BDo*T!#~Nv(niuhk2dvD}LqwA4y(hBpLvK?2 z@9Zd;SmT+NbaQ@rYuUDpJFRimVFwOK*)8u6e=aP}+H~5Z@pPV#Y(?Xx6Ph{gd66~7 z%_jR>ikwSjEjHQhrD2e-I@$XwE@mneDyZb3Dh~OBud2SUI)3A+mI~Tx-+{A*+w8_g zyW&m)E4sG+nbZO-JzTsjEsKV~34L#DkZp=L(mYJMGg#Oi#wD|B$?8}JW^A!jVuURe z%B=Q@No-{N=&c9ES(tGW&33aS02`WW<@7wM1vIA5@O+d)0c$eagn6k}U`U2W+P?Hv zV4akexfjB<(kHUQv~{2%qoIV7yafcL$yyd+V-WS2QLX)mm~$HL)=)tuJ^%gre~Sa( z?f<#mxBfo=&mlB4(k~*|KS*`d1e30LnlFb)+`- z4wS(}`Ws*~b_OWs)Bs<2N*{Hh2w>PFAlG*U$#4Uwk&zb2g7;fXO%!MnwiZ*E&jk@M zsgi0{0#;)UFsUaO)_#W?+8@D%aQmy&Ih?y4#32fI3D*)HK18Le$`t-gt^<5dDc&>K zuDpB*Jhf~yW?1_WEy3KejK+xo#f&kphBBxFc-$Cy(aWLGpT0Pb>i@(49j4F)E>$vjDw`jZ2~~{2fWfY}4fWsYn+P zOO1^5kx~@PlA5%0;*o%Cg?S5SCsLVBwfYUIL@To8+a{yGaBH#+?H7PQc=g#*=Z7Fm z*pRJo{{%imNa3r>lwwtT^76GvG+H1vto?)If(SA*8n1CyAaz-aQ>ykTEt64o?&$q; z`9jdodzo$6ruu%y-A~|?uqs=jP?aGG(AxV^A|eMRsC1F3BO6g4Fd0fhf~*JQpad== z{&4+H>O2>XCQAW@{}(KdN2ovpa#oDogWI4Lr&RR}+f<*!QyT{B55D5>?T`N}JsGU= zxAQb^5C@ee8EN*xO}|Jab>Sm;YD+jC%jFSh2pH|4P^jt+zWHC<=~Nghx35{imBvHNI^d5F z*89F7^7qLT%JVL%BWIZ|-!Bsx8I_o>DfUZ|nq-^aE#8%?Ft0T2DUMF1TK!_Sx|o+@ zXdkd{X2COw)cM1@&v^#%6ut_|%MsI#qWr7#~a zT88}@X=uNdmx?~1Nu2>X58X?n@c)9vM`KK|DRg=HwAf(Gfo@nE6BB`rpv&c}c?oDM z&9F^X4^9;Zzc={$e{Ty>3F0jO95@M=AvOwfL9S|9=er<&7+qzOC3WPwGo!+y^5tXK zp7@PwkYl8|=E!L0FZ-p_B^4jGR^%w=94m?1P?Jwxm|S4BMp!7<|CqCFWlg@3(S_`_ zD_vmMe4VAamX@F}za(3*X+EE7^{OCodlx3(=6j%U7tS@bA60hMUL=q@&n{=!*TpK_ zP0PLQ?cykWRaqXVePCc~w8Sv^LE%cvi^)vgyZ1X{F((ysUgwR+Th37z zPAstI9=|Hrf1PuhOSx}kWGW5DCccoGSfngQYTqi%C#5a~;jgGxPjb$3mv_pyB^0lt z-)=Ir&&yxv(+#&RsAoGmWtJ%1-)COjQ&>!;s>&3;3d-a1a?(6j)EXw=%=^th^Q2Uo zm481x`Jy7{Lmr*g(?%&)y;olTLb{#2-(y(IPyPmc-()oEr(}VwSBg`r_flG36px_$ zHp%7J@;`ZJRvEUb+RwQAD)ZW&>SD!%zwl6~;2>XqT2WK-MY(cG9qADG$hS(Qn4=ds zK46!Ky3jB%)1NAm>)Q`LmzuN$MtG&gD9oDzYrW!Ps8*`yFhe{5-Vh=%w7=*71SImL z&V2tv!e2;*JI5cc6A~1@s!S>VtA2S^AmSYmV_5qvaD-=DOh)7Nzz?2fF^W@veJ=0t zpMZ&YhHa|vW!&ZXKNhvZLnKt?LHvJCb5|}~Qb!&n=@CVkkrB#mrSst6jB&>^aA+pq zaTDojP)QAVQ$i~+L;GBQXHYpzU#}LZ1=U~_zD%$%@DOI09LifAoCJ^W6cQ6@LX1+J z!JR~BVe<0X+(pa)IEc^X&SCjO^VrNSj*h`7Eo*sSL#i>kyo7&0s1!47Q?&<|um!FG zHP|4Mf3KJ+S*-MXsUvgQ-BNXEFf0oCdf zduPTxXw)aN6N{XoQQyg;Ri8to&S#k?>Ii7mE175O8E`-OS>|b2HcY<6N-BE@)4r|P zpl}U5gH6u%&N>G(Z9O?3BvC+KeuGmh_zDbbzi~d2CXk9mj&|%*Kxt89e<*T;#(f7% zQr(IewyE0BxLd|NT2F`hF%8TIbu3kW%#W3@9~!e2%%j1yZG9nzG3{`vi9eQtJc3ML zgRNutLCg3D*c@qq8`>X6eLUyGj8_*l#pN)jaJNHi_ZC3r`=TMPXJHz46-WxaiYszv zV(-Ea;*?@ld-C!utPy<&nIDeb!mAO`bDx!!}j> z8FzP}$MznCiz7bhq#?J@TtdO|`gUI#LFGU=Of0c!Rsk|+{FCWrF2Rgd9HonP|A zb(UO(`zU@=y&adr@8>1eZRQ##zac@{4VX5}AvuKskl7~A@!)j+>bk%osUwjGH|0vKd9sSz{yhx zb$kbkxfntnUxXIy)rC5~8+CRyfx0~tj||j@I<0C)UT#X@=q1#!b_s#v*U*f{RBjok z5>=d1wI`R)1BRXgsMD(UGVbc2#d~$2PH#bn?K2$IX(i78r=Rcs|BxZe{yzU_7bPqz zD1zeik0XGVtyeasoCEx)TLG0UDH*Qxyx=c1?aE)}t$QWIRY$Mbsx5=zK5y`ToC$@P_bsvv8o1*g7vtjdr%zf0eTzM(Kxty_qt^(biRjS44X_O8*V(BYa5B=!)&}z zCo7~3ZY(IjslmLasq{6`(G>0-`$uV}{%X-Fmv%*Psy?*VGOy@NYpGHYs3{-v;oB zKLNY%=LWm&S@48@NYz1p_}46>3hqvNsrKXykQUt6&$Sjx9_bC z3VpBne%xxT3=BYWcVgSnKS1A1C+vK*l3Y6sq4z@^He(MM=1@9YFhevGcI187=D7V> z$EvNEW72(y(>TmBjf3iKI0{TMo^!5SvOr-ehdae-UNIZ4wIA<)lF`_`J&V?7;BvE)C90-Vfkz+~3_4C9T5%b{8_DHqlYOJlh6ZkXZ!~_`mZ^Ka;8u+Ty-SI z`*CYn?cfzIo1`jRP7_BT0lSHmx$3l4;2LSVfEzX%loH)~BfLTJ>4LFq=?P`*ZoM;WO5!flW0$-$jf`=MFf!V^K36m&ykk|-#zUe}VO-NjOT8HN zFue^4RySi_hF!M|v&xNS(WdU3zTv*)5B8DJz3bW&|AYbgC{w-U6gb3|Sx^%)IfEGg zb8fS0ka4Q5{I+VA^qJ^mr}yK$q8ypCx5y^#kN6|%xVhytIc-Rk^M<~e`4Ro}@+~QI zA4Tot*z7W0aGGhutKHqL=Lmk6z!OlG?BYwLCwCed#feAdF5K3!YC>{rUWzr>R4p|@ zwt1u8hCh=dGVIN-TiPc+NE~I#uxXFi5f_@wwe^bU#TlA>8T0Vw{IKH zd~~S)%x3d4BwY4e7BSm%PIAo-ZCsa)<*lO>l}MO?S2 z-*_{2`^@%;!;aSVc%wtJ?Gp`8tG2$gDb9csXh@Ki}xm4pR7}*;W zyTreaN7IWlGCFpqC6p=dSat2PTJX+zy$zX{^8ynS8hh?s$zVQ2Jo-Mj6f-BGqu(s5 zOJ<%#9Y2T0q%&!7 zx{Dv8bS!1ABteY+iJLA+Po$&cVTV1QK%%uo$Es4%NZ>(mO-lxyca@rd@0dU4u#*Q>U9~0NR_*R-MFz=wKW<*#-t1X4*`(geHTyRNTTauedYt7I z);DwYVRl9yBW3Q$qe!w1M8V+1XbtjDzuEG0kmpAn!%mw@- zo~MQ;C=X9dueT8IS2;odnschUsA`L}t=f+GRk%(8?4rr9v8|P|NiOLargsG1DVHHd zdk^=`oSpUsIaZxA*EwN4vA^7O!Bv4EVOm+YUKld5;NZd7B>}XuBeGH>qn&;RXA?`H zY(2)v(~G#KUtEjjo<({av|VRhSY2}6(qUiG>0JjIHeC0ThJuQ@w!1yw9I~oDZ*Sy@ z9g3+5b^5SxXk%yf)O}e_$Ik4oJn31rYxq^{Ae9=oeaxNc;&FjGHe2rZK-*%zWqMar zp=;#2ZKii~4-RK~?f$d%+u<@~iqE9_CnxQ>dXejLBhP-~OTo&7Z|Cx3Phnh%cgvOp zZseBy#&fSzqCM`M3_K;3%C<9ZchszxSzB!%nj@<3P0A^ zgVJ2m?;ZE(n-Fm-W%u#XZ;C=(vr>;%z5nE!Q=Cxm_<5=4i9_64m_hH7bUeK3M;HCK z9;&unzaKXZyk%9fWRq^;Ygi)~meVqcK8_!~Z{|U+AwDN6W$sX9J9(aFx*#b0D3Kb{ zt@pumE4bnxyTsE@jj@PoWHiU3HZ;qpV^#7>y59pIu4&53$COjvdK)rUcKOWnzHTX7 zIgZlrHJq8WLO(Ltzp>}@@>4J|)1xn8brEYjKKg&K_a1OfWnJH>V;jL48;pt-0l|h+ zLlaR%1Vs=5MG-N9gqBbO>B%YSp(h|s0*WXKs9?c@ePThejiW{pb*$rnI(BDtJl{U& zjDo)J^W68l_r3Rf?)w;io!y<&Hf?Wua z@1AR9QcmN_b-Yw0#N>_qa@rx}_4iQ^ zCZ_=V=h@Q_%Eo}TwRGkHS)Ytj>G(_@=&z{kaAr1{*k@~9ayp+b-mKNbAtp|u zAFIwmufJyr&(=)^LhX2A-uAnac_X5Po%I%AwCFD~tKJ3`S>6yi6)y#1lbd*ib|Vno zPKkPzgaBdotY~L(I1pYl#p${2KzQ9Krewc^pB0seUuHi8UGJvo;TkF_(tW;0B?qfc zXeDAxFv2)|MozCyUB~J?{{ct-@<* z2$1L(35Ch?fUG!CXqz|<$g2y450ZRRF){j+c??;t4jK%q&73>4ZZuwo~I z!X6OVX08B*yd&6@x%NL>GveEjwS9k-m1?Jfn%ytoQ-y(=6)D>4szD*oDOB5k1BJ{| z+^=s!ACG=0H?6h;h5bS9Qk(*6_=wCwYYS?4ihNRu1c;~;<#&rEpoW*q=47A8-FC}X zWCLd^*l_u%;xw|RzgXjuOrC}=;Vy$hO!gvsQYL}AUV&65^aXW26?sj62I@KpF-^cE zMV8j6edZEq=0piPg~x=lryJ1)95v+ZJV46lRs%1Gm!(d~D=4twrSt~;7U(&Ql(uB5 z2u)nPNq_x(<-q^f9KhHAW*c_TLwMzy+T#js1dagjH{$Il00IVkB&i9v5L4 zi8#H`{BI+;Z58kubTAl7N$hd%jU@XR#anbHVwr_uB;rZ@{Zf26eKuSZ4$N)ic5Vx{{ZDfyZW;#Yu@M1r!%3a*=;T(Pya_2UnOiotOt5()Z` z0^O$r65oQ$LUVGZ^=cB+V_)g-@-*)yGmo!bki}rc&3RK5QFvuTRe;xqzEy2)KQ5}- zaBL&6EKVR1WA{o358`imgy!T*=+&gi^j)$8E7H7Au(A|wi#tQVN#3hE9&lUe96u`8 zb>@`~@8WJ3j&o{j8xZ$wZR%vOheaR}ZMQ^@2gzKL5_y_;a?V+32zZxzO}kO-#EZ)B zD2Wg(O$*&?Qv|HS?jMNVL>1fZ;!VKco?7ggyy7V^lDN8?*ht|rg`_`Q0tjx?Z3?q?9@1sP6 z2S`4$fN`4?unYfdP91+VXmBJF#GfRRZfZAir8XaI$YMfc!4SZ85((;V$)TJ42^7z@ z02`lcg#EdzK~6{{$Un{a-PEM>Uv7WiGjI}(MEpL!57)cbl9+%w!G3u7^Ut>V=waV^K|#wu zzub$#U>pXUm508#{I^>J%Q;|}o71~j(Eom{KL1y-ewpIe@K+A}>vG_q=>Pj$E)JTr z+|oBBXx@?#%LQNE0DCyNyZ`GP0r-Ex4X~3l!-4J&RPh<^u1@x`?#>i@SB9greJmr6 z;^ItoXV9Ena5up2t~5vIICqM@GaV?|yT`fE?5PgUH2YYOST|=kiZjrOdTEEEvGwx-s$LIi5~=V zw49CyC`7;w|8^yAP@M4J6ldbw6@0rnI9N`1as}u}gz)=r4)}dH2N&Yo)p9z1n_@Yg z0sxQ84J3wtd*I(hg5aPHLFGt1?&xee-3{C!o_8ctal^ke>HJI%a4~J^Z0cYcOLcU0 zccpmPySmVv>^(f(==O96aK6Xgh3-gkb7WAQ9Gt-kASZ^SldB`c+1}ZM0?r6IxVYH6 zI5|4n$5P_JI74%FbE7zb6F`n`?o=m7DtO$L;biX)uG@pbh-&W?=k7|U#de25%{&X^Ra&#Gcrs~oj}kRge(_=;P<9+Gg3@@v!{doUvQ|)A=aK6>k?-l>+Iy{ zL8rRAyU}s_j~V!iUperV1OLZ3fN%BTE9`&j{}10$>1k;Re*oX0=+!GEK`n_v=nN7u zj70p-r!!igbPxW1Vqrc5N(N652%aF2c%pm9t2=??Nh=nQccL>$#4r*G^6s6iZu01a zbs=L>I)g+EBatBQxl`5sz(&al;S?2}K_c*5LWGG2KHsV8e&l84g!LoN(-|aU7>V5_ z=zf<*mUskTmooV%ok1dok=R{?FX^Xp&%+O@=nN7uj6|IN>*p&6{+l^~_kX;Z#xrbZ z6H$Nsakj0^8`t*3FZMj`gA?0;|E|7m0-ufmsU3wxGp|z7=#IxS%x5T~zhSO621>J| zuUi!wf-MRLUOQOQ7zPy{yXw1EA9-?3!WE-l6a8(;wV$_CacFZ&Uj1}z9W8!w@skS| z*99|&7QH*SqRKN}UEp@MZ-p&)PLAM=dx@FonreOPCM|GyCEI>-bWW%I1ajm!Pi53x zg1#}VlpFF&B?ZS8NGnQPM5B&8hB(!>!h!=V5w#W-Jl`KL2yPf97`m^T=h^hV0BM$U zZJRv==XO5gV24`-j@wsoIt?R)Z|Yq*S6jCVzpr)R7|!L3UR5PHWDztrl7`BEt0R>F*rN(rEN57-rqr+2-CVSEE zP;tCrkd^djI3T6Ttgkd14olXLe2WUfqh3k^^kpkd^V?eSddIjP=? zIXc^rXOz?;FWHTSbjemkmQhhUQ&NREWoq;bfoA1}OiuMf@g5|HL#-_njYEsL!42U; zHM$4z_6K1jdYW(BTqrC>M+k?tgo=Ox84-5aMeKlbC7p(u5`Q!Uy4t!N;-h0=!#N>b zhIYV>mr!IQI1OB33`f_YG*sPj9o3?DP|lsm0z4Y(*OL!BVO0b`-3c!9iKv)hL20ana7EXbh%&u@pDGs6f zk=DXwahFUpcB^E*BvHz>+6u$69k3mRe9p{k8Zrer4ivs~kX*DxY7E?rG8Lm$jbXV` zH+BCkedI&wkgU5|nrJ`SpE>Gm4sD0bOZ!a@Eq<8%)tccs!OR)*{^fACXZju4aU~26yDN?-%4sJ-4U(gSPJ)1Tvz=>qYwi!`& zR2+wfwb-e|Wha2nuvSGadH~qosjAOj1=#&TJz5?O*d46-4*dmDcZ}1VhCBhgGu6?O z7?k=jQDqQ^p`On)@->pn$gp=IXdqC*{n)etYd^l9!&yDyGgdo5CnV;GVbDs+4$&jH z$?O#ji59~8kwwyS$vH?f_7qxzn!{WxQzS)E*Mrp*L<|n{YobwkhqMc##cx*T%7dX` zCQG$L;SYJHZ&1BaZh*{0yOb503ZPkhMDc4@DcmVvDUZ!L0vYne$b8qqoAHr$Ms7fw%+AOHIa@)SX_xilI>4H-rKngm1+*Cx*jX=Cui) z!FL9n(Zf#sUyv@p68Jg)8`K!M9=<9#2^)fsz;xk0u|9GXf(mbEYNF>L-a;0eLkmT^ z1XI|wcwgk50L}G|xkKb2!pMJcc% zxt0UE;URcWnE`aeR`^3<7wCrP;k%k=pd0=S^FG9qf=M#R0wyUp2u^N4&G@5X<<`TP7 z!=Pb#<-Cb?O=cUj>p5N9^&`jUxCs60G-EFoen8C%xK=Z(1}NBEI|}(6=2Knhhfc{g z1ivaUm5EFAk)?TqWP2+$(HC>1(k1I@@#EBMk(>>|%)!cY@WTzB=?CPA@T?j$(Me#@ zp|!@4w^*Kq2sSkq?vfuu25zb-bynO(j2krii;7Ofq>58rpgfKoEDmmX2JHO)3Ccf6 zt%qv_w#^q*I(QbCF>8=9$Wrh^l6-#-iuA?m052)-Jh!Xw1x<7KpKPPa4|z7_*Fay{ zQMEv|CacM8*co1>{e1n%LB;#ysg|0tp{zX!;=#4bp7WQsC6Q)#YnHOGJt=rvjNb*F zWwNJZ>%8^4XGwOHDgGseVN5eG*@BqD&LqsoE7U&k0KLoaUW8Uww#pcovebo3U1|vK z6MiM>W>908+miROZ+!KUo5QMNioJo1vg~Bk&t4o_e)RLmBhIw=Suy7$*VqO#Peo$U znU}%K1qZr4;J88GlaF=x+t<<~*6osqP>2BH*|awRZNBap9vqf*VpI zf>-RGcI-U8EK}X_#J>833x{*Zb?U>Tu>$Ht#bjm6Bgvw! z_tW<6{tfYbR^{;hcCpm<^-HI?O{2j4+Ua7U-v|0Ig|fNmM?z7lhkaPQ+MI}L%bQiG zw)ZlcOjvnR5|wEfWG<;=9{_`Yma~$*d%u2U^CW9d`A*H)u1SF+x|(a1yzp0PpGA5)Zdxo*_ldBhtO}cvHF}AeS4S8%OC5sw3=W&Dsa@3NcQPzX zwJ^jOxMwLviG&-16)VdWcUOXHqczKI;`EWnqBl#+R%@brhlIkeVI11Q*@5EX03ce< z%oDtwAI$vT*My4(d8Qi{4B*TRvE@dGh4O47%tVV8uZG6PU~ukiqih$qQ-0g+rsVf* zqsG%SkSi=VpROLJc$tks^+YLpCg=PKLR zLC9Z2khFK9r(?M2 zag1QG9p!g%-xc45n|ZaOR$*pKF`pT#M+^5X@A5NbJNV~C8w1^SXXYMSWeB#ZIO%7I zYz)iUVz(eASRWbKB3~ZkuZf;~c2wps{v6tboA$ZY;k0Fy4%lJQG4dXEcn<(s29)!!YzQ#vM|TKn#Jv9^*E+%V$6Q{{I; z&!!uDJQEjZ*fw9jZcf~xtryFzXS;1y;G{=51rGG!=|W;97vR|@xn zRKhiVb1!9RcV->JUI!np5x{|(Jo}O(-A)hnzVsPFe^I4dW zDX$#)jcE)N3Kt`T(hR|VB7cODu8({soQ+h0iLi>l1F_2ROwZ@HBSjfzB0u38q=JLN zA);ucFTYbx5!)fBMPR8Vehb?IQ6WjP8Qzl5skVk9;JPi;+I0{Ee%ct^UUQx zfdQ`FXxprZ@58CZ!&?3XM%UePIt|ajvgJLHLuaAC!7l`cbI#~P*n(eSB*UK1AVnK| z1Dy6I^@jipx+jWm);JKXnI#?~czeKTvXx1dpImJil)kiU-Ho^=Gv}CRjj2ick>`{A z8}cyrb_K5aLCv8Z-Qlt(OF@f=x9=%> zrwnG^-{P+G&-F~_RZq&jQfkXJtUal8-()6gS+g6SzXOAx=dIy+?eCPo)+|q(e#oe) zRQF+>Ib_IdS2rX%9&9W;uYScixWA%wUKTgOxKE?MloybB@&J%Aa;}Q{odwdD*vNfv z2yO_@&VUyj_H61Zy8{RAwr#Gcju%(%2XbpeWX`m$*x_?KqAQS!PQ(7Dw92fCtF58i z1nb{a7|z|@{vBLdN@;P_v4DEXbdDMruj!)W6 z&0W;xdvf)5j~vfuJ5Ej5EYBMD?m(+s`2gV5x~_F^kts@4O%J^*yvdUF@@_|1F0ICB z(zX1&&h)5ZP!u@-x)8{8xkzSdAfg{hk(TIgBAT&d3exkcL6d!~pP%$jIoKGzslwOL@QdJ@cZQMGFiJoZdCN_T13KC*2HU#h5Lo0a_LtpN>_*- z)&A!-`u7rl{*;59Y6*H-ei^0K`ql(&d!-3(C`NrG`%urO0O8L`GwW@ehlo#Pp4~O9 zB^}zvX+DJ=?kg`!bNi{&@Qw6A@-L^Zwnj-ZlI9&WoO>ZjNPO1RcxkpYA|bNA!sw*( zN!Yeg-Emv9KgqkAb4Q-_L+ZSB)Q2xL*E72Gi@N%0&hRdlfu))HThWqY+tpQ*x#yw=FT6K*wf?JjxA&*6r&xUbmsiOtn;VpPr*1>Ca7j zpQzQ06-e_&ec*GgYI%CbB4O~f-}sRk@5OeMY=H~%Gi>Hn$+bov2r!@M)HLq!#4f)X zaWQd}C17X?o)o%yrXhI$q)DMax;KVBG%;n@FV;uezcepG-mmSRX>EBRpNbpRbKfrFylo(rXv8gbB<#IDo{&!{BJm4C>Xwcl8ilv?M zvqJ{wZ(M5B{5)(ZILKhgYyRQe{JTpV3pZFS&&>@7^)gtx`j zgyCt_+CDQ3v!{dyH(Z|2p`YUb**wyb^E}hGc?#o3?Agp=EvpmG#${%J9g4K+DFMPx zgGPLcX$FR<=j!qCaj4(UES`hb2jF=b2g z@hs}YBPk0K{8Wp&{!G8Zh*o+&o6MP$_(5U&`XYB)+7kJ&cNY9}{Kr6VF-|ZOIYnx^ zf7JKGD~a4LYA8EFbwG_K8QKFo$H)wW`s%(fH)b}OH59*H$%@vG3@o`A^B`I?R$h5B z_FxLvO0xb=LJ49=nO4!jkC&Nw>GT8GSxU^uuDX8J$h@^!DJ;i zBDK~tJw|oL;@d5@+~--oqi$?86V>FWjT*fUgJX(zj!?VR10Kv9M_EOp7@=6{SHH`J<-fY4pzepT-;o^TsT_`MBW))LOyX!ZG~3;D*|= zWn;GFcsBi1x^n!LEZgR-rTU3efJuz?C2Y4;MW^A`nw;4$Wmj7R3JVteECrn=e{}FT zwDD4LPC(c-xWZVkIkn6WEUYLh*$N@dxidum%jyMSfi()<#jb`Hb+OUgnHXgIS}Q%z zJ_HtA*0L*^MF`PZKIu{7wO|YWyZV|4-yACx8%+!nws;3vmW~(B-nCW&mRr2Ud-Y(! z9w8WbNdOk?dxS&x50`MQ6e3CaXs|4G7MWMwhs?a}MSs;5ffL{=(afXoBwc=&MbJ-2 zCC0!5qTr7HkRccm{do5r)EE{ma`-hDOf4azGo24$4sEt*z;h|s`j{lz^g<>HX3iAJ zUOW(cre}##p7)lRiDJe5e{Y8{_<-2^=^e23(J3~6S_2mFe~Ev6`T;D|v&4&j^OI6* z6U9p(8l|31rJ^mjhRbZ5C87b>6J^-po1(T8l|Y=S5)~aTQeJIM5gprKqNp&23TN-3 zN!1;h0^#1@5zZZrAY#W&z`ePmrDa=?MP06<-X(Lv;vibIsU#n^eO)2q7S9A+JT88d zwdc!|DcvK^8>xBW}_EL<&DeP}N zE+{(XoKzpL54lE-SITDZJ%xt6nv}gh?j?E}WpDKJ1Vx z*`qa!y42F?nP*j=&wfUK%`_@)UynqeaNQKZWCz&aDJ5s@PkOz0Ejbd6eZuOML9pCt zl0}fJqRm&#SL#O|SzW{&4fbFrCP-K*V0UUkS{1DfqS=|JJ!Yt( z;A#ESd|Anmr{j^--RTLC9i=?AKR*I=0%h7j(NNF{wzB&Qb3i8$X1ej7i;aQ#oaLFB zVngsqu0PvZ+!)5^x~5M8do=0XjP!A0O>{ALOoktu6N4KxVr)~Q6o#a>;oTzx1|&;m;*%BH>uKD2l+FS_mdMlS&!(*yRL!GB`9Fk~SkLsWY{Nbsn6BS4&mTLw(oA%i@$~eg6&Rehdc(zmD>~)lR}3(|Ut!e5EOour z5M#(wg;m@Ok83RaHMHNuGmMJT522?YgfcYxkTCbV7Bo)v#AUVZj3{dDgy@0iJ;Q<< zV&Z;2{A8hLQ%=JDy}1i*oBfm5@BB6hJ1pglT6b+hr=gW6spty2+DhXu)Q<=?oNMNH zRo)42ytGPCUF8y4VcaQnt1OCAcT|YkX!<` zpi|kmB9^JZ)@AO}6nBk&B)ed!I+d>(Tak6SP(tTg0do#zDd9A`JBnWlF0KlmW~1Dq z;Us!G^5xC&`wTmZ3(Dlq1>Hu4tVrJswlouD;VA(kV_NjM#)iC(j2P@*7;Lt9>Qhk0ftE&h)Z^NiS4;3Zm#PGA!Qw(^P^p_8j@ zxs$Ymr9rFAM0;{fWy8Qu$7>C+{zL1Ozsu$de@`}QHtEKuZxk8w1f^DqiwYYHpOu@% zd)HT#?x_&b*Y4No4f?j|j6Iin&M?7El>%mULJc*0+QOniVCg6@sx3}@qZdob!N>hc=C?Au_*{bdsp;()- zw-(Gb@(<}($`^H&qdA$rYdoI~hhqfa=G(rO!+b%qdf29FJ74l)Fdq2y{^p;Z3;3_b+N&4h15BlW znA^ZXs@p`Y{vjdZpCwHM@G}hmJF)iawGQO9&uj)`_-vd5|IcGBFS`a}d;#+M-E6S; z0RF#z{yjMGN&hc_E}&6q0@DDxU;?26xGXUx*>X}qDw8STrKZMBHjQgwps_^J{3@6p`8mhxYW1E2ao`){LPra+U|!StgEFjIyb zB{q)g0hF#C<6P{iPEH>7PVR0_KvmnxgF<(8r!wd`{l8eLzGmku2flLPzrulka{gyd zaPS;(`Y$9r%o3L{!vf|kp0~u*)Fh40W&u_I#MG1waHhjVWNKnUqh`=!Sk#PU6E|@1 z7XN^Ysk{_A`#)TbXQy(~$g2*2e*i6=OJ@sGQ~r*m$Bh(lWF)3XOyHKg6Zitp#Iots z%v5$vVw_1F1Dtz_bp*nN6P;r3;t)r%clU6l*gLw=Txbr?;FN-+`==cJ*XfzmjE{-` z*B>Ga2%i5GrK71UadyWQoY1ic0dNw>0eriFBR-C%6mT49IsjMvT#h?9 zs^bX0or!NZ@crp@j-#pD*CT%amCo}&fX2zx(apis#gv@({irFic*BZ~b8@Fqz?mP9 zSa86TMsov>nHWF_aF3%nxH5pxC7L6CO6Y$;`M(z7D+m6aIq(fAJie1god3bU@PGUw zZsivM@w=z~KTeEqPXh`lRWTrx&L9!PNF?tWk)zV(B5zX?F^t3>-ZMxt3_fnaU^;_D zToDov{s5hulwApOk#Pp@8~ygK^(dOCwd3?q@C{LKDOuU@ny9qqi3 z&L9!PNF12!Z7kv?PhS)nSiS8hreok1dokw~QQpPhCJ zg_;)cj00IG5M-S|g8V-_?L-yMK+XX6k%(a=_Q?O2M~g_k8aji-f1+p8U4lPKf6f+^ z(-|aU7>V5__%ex@To!NZ20DX83?s3-2w&2_NS<7LFqh6C5yMC%=s&(6?{0FJvql9q zQ|Sy6F^oil_|tpbZt7l1Ir}@K=?oGvj6{OE+a69gxkRuuSyD!4kceR6D|L@KhRr(7&4^WawP=40`yD917-bywo0f}Vt-TfaA zlcgg2dt2LjjwvJq&QnMv=szA7{AB;DAGnO$|2kK60Ron^D0!+rx+@#Dy6F@NYueh* zyFW#O)?yfmL=4^I0Ujh1Jjxg}s2i3EA~cz_4V)NngqPvNNi5V^CE zCcCnsQZ`CZAZu%TBz?_&40N|hBp&a18WO)iruWfq*Iv~e%~~Gtk3b;tz{diT4HdtH zyL1$qA8EZ%WQnGE&o|K5t`OZ8Zay%5LpncnufuNj=0EvuZ7n;FZaFQ$FcOKVdgzVe zVbWgLv@ClmR{Cnv^*j@q`Gz!a>%yt>d-a{64aJp;lRIt;!z)&6HXgjPLAx$2Z@>xQ z6Jdj-Xuw%u@ti;+-X3~Sco-K?h31h!Z^;~aHR*TYHsdX@*YzRe2V|9!!MMnLBuUA? zElf@*63;D)%8!p7B62IcvY{?IP4J68bnpCS=Xs9RZEgOG{kX4zWi}FtbUuztc!1Rr>h5u=oBTDD%pC{pbcH~J zL~;4x#4LdX`KR~!-P9lLbhY!F1t&n4B9Wl(e!tvJ-Ym-^>~txiKMIzCj!Ggy{txVQ zt>P{QcDfui_xJ|zo|HtK9v?~mv7IjPe(nFZoi04~ub;0R_{xF*eh!emwpXvU5&b^- z{|Mu-!DbWK(VNFg%_MyQ;mOI*H5Y_}?Jn8tDU2Xub z1!We$Nq7in&E9dOPrWPFGbBf#jjBic}r z=4}EuDh^lV`;tENw$7i0s5I&d3|oXqH1QRn#2=*>oFn)wIj$e+Q}5LzL?VLwA))TQ zp)~Y9ymfxNWW16OeDO3$9x7vitU@9Y1tHb^Bfb(8gfxRw;I;9Q^WLU!0pIHcWfl+M zKj3WIUcx`~o|T|@$ROltBrqO|j72Ukb#}(KpZUbYEp(3Shf6=cQfKFl zf^+a(Fp~}tG{Oted|%Q>-q!icL|0)yXo+|ua&{(t;Dx$NXD-?pTA1d&Z)W?3KyAJ+=>u=;{N}mt>%6ks zg6jOUt89V$QWA-jx}_+bF=WO`-+1QJf6bgH{h5*Gy_na9nkVM_lD_Y@&iCPLloqG9 z1x?7jC%q4>Y?4Ss*DYn?(czNHxPGKhyuIT{A9t@N_0O7yjOREzcdD+#nVD(cBNd&H zO-8;i>D#U?$X9Vn90;N%k%+dZ^o2)^FDu6NBYoYadEZsGz=fi=pmSDJm=?nt5e^n}WJbJnY!ok<^bq3$_bY$G2*Y2KQK*r;l0zAx!>ZtMJg z_33`Y%G!daZ*B4RSqEf(0*S2kkm|bgMf#%iHl17aBT~6@p07G(1Iz2itd*Gu#17xI5X7d>-!CVjz$y7$`ZlJew8 zns-gzy=A{|&-W#LzHOZ!kz*O@Mt4TW6&2+~{e*j{-@{=q@*Gsllf{+C87us*(YfdkB#g^Yej#Q{Qw5jaPhzb zDg0^@OFS#r0Gm&rA?~Bq0aNQIM2u2vFnBVA1zVp=@_qLTE;I`yTjz%gf(}|r+JamJ z`on*L!IMNHm2N2%Plg;_T;L(>3FsB;LZnpafxeoQi0l%XqhPE?Xo69|j{IfVE7J>% z1_2NpbrfX&v}EkkB;dX)N8%nH1PVbS5&b8r6_1@9AA5_08DjW~b+(|B1A#*95hQcv zAoB}_7uj)OoZKcxl7@ou@lSC_qA9uS(=E+*^TJF1xU3a$wp_+9m;yMfmeuBd116MX zGH>lnz+pSNsB|Zi=FLQlqQ@uiYf0maYR6 z)pfC3cog6@iFo|~r#~O(|M?M@Kh6KXsqD|3f8qD=d^!Vf0YCtlg+KGCi`wwy5lD`oUe_;W9%PCLH`cwCAdHUqd@+SUdR;lnMU?(pYE%6bR^ z_}aMbyKj&N)l1N#z=;xIc)=-y4AX(9SUe28%M% z2{7cQWQ9QzrwlSo-$aIi=LF(D@%WdM8kx-q<#~8Nu!=(BX9eo+RV3*#gEo0C9m7cM zE>SlfnJ3}y4H0h+I6Dv7-5eonampaW^i5bHW5B#X?{tEW?q7`!9D_s zINjIJR}OsTz`rF2KI#7n=)m+RuFAI@{BU%Qi}P@FjI*cF-JO8KpDV@Qi4sS%cXFe+ zde9tQfC?aPH1yw+V_ysXl>`5CIPj_e(^7e4|Mvwy+@0ti9!|~;K@p{rC$`+&3v} zF`dax%pg`lxF;mmK(L$zf9=E1z~%^>8)&75ZIC~bn(e*8lKspoEMzfs9+rBtqJ}}` z@Kc9&#;=lO*lS7v`-&tK9&g90-zFIXPqx(FBN4+$#Dxmhz(!~gf{EIPAB8NC)#7IR z$B+vWF2O=vVJh+~#EM!7ry?;hI57(I5n$YrO#^Qf?!v5kV4V#t#kKcH#4r+jYzC0g z>tNcN4It~$aGq`o)NF4JGqPDiEMz+@f!#A%QA3dTq6Bt3BN1$F4NVVVmm}l($!V;5 z3v?3aL5lVsi5NyA$Z^k21HxMJz}JvpMHL)q^$xyWxSBkMnyHd@=EayxIP|q$u@xfzGc` zf^qs)vZ6La?IN_kov{qQD4E$9z}^iXg%<5#)pLI&_3KRvovSkqCl(ajKy;M&}RFrjP*iv zsf|ZQ=DF0-(K8$#{Af10TQ+tm@3?q@q&gYvqBscz$No9k#wGZE+*(UsJOQ2O- z{){xgnr3_Rq6v(pdK(YLn(fiOigkW#i{;C9)FIM@({f#yr;lc{E_J+=HY1$3Ns5@B5Z00oehx@FklH?Dn#}C^XYngtk z_YVWu{wc89>_M~S*VHH38-B6T)TB}KUQE4o+u>+bg9r`9gX%{>@s<+mvi^)WQt&uiG1vHX;chfnPxYT+53 zpNHNeOnMdzF_soD@;k?hGAo}OYk^&>d&po3Z3w~_8y7gbrTUL^7qBN6WkdpYhVP>sF{ug zS{1>Lhy&7W|EI(Wpp8dRCIy)Wdhhp%9tha94e^ZiLTnIL6iUOfopC?T3z>)nusI1H zh%M;d7fBi5NyA zk?5ywJUmshHQwaso{);cKq!bingXUFH{p{5JH>$Y!mjN%B<+lSqF3ejpa6Dn@tY!l zF#Ws~&sGfsMIsTHh9|;A?A_ac-G#D;=f`ycPI?OyV>e|t+Xsm5(#oVZ9z|$Q>@vE} zk0m$8O;~}2%##}FZt<)r17e-<6pXXqA_J0PQ2_fUe3mr_B>Mzjlo_Pa-XjsiNF|G1V%2#Rd%1ESrxKsM_Wg9XBt?D!KqxcEU_G9^5=sJdt$6N6aDXpN(O@XX34ud&& zrX-!)2h6$G#bv@IsGTuZ5`jDdg?58rO(9@qg}7O*1Y6rAeqv>JwcL#3h6H6fTVrDLH^A($`;?B;QIy5u0(pc=Vs5ZXR`t@yyo!TEt!KQp(+ zvrC)pmn4*>UM{fl2*{k9+&@$2H(b;NPT^o7>x51TKX6%5w&I~o@EpSp?o4k~2e2(< zfuf{rR=rww7Dx};dn95QiFo>7KVLcU|2Gcc!XN(e#^wKyKk$pV?SK3q|K0OtA5Md- z)dUW6MLcDZYMqTy8YJ7OZl^qv>t%~oHi54dzf1FFtf(6bzVrvUnWdNSlu|?(oGe`| zo6ir>9e{t7S@Ei_BzKf!1n{ zq*Op&%`DsaGkOL)C`sx%Dw6{I;;%0jpzX8TkrgqVnTKb|T@kudNM zWvWz!OcwXhS0;Bxg|?;!4Mi3zt~Z}YU!WUg)~#mJG17@Bv;DkuHChJke*9Jn zBX>l=4KUgU|0cj$-9rzW%t9&Rs@TZU*%ZS^l)v!WDG=OSb02UZ20ov491NGi@T}~s z(q>jQG&n0v3Ep%yvmQvTTPvaFEIG_dL+7@`oL^sdP=43yX*2bDUTO_DkCZL)UC7|2tx0IFCv(iPPDPcQK>~P}?ar zjsv(Xx=mom^bFxlHY;k@jI+=w(1$iS?2z5pV6d;l0L_d10A20$A9F$rv^7hoeV215 z>w0tE)Kl{Q3hUPPX~n|hpbz=Yc$99;-2FJ%aSW@11>EU6&0+c^5;^H!x8OmN5n~hu zx(_RKHonRUgl8eQ ziw1$iZlP|7sZeLrS9t+x&upjsqOn9{z%sl`BSTh$`iFAIAmP#g-Ts^%@B_g2x-0}( z^}pV{N4*YiP+PZ7R2kqnIkPZ{$L1e7nSh6RL!h`fobp)TyHjFjechsh#2@j?FV&Zxc9BGAYNSiWS8a1N)mb&8%C({rNX*G58VxK_RCu zK&Rz3uZh^It#Rj96isWm-V6&O3*XmTw^|Eh3JYqO?KOh+g;Q&HKThCpDSBIvVWW7r zipOsw^3h{efrrT^*veag{3+AfFf(T&1z=2Q;XHtEgR1BwOozV(BfGEYFIWmSvnb*Z z@Jz4@F9bdb0%QST`{pU)oTaVFmE<7-U|iFSAmqK2+0N%!B0^;Ms(0ZnbMbCCN_r~b3TTkSWxU|sGG@D({BXoVFd%G~+pPW# zw2L?-h`ClwFyYf`qdOO@%-(IAL2Eq9)ps~Z+9{R%X}_KUvr9G4@cU`Nf-cUQ2W(+8 zD_wN{ClAmqY{hOT`bYwF>qHk%FBEHQ5ZL8M_m*ZmpKTStY>d z1^K$D0kYkXMXLHpb1<)astgf9GJ+YORw>=Nm>?eEJq1j)){^o5J3-eJin4;jAe+GN zh4B$L!0b~jLRU&<%`73bj6Mfw@c1IjQa30Abe4z-Z=PIR69NAyI4c2TGnAGV&$Vvd zk9NeoPGhzYQtk3@W9@!CLb1?gIvc~jMa!J6$$73@qpUj{{)*nGu7GCQ7rl4lG-#HE zvQ2Ffpk-L7uAG&F5u!`pe&I35#$}X#Ap*3Fd#Lg3ZqO_r%E~V4W!jo-d4zGI{CYE8 z9{9jTZr$oBvwlVeLzR!T>y<&i`*A3`@cBl-jCi={&PqAK45CiDhYPZMtd|dATLO06 zg~rD}<+oF8M8B-u&$9{4=HHC`j>n3cC)g4$24)Zsi!O$L3xQz*8oKO)1PDB6uhla^ zn+QWTuF4l#w~m0{MBU~w+b6*$)Di66k14YA3Aq^<)}^|}oIy-gcpkof{#`hLi}(13 zM6X^)eCF}ffOFQ2m^mr&z;H*m@;`nLr}(%ez+VaUToT~Vj|2be0T<9qs4X;f=qhBP zvahhrA`_aLeTHLYwFKhj6!Pk(1fY+VGN5_2OU4&JD{Jw}klLsB+2QBsCb<#sezGm- zF=r<2m&*|my;8GRF*`bJm9UbA4nYk7j^an2_(rk9dlP62zRJEAPePci# z%GF@cfh;FkW_(K+gNF|NS=x$hg32tWEBBxyC016$H9YB5aove0C3gy<#8yDz_Y$~W)vOJ(@AD3Qu>&D&is0Z8?JBx=&$}9|GmH4R8%Ic74 zzIdakZpvUmnrIBrE~aqGg?*GQUX#-&3hT1`{Qh9I2sY)k1?^?F@$Y9vL_n-7yiu}_ z=r_z){L`R_ZEaDw{wK>p6!DWDE56^U9`F$OXSf6o=-42QieF$WtM8ECRafD$b zPUZhpYh{I@#azYqx+yx@QeJqKv-4TyCgByh#j7IgF;WrZ=Z9slQ7`as3wo{5Y!-s+GE)z@`3n$64W>KqE&86EH&}A zgTD?`4~XW!fH)#6s~M@GP`IFO%6*m>beUgQcY`$!ddzESnaZ+(ri0_M?^!dUTl|g= zV_Fh)1}wwL=ZJVet|UI`0b39;>s|jUE&-gK;+vRx3oWzoT(mTJHAo2wrn?Ve*G-wW zK;dEmI^Y{WwnrMP#Y^RrI_C=0&#%g}GEkD-7Q}R095O5^BH}D1ZSkSRj_5OPmBGGA z48{-cKLj(9hG+-4SuDPrSXM^TO{o6fim7cy$@B_91jOAr?R9*irO=G{#0E#K=|3nG9 zm!{pF&YL$u7Rw(x)F!zX8Wd4x@$<^*YF8I4tBQyMo$G|UDP3Xhg?%iXoz0h27WfZu z@zMq(+NC4>{H`oWUc(&K78Dims5D}1M8w_s&uc0^YMY$)wBmcjTbV0r1mF+;S| z0?w4G#+H?lbQ7uv1YRgRIHs;{@&f+aOQW53RxI4C-!ZDCC45n8)pm=y271U}`lA+Y zZGVO?D?2+n{4$x3j*gov24<_Lf+>S~QSRjR5$>{VD?wgJ2j;IMxY9`A;6tjB? z++EC^?2D;od~j$B>Ama+zLnKJ(fEY{pq-rHRxVfq4i`lWLl>E#EnZ;-w}b9A`1!>Z zCob8$4-`*gM?ENuh;S9g$H%ifqQ!97suS4^#(DIwsDXt~Qphlmhe;-`j#8@C55;!0 zZRil~O>sIc9RV)k5e;|0Y<2K9iy6jQQ!V2C-O zNXWJV%`{wLpZy~!s7SsrYmuvU1xx2C+tIe57^z0_3>0vV3{`AVbVLUz zZYy4@8H_r`2L)U6B!vtES2fE0ig`I@WvKFu!Zde6HCs7F`CE2NOR*wEJrJCMU!+)_ z^%N9N#&Zlx1aw zp1ZQDuexrE{n}9WOZVkZITCKR$rJ`1? zh*sQ!B!PrbH&jq8xFarrhAb=zVaZ0;naNJr7lF7U3MlU42DmHY60JyWYb`G5Rp?^1 zYPGGJ|ICaS^y}^I|9}7YJ-zoM&y$?Y%)6cQ&b;rLvtC`k6`rsUw$&|P8yy2^>iON^QhlVXvH9dpbnij|QI99LuGvx|G9 zC|2IxF&EcG4^NXl?Qwoa^7mkrPU1tL+(smTPZ7CRCs}qC#uxPN{ad;yBj}KAV2Ev9 z>WJ&^?w3n0SC^8j8^e{i?7+->a^s_x5G*bW%HMX-KeN|fW5nX*Bx5r>dc|M^WW9fe6)%#D*I{Lsl*Le6>A^b z?HxDi4YwXFyh4tzu~EJ5Qd-Zuyt|(lE=nu@`u_c->}Tnfr({nrSP$fE+2(MN_!0QG z5k)xh57i7nPh!1$uTvaR*kMKOCe2SS(Yd=%O87D1Yg5G-W%LwTR2H4yb@@8%-J+UL zpDyl~wr=h8=|6?_%WPS15B@ZAirH;lXy|Ow+2jSejS+*<`Ahal`TXm;v7rk^_eJEf zTs~c6S=Jzt;XBbv;HTHJ=U2{dnN&eHs zBrB~lfHy7q5!~85UQ5#4^l7KoM^`0INdw0mK~2K=RN&}{aW*zKB)23zGTgs^CZ&^N z2ducks+;P8u;P;V5%{-?LU80sc6KN>JS8frckd3Sh84cawt>H;zntv~EPO>)MBts2 ziZQEAo#xC{(CKWnI;x+l=F=7CACi9qevX+xOgl?q502h&8S5(x4Q;MCjhv7+M%2_S zk~Pct{A0(zPChNaFXA8DBd(FjL!%+Nq7kyP-)6yS+JW z=V#H1g;$2;E}8K{(bzaVZ&i>wg?Be5D{L-bdjI~O)XPhMN|rrMHRQ&e0&h$bPl5=q zMKL%P$W66K^>dmDHr1w-_sU9u<$Ec^_Fh)IyZ4Y49()2!{ddwuRh=}`r(aIFzp;nb z9(+%6#&%8{8XBU!xUxIa7{OC?Sr?<_^RFp1s|vOEMFW(pi}pk6j?1!+dwXjvmY%YH z)koDf+c%QICq%%s{~%d&tN>WL!_wrc9>CJ^Wj8Cuz_f?S2Jb~RcKaQv^&nld@Jer4 z-a+tzYb=zN?f)E@^$7WnJ&o%7_gAF+R`Hoe_H>V8@;U~Dlpx1XVCNL^I45}_axG;g zJmE7`j-=^`tqf1tHT=4B2m5uyv&|H!YX*srUOIb@?%&ePvX zc)Yr%W;m83j$1SB)IZcLX~k;0eOXGP^rNDMSIp9r5+tv&@u}oq^08dr-7wjmM4y%S z@B1jsiJ8Ea6{(w(9~3$qB%TBrUW=G;zZ&vZPgmX(OJH3OQkTRurP&5PR7UX!Wx$)8 zWQ1sBdc~LviC!rVaEk55pMlaXHJ_@(r1Dbp^y&0jrx!t{vfyZXVBj(&H1vh*xQL75 z#)y!?*P@Py`1~!yY7dk1h> zu|j#D{DoIOl}?LaYG`a+AivGOjq&bYQvA;CrN4haN|nx+8f8zofX&QG^9XtRxIh2(@#I(Y5h{0?V3LP7q11wO5RI7_;B~k zii%S?G54PM(JGIw8hkB~T2r(ARMnp3dDBjHuc7Xo5M{UDDu28;U%K#0C$r!F5xT}k zg>mt|1v=i{c>Va@EZzP4gE4f=7ntnn805~HL6CWg#MAjqklVbI4w1$Sx@F?tz1e9I z@jYX01CQmPk>^&pyALf)U*r=}F=mhHofQ|N=ycy`h2r<ArGFf~TpwAi!0spa!~Xa8pY=JWd^d+N~i_fE;>Cd20m+b=i}Wm?Fh z=U-VYbeqpouPeQBI&AF`<*?gqKUB_>epo-OEBJ(?;nC&L{GX=)F8?Dx52n2SL!Lyz**EyV#Q(z!;TuWs-sR{b%?w~C zNQj?rqc10mkTRoI*%47>S`1Gt!T-H#e%vvo8tKSGP3V;UFPVu9fG1t(V1h+`W4}qT#njYj(i_pvadvw`dJFmi1Q;Xh6U{qc8u3G1 z1lzm!7RVoZ2^hr}dK$V0#4qGAQM~QT7k+{y)CcB4n#myTCm~U=l>knK10JNHcklY* zk*4RH==1^W_ZcfU$>q=1KQLa|1RTnm0AumSnwn{A#u()r?e=fh1RF~?IviwqqRBQd z75Fi3+uOVMV@a-pxq;9BL?%)iN-dUPS(wtcsix+J^oZhRF__8`*NOUVm~#A>9Qq(d zrD1QT!$Cr#Tr2a66N4nkNi2>38SKc1^xyNo27-{sMB&#=9$u0h(k1P8yACMn^jn(Q zl^s;DkEl zY|wU8Ho#p>9uuXVo3V3Djhej84VYXWqHQis$0{nS5Z0y+5d13*8C}{RHZk(Lb4=v4 zIS17r80k0ty2~tn4o>%_Vy!lhQ7ktUDCwXv;Z- zQhn93DRqMMQmrJjC}VlDxc*qq4_OPvu8m#N*QL#nRW+yTUKqd*{qdJt266?D>W8Yd zk&zgZKmy^oZn{j6T22}1%3e^O^_q)2ngJk4tD zC#ZD?MJq=(p_J+d@MmyGFVzm$YSGUTas4yRF?2iP+PF@875N0MYPKQ{Z9N+P_-CX+ zyB@UF0ca;A4Q(TACx`z-y2(@ApNUJN@m`wrq7VG6Tm4}#(EUF?$`frd+56XxZN0fUxFMK7~1>Pphy-#9&U zv@xLY!KU?@?;Dy+s5ZaslbCf!H8MHRT}P=t7VV$+h3-=A(4~HPQW*Z|&_208X@WIe_u0?}2kxuS5?lqY7EeD&1uwj2sSl_`FT^mdl6k24^os3P*9ceZrDbwU zRm{M&{o!;|xMYBFLUaIVhQCR+@RtLUwuGdp?{oo$GZLg6p}x6xKw+~^6`?gEOGrR%k@P*8Lo}@EYFvIZ>?(1G7aNQG>1PvhINdYV1zJ1 z=y!1*CIZRV!tQ^H<6A=Ee_F0}IesnVC26vE&QGV$g$MMv&mMZ_tA)+(AI#oWcXv*j z=g~RMXYYr4`ZX`Sbe*$=Mb~Rm=v#FJ}T3;gX<; z%Cjru!J=l~qB`cnlCAfYseT9ZGc&r9X)3mRaO;qjej}ZM;a~} z6*GZ-0%RW}5vUSHOVRO{*WJ>k5IB#Bd z(xuu=?h|g8Bys(qs4sa>6I~mBX8T9?Nvdj|$h*gGN(_H&=GQMhp5SnFjFB&$nna+{ zmJh%Y`Ik4IQ}{{Vd5v4=o|wBTpnp|q`N^P_&F-5^uG9u)!_;m3xNcN|r=NOrUhVCY z`hdM#`=4C9RWfh+uD(aV-{Tr_V2}Lp!vj?@jg|gK#vKTk?Avqdc-|HVa-}T!gebct z?L~>~L`Q2t;j@w_$5UXHmu$X&A|~6qBYoHOk7qyO=I zK>vB+0}a{r&F+m$Rl2+{(mb(cLHf@xd-|afU5#fh)Cb&->}^_ePBO0>d$l>~v}?o^ z_E>Y-iK-Yrd#agrJY3Rs`9@>TX~;ppwk{gV0d=KwQ(Z5vvJ$8s^-!aGmM|4oRr<%mlh$y_bkpFtPrcc7T44L_oj!P8md?}fH!Onlqpm)HuR`KSn-^|qd86%C>9Rg&)(jWt+ z`j#v*w$KPjBo)U>FmZi}Xc^yI43=Ip zZwcB7t1`Mq+(Vvgn+#Pk(~zl1gf3jN7VVjy0YbsuM;2 z)$F81kak)WP`Fycu?`S5m-xue>2eaRJ1V3*^q)v6)uR;$3>?*^S`ISCB!NBUDE1HI zK(lM(Mr4rg224hxY8QG$9sbynqC!Q0hL_@f%d6Ian z+5J+^kmTQtX`WLv(#1P~B|4~g6*Oq;1Gt(yONSUF^Jd5bXH5leAtZU_)J>UHF*V|{ z=>xK0P1X($L*SMgnej_ZxV9wCGq($$3S8*0yz$YUt<5Fhre(yR(OGwFMZ3h7YADqs z6{{9Tn=aL^mbg#vlp(G^k~DSFg>2Wx%d)w%`lVJipGB-uTafU_qx4;59$;6%)O;;} zO1${SJNbSm8ortf);vu>|8ngk)kpGX_e3OHriXhfRohSFqxAIKr^?T}1)SZND&>|y zK%+^us~YA$LaBUy+yVC#Puca@Cx&oI7nQamL+3zZ)M=VMijuSonkUjts(`|N+9)YV zMN1S&cg0Dqb;o)2zO+b;QvHM4f9rBUBTDn)5XCI6f2>+`VuZ=H(N*=!!O=$G>{K-y z&H`t5Ry8i;5iq8^H4m(vw9b3#?d@WwQLBsA2lPLK)ti3==4}N!CUuoI%~N5RV~o|q z7HQgzWEkrM3R5MRpHVU|HvL`0Vv}pcvaEyV?WU@j(b+z!Qe(JeaQdf)pMZI@8IM@^ z8cNcJAuFgB zGDccl8#`uRH9s;}HM?0~>cTDIkEf&_Fep=DIxIK!`xJN` z|AUvpk&1coJQpJQXCS!oid^_S9-Yhg_NDOIbS{PJMfIUjc{B#30rc|mrF!w`US4>_ zAgaqWA&;HxCW%Q-U`txFelj>TmJf|eqxdi&Cl{ATV^X}l87vBy$z`(WTsGZ{?bRMb z&BYeFdV1k`J%KFvFC+oQe|_9MSuB8%3e?NT#h1Z?(rq$-(%cwcQFI>` z4f48s!zxYl;_xUuUtd_h;WVEc#ir7E?Doiy|BXlPZ&kHb|2O%a6|duex7qpIcmd%h z@S)>gKmh)WVF3OfjoZuK>_n<~ihJVlYc19nhdmJX036On{fCg(!S)}Huc?|qp6@_X z6F2(caBhqqOoSXrR~~0F4ilnk!&YmLpk0=gh9A?GqXU-m`S|&jO9BN;L6yc@Ix;Z< zJ%MB_>nvS_UPOu`?kPs0_YiDF9f+hH$Yk!!TpW%_>AL1!^mk++T2b*5j=!|T?ZwZ} z&KP?HhyAjRm2jzmt#AtL1It}UQF&dqTQjil~qtRg04G9!b&;XQ%be8&{{-`fO+z?r@l(V|ziUDs?ta?l#>+0qcRt z*7s|KiGB^Fu0w{S=|JjSWC*H&VYwp#XoIq&M~PO7mP?w(25GZULBa{Ie%c~*szC2| zPh&&xb2*c*X$sMeK=Lr{EKI!2$ey8%z`6s8M{C2drAzNAUTRKa#$_{esp<#D^N}sZ zAAGmRQ>r@l0QL)?VC7K&)qam)sQ_axy22jM45Qf@TA=u}Qq1Pc0 zObCX)25~{Fwf;kA41Ei(Le+Ql=&YTJJhC*6U9B01l&7EYiqd!>^_iT>z8W7y zlv5kV)UMOI6dBn=w3xQ4=$>MPx|43aty~|XGU&yHF&U?oA%^erXXc(z<{0Ah&z4rH zmKc{839qqK%Z&6Qhl7N~5MO)6;I7(E;yREL2&jPHw-2&Ga1QtFjyy0)33njYOb23Q zJCL$02Y7BdkniB03lumIdyxa?+JUfkIbg0Gh~It(QVx38hgA-ka|d#`#sOaA&?X$? z_Zbd}5xiC?y1g-iZm75N6pY|EM0SC;Fk zDq;E%g~d{>+Lv9YjLEpKuFjW%H)*!^Lg7)}sKSvbYO|TTt{H(1TQ$qtQrsK!Ud72g zTZ$pGSHui3diZtrs`eeH%#v#JhBlGiRNAA4kuBz1L2dAE~airou--S~ao zO!1=hK5Omv2C)ul$lF#QC@W8DPEG$(t=z`F2Cl*kB!B5Z5So&ZpvYSaR9rcAvlE$+ z59?bdmmwQ5_JBJw4ic#IAF?V%g9Kr4*K1cIkJTMLEXGmD8hO*$c~+zLC-Did_cFR@ z8FNrM9?dC+6`y`~hCha?iU0>k7wrN?u>o-Mt^ZG%pM5=wTnn z&gbi!#_kx^d*w#M2`}zQ^U5&eMxQ~Se7^Fiku&+k@Sz3cO|@Ychg26GO)U-Y{PD-O zIIEF;c<6lFoU}ke+=$wu-Wi>x%u()z?`GC%Z;bI+*^m*FadX_wTrkimrg{#`MXcY~ z4flMO8<{G+_K$JTb8VmzAt8amn~L3`#6(<`j@Nw{UEH&q48bB|vA$#FgRnq>|B(6W zUr-}>1Ae3_!{U-UdNip2p&y*oG&U2w0s|BEelOI=)@&DYCNI;>E-4e#hLNx|e5`gt z`7oi8U5NzjVa4DKRa@QuUn+x!kj*6E#~eY~fZ^gd#I9xbv+$^}6HsJ<)M{mzxIJ zH$~5?5?L3Yzs((c#3fUIu3<&Dlj+%~&c;N1UuVh*KHD$cZjZ|8dp0Iqc9zmXK^4=7J~4Us+4#Va|Jx2t|fzm?a9k+3xUvFbTGKgG!YR{aCo zlEUZfw3TRbN}%8_8io##b(V&k&LaCo_Y_5zEl9PXPTSe4LUMsDGV2iJJHgD{9;uf# z)8(TITP&+JOO!3e!KsMGQ**X7CUu?WM|IrZ8QtttQVMZtG9{XHDWs_+ zAc5}N3T510{$?jKY+rHD1k*F*8x-riz&r@`(6a|TvqYeiO#VYOW)&Kf>h69E`3||7 z*3lzE_MJ8?qiO7^gkjoWGER8?B-o|tl)2HTCRU|6oxz#BB6_iANXDJ{=av_%OVew^ zx-ZXBXQr2i-;3&?Nl7=dUnDHhZcOL%&uDfdOELlleN3fj*Q}9=FD>IRLk_S4mOzY? zU#Fd;x1lQ71iI@Y(eGEmHj71|Ygfa*Wtxl~StY!dZJLi+S2-LcBoJ(UK%JTma!${j zy_!D?EADyz?5gaKO~BQhiO4XfvImIjI;M8b^dBNPQ)r0Eb9evjVkx?@sH4ZID}EZy znx?TuR|l)4>rQwL{&t4aXZ=PWTjO+P=z7lNpc}s{d#ANBbfacyp(;McPm$t!b4CRUxJCF`;~OIe?blhd>ADSBV&CB6kuaPM#S ziE}c`^`Cz`LV{&lEL$7HC0Us<86k~{l8Vfh;_+7}N?fxlD&oGTNIZb6+4wb0;tuH_ zNJyakmcpFmbUv|;*L_!Xtm|{*Y~8Jj;-1s67PRmv)_1=;KofU`Jz%%uSJ|2t{~=|H z=0wdC*r%0ABEzp8Jv>xuQOs{mV`-{k2~&RqJe5eBs03GC)G&;6XpL-~Ba6OP0@Sj@Q9J(3RTMkday z9GrAc-&y+6c3M)U;hv(td~Q;Vu}-VsC`Ga`1;fOj zU@gVhv!9B8SDh`rl*y1B0taPD#xlvUl(@ZN=_!)+GU2sB=^DvG85lSckifgG0v(T( zbF-7okSkAs=gVYtmlOeix7zfN#2tVPVqzp&ff1k>ZDJK#8%8lTiKEd#!AX6bREc($ zdKsomWoWtnwy`P6jE*YQo1z8yMjK)ZkNFS`wn#{H`i8=r?A24fIcF_W-1A3emx7fF ztnWjGxac#f|BzF%hiiS}+}&fO2iHI6b@Wh3zAX;oG>z>bN!;*3l#!hxxl%lX(^-01 zGG%=?PS-V;lgE_@L0F*E$qNBG93&)qeN#b=2OPfH>3eOs%oS!f4^c>_z|4B08^mEK zd%y(jeDX)YAqMGwO^N|e`-i%tiHCp){`zsEg=rgos&p5H>+(30d+3G=57}zN=3txR zhHo&k=V1rpDoO$c8cZtqaYJY6eyqDNXkDGwT_+ZPSJ+Z~->69BE37+rnr0<;Od5OS zsYxwn3%{>>VbX{f35D0TnYhVk5+O++0f}M1p^(N6XK!{Ity(2N2X6p*>P5m?2xoGG zdU+fT?yqL8XB-Q-!d>bou@vC?9;vcp8Nl_CkZA2q#V{WD<7TIm`XWfsTHKRjOex4W zV||yHM7GQ6{zHCGm8_q&%H6#vO}&A$zN5#wbW6$VlBTgkGG3JQ-?Y)^WqN7Jdz)&* z9;SU%Y%QtNYSJ%nxKh$}&BDxMn{_3^YlWFxx1@mG3<-(ui8*jCd*mdH!{*DvfHAp= zosj&ZWe@mW*IB$x?LS1X-;=BdkElX@mY5F2pP;`YYp`zg*`@PWT*`$zQFl?fv#2&~ zq;9(=Z@rOyANy7_dPAU~4hzy8Ebc6QUl*iavbIhep<^qX3q}>z>PII}LeG}k4Cx}F zEN<^Sqh45$F!qSecwF!!Ou}m8F~N5*2_uc~#!mtNEfV76ZvXr@DDWrpzYw?J0RQ>3 z-2#>bOr1X`Y{5+bxdHRr#ee@w#`wP`{-d=O|It}4xJZx5aG_J(JQ<+Yq0&IIN2St< zUvJ_Umk&~@xWtc2_ac6k~jPL!k*CFw*-I)k{%bo2CafgBL{fj3dXo2cMTccXYi zuS6*Z6Ti&BWsg)srbuNHXMCw{6sil=mqr4pf*%o`u<%wGOmF-Y(+6*!=|jW&V)}TK z03QX@hY1BdslF~u23`S_P57@ban1KF>7}f<(;&7uts1El7lh>}qcapRHUpoc1IjxU>2=U)-Dv!;Hf&IjXLuY$a zDQq7;o#GY6_J%ZITsDR83ri}K!QwGkQSD2>ztyC-54N2GuP6YFA_Gt5N80?%N#u## zLK38sm>w=%20e;H<%6{j$CpZBfp(d~_Tn%oUS3Qplfi`)VNpz|NOcKIkh{Gy_Hm=R zaJftdpUY-aI1Et7`!d)J3WG=WqWDJ9SsX7W-`AVozFf55e(e zk(Q8!=p>B<5h+JO_=-?;cXl3P0_%@{d)V4>+T&Wr;epyvtwgJ6=%np}CTdq*pQqh| zZUZYAvK?r$)tm(H0uQ+{tXmE`2@Gb+mfM@_kim#e?Wq+hQRF)E47wNt7BO?N zetY_8&uP1&i_iS7Sp)IpS6x4%b;EAs1~x=}vT0`w5xjj|yg#hlHMtI>8$uF1lt++S zG}}m;AoSa_Pc{SXp2j#lSr&_Wq$?VNW#`cM!FY%)Pqf#nStNYBog)tM?SO>~T0)pU z?`A~HMasQC>3NeimsjU8PZW6Q`t7;sYbjWwXB-~H8oiRLTYTmt)`?sL()c*kceLS% z2y{0@eWC}aY94PMS0D`Q79T%BuS+arro^Y|UkJ}k^${ucHo-XVc(Fu>@N*RZZ!sCXk^2KLL1-lIL6h#9n&>9Yd zZ3Edp(Y;d@j(3j-J{{K0l(tISUk~$Porq3Vw}d>kY(V>|LK4=RlF(zCY$K^EXB@6i zuS8g=qQN&~F>((a&B%I0vu#z*c*EqqM5>(2m?fHp=nmPLsT7xxI6wz#@%%tu#)`U+48zj z^qZA1gLOjm;mUq{_-D#=5U%WT-Px_VQ4ptwY>()&jp`Ne4^JaFtQ)C%o$A`07ORU} zS;jQwa&@JaGgJNZKh_1AT0%H08?ie^mnGk=tik%4LlQz(cGjIp6Deu=T3wIKY-3LD z1id=zaao7-t0q-Ozdb{(H_Z|0jKilb%Pif}7oQno>1BDIzUuk{%MV6jj>AFrM~tV9 zsudp(`Am_j)-zLsqz}}=nsHo;J#B`7gG9zSqWxBtCccc2_h+$L661v#$GPb?Cq#sy8}B zy#N`<-G&;~Mpz^6qS@L!WOsHTHb7efodsgsl(`7w@C=NjRKtAE(`8G~!Go+aL?+BY z54B7*UJ+{GMk3oMI)AO&#Cz`r-(lSvl&)L5Yp1%7REf*IwZ?J3s;;eWgylo4Hsvja zQMhXy<_}PpC7)d!{1?HF}J3I07lqELSf+^SyScwneRINYVB~Cu$lWH>+u>2O&BQ z*)K7y*Q!l?ROEB;g;uX!fpUk5vvNzfd2VErmHM)cNZw;Et)+<(rNohgzC+F6IhY5y*a> zb3-l%__)YBHVK`AosZ=X`w|y3sp2Dn%Bm3onpad@g8)c2-CEi184(dUqof@~k} z|9}4TC;UIfEkMB&yHVWy zpsSacmoE)|UfbHgeU{XYwVs8NL|LAUm#jz-IU8xcAmXJ+mV+xI@ZYU;FZj_G~gUgI!`!ZgS=&$qfZ(9HV#ZLa$)PMB0 z>OYzb3pes-y`>MMyYYP?jXRIcrg*XWQ50Vuk4ItovcQFl?#1DA`D_Nm+pAq4_CNoe z{at&&H+h_OZ>kvc+W!CT{2%x=eEhAi1o%q$c1SS14Swf0UkUIXmf!g?yc{fofZr7K zT{Ioxk4!?}L-!yyRxhj*dJ~~7c!<3VXK3PjT_^Mdl$NK{zl-)ld1dGH9niigt@1k9 zOZP(^M;r7nk^U&}^ihBVAlh|_{slleh4 z!U$VmA@Iv@7r6yC+eDGq7U4MVukjq}K;#fI!r>qxQS!CrHg2WXTGBS~JyfqbsC7UM z2xSOVxQ#Tb7U*p(I?`X+GsiKxGqOd&$>RsU)Xr4g$+ZWMMINMF&!jC#Maom&hj8bE zvCmSf;_X5!wjkv!pC-P5u~V|5Y>La6AfI)itW< z>M$%`MPoZ|JV!UHvN^oFXV4oe_n4wzN1!V8d9chT@gOjM%QBm!A^BS5aFCEF*UDYL z4sGR5P-y;a^9Gj!+rWvR{kq9s;27!_G`Q?Y0e|G&z!zo93+&_E0({Dc71&s>&IeWo z-Y%;tum@XaepYt8fVN<0kXzZA0^5?x!0+MMv3y9-=(0-%eE#mhM`dRV?7~4o{fvT*&f)b{ObaqX>bsu?8gFo`u4ysJJ#tj{}5j?->mHPf%GvVhjGH}FQ;fda=3@;vXZ&rE*V;ezu& zka*A@T@L&P@P&ZX1ReyXoUd^>F+U;5l*kk4Vwv{t)%>5ZX%lP%8)K(qlY;mo567F) zlx6mD91%p&W7Fx^g>>Zi3FO&Q+JaMp?FhESwnQPi zj=mG_Sbj<1gPvN%=VyrmvFs&wAtYzO`15Jv3ej`SHPohfCU}W?gh1RT(KoudIXqKe zVY%*fkUjmSh^-$LOv`&$_>C?Z5^LNQZPw2Za;&Eax9C~Zcx8PO{0!d(*tfBTTElyj zX_ZqFstp&W7F87qCmI7jbsQBX3^W!^;hk0qrWyTjwT0>aEexvuNMOPP# zel~XTb=){4oM!U$hO{*Cy-kUX^FPc@uo?vuXpdS%rG}1Ei++uaPtcbIf`PhVhHlv` zf_%=`P@H@t2=c|nCrK!o^uGQ=nS6?YKXR$yU5z%$K5nMKtjSEE)0Yb<>O_f+l`UYZ zzLYyAFBGg&-%aKR2EuhD!5%CT98x`y(iUXF^%}WtNdTOWOmHl>3j9=SBwfCaUyuns~B6uNjnJQ&)UK( zeaCvTKCg_dZ{J4Nr&W^mi>k=_j-zCK-f42Yb~`FKp!!rsYasC{x;kG_tiF)!xItd$ z-Q@}fs`p9G|3KnHdjy^UnkEPpM&d(|t?g?zPB{F_?@YcP|H*4IUTh>$l(~0P$&*-m z-%b0YOSi_@2F^Tqv20w7W9ZT9t>trL_#>|$K2+`zYaiF`sJ?tvES;WnD69N-jEzNJ zcTE25u&w;VSbpFKM@{AHV(r0)YBrY_$Iup>tKL_>IL5Z*qocXyn`0fz8>)|&vtuBX z=+V{XyJGFa8`Yne3u9>F@kh(ct7C17@2ansOJcy==4fU4saT%rm+IznZHzsA#?fQt z_E=gTS>I+O>pRwy^?7Avefu`DKCP0hUsOfbcN`__!wy8?W4CXp$tzEfp*4{B6kT0* zxTt(ktm6iGop*P|p|$0o$DIFx#E16i+L5&K>{y3`#0N&hDWr*}^j=V!wS`xBM=~00>xZ%${e6t}3efHcw(qIPEwZ zp1s>}yM}jqYW_&$s9K0=n%Bvgcaqj{Fk5DLQC)O(YHlaP>>9_7jQo22up_*?2z9to#s-SjWs?$M?J{mnEY}6Of4^iANV*g3F(_- z5BARY*3hi91>X7T>ZumnlBaom#4E?Kd}6*@y&U%BU-OnB3v=wkY55|x&O#Hv%nL!( zIX1;~INk!@UGG3n`1LtF(~SHT>I)Wo`aAg(k&`*JJhHycM%H(%C+qXd$olqeWPMsC zS-+@?tnWBV*5{oj$7{Eb%@5Rkol0vU@hQ6MmOodk%W&KvuLJ$^w`i1U=YJsap*?z@ z_ZdP{(V{qc(kE18f)7(8qWhSR5Ziqe+^{;*rqVI{nU^6~I_d)-SPsj@a#k^tD!;VNBCM&Yp)7oxf!XO%C=T z`f)-K;5Y;G7BfMFvlR15W)PvU@H+pdADsWkCth!P|8@Sq1=8fi&qPu5<)AjEV;AD zl?+DykXna>ghUa7C3lu!tLJ2!xef;ji4p`W?ksV-@Hh6^5b!!6AlZ#m!tX3MR&yW8 z0VypBiE?kSuFjGp7A-Wnf*%73iIPs%%~?!1bIJOU=?(`8iDFI`#aV0vpQBo_+2J5z zoAHtICuhNbxg|S`ZkK$tFc(NmLgxtIZju`EdwjQQhl7O9A--L1Zww2CS#*$)DA&4# zI6M6?HCgm4Jp3di3cX%3oTa8KZXbCHB1aMurJPHIv)Dex2lIM?10o4qdwhFb@7XE? zzs`3!Ncg(Tw~KMChfd!Hfiwx7eZE~TM14n`2&ocCh?o0M{rrjiFYuN9KVkUqHp+kY zZ2!^i=Kp^i^M9|l@;_hD_JcGQtn59#nJ!>b@9D*Gflwb*9~TCG07mt0o-`L<_)lZF zP`%&^6I=n{99Zz zXkgusH%`MX^J!q*@9FJAgZeZ$h1aKpc`05W#suyKo<6=V;9ubB1xNT*stb4+;I{oP zbkfEjoEPvBf~Mb>@HX%Q44^922QQ5qG=MLFrx%PEtp6#VfCme26pmR$JJ7BVUWHBt zh@SfSFkvLb zoG=+skqISX6rOar0bm@&OoGiWAPpy&coTFNCJP1#6VC)HdO|=yuyTR(5E}-ZJv_bO zrU7>kVosR2bQz-TKr_BR&Y&^v^5g&1&7mEy|9A@g>Ha@$et`eN0Jm2Rz}z`==PqgI z0RCl=vHvv<;D6-+@a3A|0lb|&0Id12ZM@nO$ORn^@ZA*OSl(a)XmCh)0m6>q4cjZe zVUR2UzN5pQ3ELKM1o#F6`v<`UP+`L-m;mV5$pipr08PL)1zdn9mGu{xfLAO4Z0-aL z0DH5O1%N&36$=1cJ+J^^bWSn=fCIiA;zK1l04n_z2jKIH17P?P8~~sJtiT&g0F-FU z1i&`@CJzAnFTn!aem7`G@c$YG{$&3TSuk@>fSZq7;G8)N|IW_;Tt1cR#Q_&1CJh3O zdwX%g`iDhjP^jKf-ceq31|teyrn+_|t%$(-=P#Cj)B5KxR{CGt`x$ND{;9Adg8dJ? z=X&AWw+{sT#5a8(;*FjK5Z{bpTfrj)z`HHD*TZ`py!PYqKRruC<=$gVbZ||AHoQDx7!W<`>z%_i-!CEjTgr!2wr*rFTnr0 z1&LzV;1JC4^`dcoeCQBV7o2{*m`sQP&i92x06r`hn??ia1_+eeMF9W)cS1V`{}(BM zEA?43^dzZq&M_BkDAQ!8KocojOxrCvQH1ViW<%Q%zJ}H zXr?*T{Qj`BNVYjaJ!>>y%QJW22`CRWGtI#f>C}47WHW2ocAp{Ih33gCF0zhm<>q$; z)W8Lp*c@Fje^xU#-#n>UJljn-&HVGm{P}mW1?G>6_br-(Nz9)Ve7RJEW|#vl12~7Z zGV_@@R`kcvj{l0Kg0D3G=Avb)1cxTr%!*ti# zwE4)Qxb14qaP!*GwB3aoikTa>ZQmD~Ddy;9_YY@lMdr_==bgKTU2k$qD2Czo)wIFY@C5Sq%SQGxrBy2OgirWKnorNZUjNZSrD5 z+Lb6Oi%sYFu&I!_rJZg6gXiG?>Zw0|{^$EI2zCo}YxnH;Q#N*A_4${g8Dxx z0H7zR8QR1D{ZFh>IRB4-XZEfq{r_A4;LbkyJ3#9T0?E7MM>vO{>_T$=zg>Eh`-e-f zcz3)c9zizQWtic2BAO-FR+N58JUsd(t&zEjm*-B1ngA zPYux(F6dw=%3g7EE6`mrmuSZs*t(gy zU!ihqsIF#Bxt@{bt@AAZ-mo!qv2Mfe)dtkMU-#i5KYg?DfzEt75~J(ub-!IA>pL7I z#7ErzX{W&dQ40LY{vSf-|7s8a`)?Ec$CE+s{{LBE=L!k z5BpKka#V`i1~Spds1cGGOh89qNob4P1atuAj^>W=MZZKV5Wi8r=oe@xh!cI#ohSz> z&h$aIKpdf*O`d2FT8!-bk&2>_i!1V>7b-!of!#UTZyV2FJQ7Xo5Q}?K^(N#u=-$V~ zMSAPNG1)9bQXo;pI@1PZzOKb>mHs+11Ir!pCQ8smt6Kst0>ur~Tbm5(Q>fJ=BCb`aL_mz8+)gBNbq90R}T&eC$@5W^)n)5V%?%z_+I+14hb4$)N?VciTr15V^% zh(Wv-jp820&N1vV?@_D(w7RBFQYe9{Us+6wWT5IpBVRccs5w5Rr=}jUj#q#;q62C0 z&yxP4HfYrWUr5-hz3LG`b0r_8bW}*@c9QH!5GD;?=puP8V2f(Q4oK9o{=)cW6zTKm zTXBb@(xngh_Sndz;ZjyKQ?N5dBIzAVjR&7*u`Iq@V!8el$#syh+dE;+}WZcaTynsE<-}4@t#r0}Iqm$X=Mfzv6+6huhiwE}462ZkOj1 zL-H?Zp1F*i`7$p@UEFiw{LcATl|g;FE}fk}B;{eh$cU``mtxz%=m<^zV^NEnZOOZN zsodNVUxxjXleA*rkEvn&Tmd`sp=m+q+<0#1C*OXyI|q&1@k}wTdv0~oqUVjDewz0X zyb8$vh`DsSi{LjE`Ph&FnC5mF5id3LTmH=DLr$f|b5U{6-ivN)NAxRbAPi%2x&EBwc!>73;cFx{fd5g90M-oOpl*~=s zzHdV3CwoJqHoP}|$Ftp$%kzJoz36#t+~U-lWex|~FEK2qdkQ`*{JZ&GGEkGlK|-R~ z+a9CjLqWQy*zNWD)5sOI@Y*+~MMwwDzwjW%UllgLO9n=AAS9e)o^X6Ms$5^3*|YMe z_|b=E=0;T%^ZJ|}e%5o#4(^t-cDrcv0B+qm$)zskE}U=egTEMDCW!LAcdr=SYQTgM5O*mcp6XPZWC^--kmg@s^&ZhoaKF4e=Xv&?p z|Cx1{t?OFu$>PksRkh{2F3!yLuodj7`6_2qkVbdv#kJvQXRF>jF}TrgPuFfg*4QYy z)Z45(Tzq}-7yR5y2Uj+p`s(!B5BEI0D!k@bmbCeHL%$neZyvKocm8lw@6zA05{~ii zCa>vXzPhK!cT;n}(xq?O_aixu$cHmc2Hk+X&QFSsYjq#y?s!&hlIiO5{#n2$=UkHk zoBl9x6J7|<3bXm$P(!{v8GYvRK))>|0+#h(b;nghVBz@~+k})t>awk(B3_G4nB0)ahi89KE9b~gnED2OeAW1rVW5l}iB~Hj^W&cUS$cjeIyzJuW{4S@e9e+L9xI zg_rD)7>(0C2_Fs~3kX(Utz0$&o7;to#LK9#3LQnhSF*5&{T87u+9|Lab;I_f!I=M$ za_p&Afwj2(jBS)(hSg~;W=(Fw{6<~Dz7%!UIgn^2jSLTd{JK`z!L)HSgBQButSp#89x{a0? z++l7$#KvGfVQvagp6*k)BQWKa0wEA#TT0&AqnHD^nsR^5ejvh7Rd)V7EN9bQ#m6=? zw(rMzs#|$MKoo!VjXWB*mHeQXokkL zxa~BTOM|0xN4S~~#s4G5Z&ay%D(_)j)7Z({!^;;7t>agvbYJ>XNT(Z&D|nqm*BE6v zza->AR?Xy`C($oNV<+u2jAnl>YVen(e7y96P#q9$Oy5v#9um>;8`7Qc{ zNHRCyz+$f!4PLk;g}!u^ur@5%i1Aj%$1h9C>6$oP&~N#)oQ^T6qQg;Q!)I*1h|iyp z60|f{*fWl5%;B-)_s1{KaZUUoE;7j@r*}-KsIzp5VHw*?v@_+Sl;un5!Z7s!<3`>% znD$vYZizb~EnIPS=hz;iIp$^hIh>)QXVy~Lgoq+xapqwCzF0>5%-mU7JH_MT9<21t z@)zuZpdxJDQ118f{%Z%xD#IrTTZ%{P3!@JTb2jm^a*}SxvNn5U?Tw9#Z`wLb$Kn*k zTX(*chKD~9&?>v>E2GWAZ}v~iVkU2o)m3-NS`o*LAA5wN`<5e#Z#e$7v~$Ei1ghHG zx|o<~;qbGAvhF0SV(s>CGy4eMisb)!T}reR{ihta@d&3`vkv_NVYh5`Aq-W)A3{HCp=3>2+HOL1TP9& z;5?82+c5(pg=lY{e&sdm=Ksz+`1f!1`IGm5r~H3bfdA|Te@|WDUlhr|)&>5>@_%o+ zKb6|n`VUOd!6YB-`oQKMEc#yk`Vjx|EZ}YI`oRCq3sSeRs5Cl-AI0>h_;A6xkH_NC zC|oa|H{X}RWO{=?-{0yMZ6D14AO*mM0(=C$A^ty}_h&j=#NoO5x}nBKlDKAXwma@vjl{s*U}z3Fxe{I5^|XLQI<_LSFuUY!J91||5J zw(-C5Lil_BJMWaEi!?KImpw>0sE&jV2MO^KuS9V8U$P1*#k%Z4!a;Q;d{fpF!xKyJ z8vieq(Hlx~18dJxDmHj)V>e3Gs6AZ$$OL&)_0D`1Slc%>a0jFf{bm*s?UU zZr_R>V?ENIV7{4Yl+9+I#%6m-*=P>b45}l`IUFR!TOh+a5pnMDLp*xTJMSpW9g5xJ zFME(sCjZhnyI3O}R7V~=93;d`#yiC?_#sI;mH7n9v0nBd;h;JaLeLcAJW;K!IL2A= zOXLT|ZRnSTgX&1=aFCFw*h&a83#|{SxiTZ5e35j~GQAltJHQ6agOI7`#x$Hqg-^>H(&q{agpgQsxVt^3WiOR3V z49?ok+AZlnsV;kv@GD)R=BMnNszG(+vBN<^qP9~kfLA9+yiWbuE=uNQ4-yWlBca1V zLZaI1*Li2Bx#&a5x6mmGeKW^sr%RR4DS7O0knr`8$?3wYlhd_cdo;5NyX-+inVhBW zymF$>H*&cV+`Cht`Pxk)&vj)V>e3GsUEpLPngQ{X>= z0)OfKf1&^E&)lZZ37g$61Nhs7|M6)n0s!Z5@U(|mGvGWAu6+>v1_S{R`3C>RAcz(H9iDt1nJl19;QzlU&HE3N{}ti4K`$hfE^dI1_g`hR74J+ThoT zULfj~ms12ucky!LaycAdFFp^virJ7*7Sc#kIBam*X7lJikWiKbex*?myNvJ6^YQiJ zLNO>#;qy3LNF*5rt_*Y@2jXi{A>RYYJl%YwxUf|D@F^@NmrIG_acLAP+nYfFClQvn z7x*xPyK?)A^-ouf_P!=&cFYZy6qR*De(8E050mo-~8!xF{!%5|Gbj_O~K9n@c(U1{#vCKiXM-( zxn=vcX9Z%7W=E>_kZG1?_D-{Q#|DW;4#y4$c^yapl^hVSid#-7di+zl-=7vCOzHj~ z_TB?5ilo~gW_Q(fQP;&avWl#ts4GZLA_$1Ws(=CpP?R*pAq-5O?&|JIOdw|jLBIg0 zU;sr>j3`0Hz_N-tC)72pilM*iX%%+w8}9c!_xZl}fBz1T{${$ns?Mo!>eT5vr~c&= znyqov6O9pdk0uxN5EwjOS}gO9)+eSNk=DjjvkAAgviYJ+%I zR+m>y))?t!WlPjoxsFngBxgQ9*G2lf+%oH7j)OELsibjB=0IsVJmW7`eO){>Q9eCxZbi%h>uP7oE_k@HPKFOHM`48 z#12(GbM^|T;ie{edA3AVh<1t(`aAQBh^4?Mq)}!oKQB5TVVN~kv07vwvwcIMyaI7c zFsvR-Y{E|POBy-+HPV+rx3wwOP^A=ScKsZNr?nG9Zzlxa%XC1b&lU&7WoxrZvSrnQ z?BHk(#eG}Q`pC^*TPJN<`Aqj-m`ZYHS=2N|+<9V+_btnpoZqnwj{p||ZxG-v=<=G* z8IMk#>lHAU#Y5d@w?y5J$pZstJM)v7<(PPGqije_5MJbNnKe3&#Pz(lZ>UaS5zAK^ zR&V1u66}>FjS9pZ_wviFdO^!mc$LJ% zS3_=}ZImK+gr;`sEoskm%T#Z{3?gcpNqPdfAh~7vGy_8gvH+L#WNXAm*5#EhE)~v^ zdIgN(>j>USTB4XNH$Er&IE@>|y5%&+h&yA+xI@r+!CN^w8VG(r;AZOzE>@ zYF7EZ1QVjCeD{eO=BkwA%59@%hFJG2VivpE+u$| z;OF`-uOCD|C7#^l6~N~Xi=ABC5~bu=Gc$KN^XCb-#h%{XDBCZ-%YMEGcCqA4G`?Z` zhSyn<^2Ys!)m83^U}ub8hTri|6bj`l_bZe zK9IDp*U;I|_Ds=9ZL#7Hqqo96(kmU~OF8R(+%jyUfn)+Qi$2d2oi_ ze#5z0$vDq0MXFX-=^moNyac@bU>Bf{F74DZsO{=dMf-g+E zW!bs)7n!*tz$LzlsT!>8^2*w@H_b`q6)>PAID4@tp-?L?3%R9t|;6hvLkiqZBO}?h=nQA zXO~mWnR}qXJCbM5`ZY;I#(bXOb_?6eifpXshyo3L{vtg?z#6Y4LDZdr;~9Bk|m z1-R_@F>kmIy1dRVX{=o;@d}viWwPBNsU@o2>(0gur89quZ)8!rs!=v|)%5IowPjXM z@UfJDlChN9V#Dta0m6=^rqAyoDB@MkD6LDqx zGMV(*AB-#8(B@19ejU^-f=$Rc9d62tnl@>TYiv`(Ez9#O-?IOV3vh8>xdYi7+vT-= z)gkHD7_We|AXGh)*%B2W(U3Yd%9%edZcB1)M5F8}TO}jlOz^9~Q`i!@eZx}G1g06& zuv#Y63D_D7D~ES^X;PeSYc>0==b+fku0L3%D`!Rxy`2(k$($D^eKsN{Ej}d*zO7@n zM`D^6ZkpYee-|r_lEa70p;+maUfA~)9yRT-++k-mddqTzqIXL$65w(o>0EIR(&cqm z_AoC1^$Hj$>BzYRm4b8F+jW~!XZ}`XYQ+_#QMN!}SdSu>SvxpGn?0bCV4UF87K0g9 zZ-`yeJVRX4I6ZpTJ}0SeYkS1`ZBDYxF3(7~O?N8rO5YcQrz>HlFLb;~S9KwT$5BYX-yknhXmwQ!5KbXh>-27t`PWmTvHckN-<+fHLnH>|k@|A7z1;!Y0yTC)Y8BHz79hRf$&Wn*8>1T3j;Zdr?-Uvrt_j5JhDQr&@P+|k!bl15Dy zpWSx9r+#XUVUNS6$$h8LNII@dj8k2e`ns-2>{2&ND}Q~SFg&?iN_qw-)Tw63$TfQL zqr_?Q%1FC7Hhx9!!+sfijW{Xy$>zj&sxxF{@mkicWGh)^<;UlnKITi(sG;Dqn=k$L z>I3%8I{irBDKwI?4n!<*Hrz)VNH}9P;XZJq7Z3g>i@zoo$c98zR&K+c)qxQe`zGSI za(g4lrjO}J8Z{;Kvwc!^MYmEfw*fp8`%a;eZ2E{Kjhb|yOi-T>@}*OzEqIml8|(u# zl1(3xq*0Ty&(=s4NVoIm!bd9U6yGT{R{r{Pu{i1JWHMD=5-J-qnJgYvGFt94nQWr( z!v(A6c};eGw!l96c{2af0@&6xl1(3xq*0Tszip06NN@Y3-Bx|4d^}w(b~|tctCM}C zKLI_^nBtRN1LlGi$vzvS@f2XMAe;W}DQr~Mqo@M4)D4}8xZHONjg>Rxd$mL#N_{P# zR~K^*!-G3Hn*?!&_vctLx(X;Ee6$LrM@W!W*|MLxAA zTP+fgudmqGv3nOkVguPk(?gOpQk3@Jze&+gxtMEdMdZXuYVv)j&{#RcD`!03u#ucq zoI3$A-R)~xS+I(K@NlJ_Wii3|s)KZ_D1VlC_oA<>d1ZOxpEoLheYahg?RJm!T(py5 zM?duSZQiBFp59aGr@dQ0(d03?=C@rV63#yM30<{o49oNh8GC<+JTB-73I42Zh;4oB zBRyH&8;d`#R1U4dSPs$=2N|WsP}E7Ms)! zp^=<*0&5jyiAl$kXk`48BwyE9WM<45W#zAS!t0TlO49Rpt|-h-S?T8yvu5pxByx>g zamZ!wI5i0}Lo>Z) z$(73C5k)=&Q+={4nQxX`r;^3BtRVr7umn~kX?-UxzrjJO{s7cjlQ|n76 z1HVShhNF;1vgsp|G-{H4wvF|rIw^l9Qp5VDk!<>iB#oa*>}w{PGlWp7PP2Wd&`36Y zM3Tln8vmYdCpJtF7~wmGMzZN6k~C83=-F}UKX3!o(!!dcyI+%=tJ6H{$xj~`GRqN8 zl?Tkt&6_@5qwv!ds@-+Ou^|5f{jRP51OGpLr#PgH-7KB!G)A;afAyd9sfQ_V zUwGCh3?uQL{9zD{K6(^>AVW2AGqkigGpOHRYspP09HEtGMd56%3VRAwT5WC=Uj3@a zheG9m;eiysAM6aH<&vQI$5|&R9Gy5<6JNMOu;e`T8~%|rPWns(31}Kf;4*e1HXM5e zeDua+!?4$YriBEY8Hd3QFdUPcB!EL;3YKSO2&%vwtit{ox(#?^`<$yg3uEigQghrGhNfPPp3(tzCorpz7$VDEr)f-f3} zeTRAT51?DHudom>5xjxrv!yKX3;s1GQ|$z{_!ulV{SuggPsg_8J^};qX;^*H4R8`0 zg0+^N2N~E^&|T3D0SXW9%B?0NEIdEtFV5Hh#ezC1F6O znt+=C14!ml+#P)a>WdcQ_tB%Ewd@p(uo4Z)JP*8yhv>socIH7Fmw*(Jlqg!$rz#hv*>x&+t$1oP!TJ{*7h1;Xu6++;S-$&ln z{*DJ=KFF}ft;FwukLVpvCCb5{$lq&DL*nQ6%hY=TEv?@`0)G$s!yp=|zO<3w11jvl z#r*}COh3$5;sH0-J15+dbcytHL%?>4EuxpZC3gd{63NRL)>sV$!aNyO5|xhXw-jnh z`uv`n3L>DTH4eW190^OJ(MRuV>^^voQX^W+cp(H-|9YK~!8elhigG8R|ZaeYA zAR2!j`lrX+0#CwaY}8!md|Sdt&)cpscOf2XU}1GW>nyC{VvA8}p#r(d_m*pw7ZUTV z%B5;Aam)HN*3RtlQCo?`LsmeeThVf8@UORATmceP*H>`-hH z3~O9u{ZcVppm+GP)wk+BP$trRK+kmzKU5X;^F5-fWvr%JwD3{b*vKCS(daUEmtZpf ziVEhsjOu#tjxXN99>vjbtH)Sq}y-oOqhk)4VEO984EgcozDi+4bRL#-7;$bnl>Dyxd5Z{g3 zlG`2kj(8YRU$m5U1Z$3HExXT}jjoC8uGq>77x_lMtKA&W7d{QwJG_^*0XZAC;0%=RGF1UOM%?C>s$Pit;`(8P zN(xH2&It`lFYF52n{TdUV&gd>pj`0*m~q)sk>VmciCLVOIX?F!s zZ{ggLzN>Xtv3SwaCFiSTrchdwB#j#K`mZS{0_kPrAz25&ksk)p7-~ST8T7-7Q4CBI zd-DyE`@lxbmX1S6kS@thpNCce|D^gN4rUKFt6R&e@EL%W++A^km;+Gtu*RRHr@>u( z)`^?a%U}z-X$|md)9}fF7UaEgspkK?wNc#MGRPs7&?8 z3G-|pIFl`I-Ibaoe^|Y_^|zFiq$TI+{Qg-89g03$242JBu%PT82GJO5K<^Kbo19W_ zB?9v*>|Z7yB(~(Vxh+UeAdsx#fi3b?xK4&C^SJaH7L^$S8YS-7V<>%(OGjam>$@x5 zlx*OOu}}l1B$FG1ab`40ZX`s(Mro4SMxBKn zy11sfBg=)+}6bK%|dEQAZ6ik3> z%5q|;=oQ$K%as@*+E5+&MJf@D#kwnw%3@-2p!lhmEsQ#iy{pxiMMYh}hBZ1$i(__U zdWTm_#v~?Svrb$W`|%cGb5D(s6!4Z|OU~2uktB^2M}H0BLobI{h-Rjog8WrS4;4Mjv4#`wDS>X)P;{U3V!_wdmytHN2?B@hcaTdS z3c$z z1Ph85L#^d&VNC%Oops`laC^o8sI}1Lk|d29>i@d7`H8xw^#>|QTnXnG8ePWTMA-=( zv5($eR3~wuIMm=V8pauib7mYzUkW6M++-&D3ey(kS-B#f@*ZA={a(>!^=i>R=QW69 zTD-K)Z8?ZdosfRXBOX7Mv~FFG&kBNxyxQ==&z_jUT~z9vFarCAzbQ2Y{E0hoH_Eo; zKEO|K-zC%+{f-afMaH+ny$TULX57Q-se}o?DE?h-C2^Pgnl-F(jyN=7UZURNG;w=W z9DCM@J%n5MaqiqxYHUqRfN05inog3WQN!+E)@1Y=xFI2svm5FrH2UaO@ON@luyF{H z4Sx{qg3;(CWGU=|=TR}b7RrbDXfg1D8uT|vG&%)#xo4b#%qXZUe8-*`76Ww!3-&j` z5m4nsxO>*lf?e?)Z)#u{>djxu{Scx?L%=F_Y?Kh{cQZI&B`kz;C75T(Z$om^e->;N zGLS8~bA(|+U!=PtOZ1Y%f{i#B*~&A3jYo$j`WGRJ=n-~b{B71^IKpUj8T*HLbJj9A z!fuFrG8e-URwq`b&xCBX5^01J?0Y4I56}i}ZZcvN`W(IF(L`JW1EBZ|mMlzr3j5qp zX-UyRYmg~8RVurSc**NEfM%6Vu zN94`VQy=AqKvi#C%EkC4scfl-awz9?x=a-M=I`>^|ifBQcG#5%1(X9AI*?NZuBLpieZSJWzU@^NxyyQGhH%Zb+@%!cT zKUo7X{)ZI|7ppxhE;=+YAynH-13zn!U(anwg?LR@*44fvPHk-^`!>>`!(<{_WvyYeEx^`LU#}SX^T94 z-Mzn@|NWnH{`X&^fc+y!KrOc;R- zix8{eKs(jujCTroVs{ z8v1YjfME;ReB7W%d)+bv6QaKBca=UN_tRB)!^8w0&Kt@JW$|NULf8ELMocI#am_!@ zsow^`P=;k-FmwooTabXXW91cD8` zYNZY148zlS%(cx-sJpN{n1i{gwwW1>6k@GyW=`F;(1sC+HO0V?L@;N#!yM)T!ws23 zKLo0DD3LUYp-h6et+k<4GBUO_(>AAwr0#2|gdPh}h?&wsNK7q1kz+~aMbSfDu%;-Y zLXJRsZK+&rZL}?^6c)A+sSBoN@CzmHEGdReA!usC5`Ljdfoxlu*+L$yVdx~9N-GOk zV=%CY1@#~-$;txWhar|>6cvgIc-%eA-U=ct*BmCdq#~_YSyB(d%7#%^sE0LSR;;Y3 zFep|q*ovkc7zjl}kCin=7_3t(YgnmNRyK6?S=mtaN0nitNeEfgB(#MkQ;*rg$4p^~ z6vwbdApuax!{T8**uXCoKNi$=ND5r|SzA%zU8s|jV1t^OLxkCxQrF>q3y9V*8wzdV zb=Z{FumX+EU`DnSD`wg@7O-t0|27s>U>F-~cop_38`vsF#x{M^#}`kK|C6Ns7h1p6 zz+Y?N)Bb-)t+}7~|2r{Qfe|5VT$!IYCWklvdYKb{2pBqSh= zNRTC^_v}$f?b@grNFUF&c1Ria9(V#B9V`4TAkagjYw=$20f`^~4bcmRqmw4-6K_E( zde(3xM0=RmeR#hUn453GUxR7bO`Bl+1#reP9Zc}&zy+H(?EfsOnfI5heNC{F4H@#CL@ zmC{#W(xgYA2z>#`xCb0#A44+kg0+#4z)hRG;Aq%Gkm+z2oMt=#moMJ~OJg3xV(x?ibguRebSN;&Q;>J@k^CSg&}Ks~Ij`0+j996Sn!FuDdFvfslZu7Go_cQEVApeOb% zsI0sW0^;9+$B(apYS9N+#5J%F`9K%(*_lSptXj)XLjAP}CGlj%T$a7e4@ED7!>_qC`d=YD(2sQhUP z{@tD}>M1J1Ld`%ctJsEoV1nu$Bws$7k)XWPadGn)KD7 z)`uR_N}HpibhacITHH4P0BN6LM-uT8%B$L=xSdf{{Kzo>b+ zEH3)-;~|xY*H|R1f8oBtBWe-fqxVeiF>VZWc+X5#5MCd!L-|lI;fj{j%!lYY) z4yvL5lVeEVqjZ&4w(QN)#@7yEAK9DUCDGAY(om6dM{#KM=pFA>%hKYi5)aj)yOm?^@}}1kxgyOsu}2{oK868uN~s3kP_!g zbaVzKH51Z`L!+x?WkgnW{P-!-Sw!!yNs}VP(}|=LXAO7Yenib9!ss>TPkhze(9j`n zSG2rJzPVS4&dD5gwx~}^1rRuYW z1Lq*I;c0}?SiASklNm4Eb?s*b*R5-gpW}FCMPPXs@yFbi%PwqeXt=Q8koT3+X8~5n#37;O@2+zIghz{oY(Os~=-vBMF z{NXWQagaDC>BY$J@I#{$rI*6s*4*)t(goqu@JW-*q;sOa!3m>CNi|!HnVZ{6xV&H} zo(&~T{xZxaMIw2gm%~$$fQZdfV@0&I2~WI(9u| zjw{UIU4YCL2#fi5fK7^z@Ll3A$kbrrdsYF>6jh~t!;Y@gN-==PajhMqmXs9t!WMLw z)>Z9-%#}&^?a(IT$EQk@YF#co)q<>Eo zBodx$J7xlnl=?~irZ@QFL?_sYoEb>ty-CSL2S`SY?Xrloz#lo|nnIihtDrJRz_>lf zkyVK@c)c6$i-uD)ID_0P#bEkSM7fcCnMQ6H{@wy`WC zRWZis-h?Yrs}e)dL2U0BW5F&|$jOO^K1ZO9TgC2>hJzYjwxCqC4&3Cez!K6?Fo$bEQqm9uP$mD^Rq)A3oQt9FkR`o-@oH`>=F zm=S+xW5StJf`a(e&GWjh2~NjnZytSlif|e$e$(9>dxQsBA!YUV)`@Bo9uz4b7b5o( z_vIM%+JhSID;Y`B{823TbAP7&<4nN#1XKQ)2{fK@odJB~y}0n$4b62;lF!SBPwhx8 z! z9TaC1XI#I-{=kMxOBdb)Z;>o%;*uxOIcKKWbva$)=+#fbVM#c8FX#?X%YD)M@IS#5 z$!MU0OBCvFRpMK)P9(vM1#f_-upAyH`WBoL5?F`y12~J!#awgW0anskFuLeHSfA7l z>bKD8l-o&=4R<)WGOUSu$R}OReW4?%`H1!+zM;M(DoYqMfktCHvy8(+z4$Y(l^N@VW8+G{h%R$^geCeXOb!r$+Z zj~jz@T;hM)`(0?_5)(gH-$aHgMt|8iD_=55a_+MEEB(SWIsSE-L#_X`v>tfxOS|#I zm=r3=US$W_)qxGq2L)2+HOTouFj=B7I6bR4_l{Y~mhox3;Ed?J#+ z(;-Q&H@E`;3rx=~+I>URAzgIdWfvDDq`RI!zB3F{ZkL_0 z+Wif3_r{Os_V1hw6|KbcuDcJ?idKIY42lri_kVp%m9#5y%mf;Z?G{8{mF-L{T?itZ zWcL$QF}^VyrPYb>WERbH8h$=CK%YKEw}83~3mw%oW&(}IcC$-dLnj_cT=KMJS!nZq zRZKC1Cd~^3s?IG#cAA9eWXZ@ZuTQ z@yF&yF6?$)e)@28RNW<2%&kKs;_hE6RUJEcA*S~-*`#w=5x?j%NuE74I$m;FlmFkA z7qnZKj%Ch~In`q((74K?(6r>3WtFj=qiOE3vP#mCWjf?oL1pPeruo-LjJLWjA8394 z;H&C;L9gxnn-?wD(01a?g4|ns}ghUQw}WI(;>C8ncLJ`U8*{6Rn_!i zTSEG8mh%oy+@{=q$HL(#UTw7Rta<#g%#F8;Etn~2T~a>!^7vou+k~Ze zZ*gBWYQm?!|K0zWQt#bI{Dhgy9El8rmDpBU z3ejzaVyW^&IMd$?dzBnMaeetNwO9DN)p7QSZ8XK0O}hh zp4>-#4V`_v;mO$y=%IWJGW{pG1Ac^4?HlY72!PDK1BuWw1(^jyFk8snVCWJ35Hgnz z?t#;gx%1!=D506t_`6a$(>*5-<;#?kZYw)5Sz<>RxTk?I$*+*7bzr!Zhtx6U*g8qQ z&@CetXGnR%i*-k_$&#bU(W7F&w0VlFu2ZbEH%&g4xmCiKuPDs<_&oVfY)CG7kC!fiEptV`vN8;w+vK88BD<}eZ4M)^BMjU-%;!Q!2&VrG(`%>|tN}w) zFA1t3!90tVib+{+83Sx@Ctb};FMnhkCL5o3v2LKvG_hgU?1_sfSSlAs)h=B?(qWrzrF$zaaKx_d-#;ak?^7NJB^bQm7MoFboAJU^(m zhpounFf9PaDORMF?psZjLkHCO%ssUPa32vAAA-8YXBd3NI)(PccUwJT{Q&MHF#R7U zJ_HYib<7UVYHTYUPEUAg*hAbc!%E1&3W@acYSAy)IpSj7B;h1%6+XFTF{c3FXms~+ zRu+)(YkNB5-=HyUlKeLQ9;%}4SpAg&iXOW9r7$=^Ge5)NnOKh22)nJA^0`6-*a5dD zg>nWdnf_Oa-~=qSj`;`rhLxB~f(F!@y-n?w;U_i}-dCiTZrNea0TJ1gTVmUzu~o^@-B(XlNDGkKou#>t1t5ZZgQ9+;W3AZPW4`*f!8PZ+%*APl~9jJPw8oc0r&Fo!;@vGOFArHbrbzAb#luNr4VdZM0fKP!!RvzZO?-w4%Q2eM`1|?ScfKn>*v@g zpPWD4M|_9f%@#v>e*_&?wjAxY;$SnuGu*(v6FU|>RLt~u#9fyC2nED@Ao7_=tz&@H ze?8%rF&MkFQXdM4d|djN}{{#KsH}rQrlyRxuai8NzxOq zRuqagmGIdH+DFA*zHx>tmSNzHydjd|gt}4Sw=Wb@F-ml=-40HyddTd4YEkx$PVG@C!6`hB}#1FlVK7+GP3QFeAfwRsI^kvo!C@$}y-xDXX+8!CGNn=96 z>GwDsrHD`NYDmUU$ROoZnSuLEbhJDl4w6xLg5sE@j=2wu zlq5ki#^O^IZJPODN8Vd;dil@NsCCAWj3RjxIuQ<%)}-G#0FUm@kU!; zP)5BZ_^johTZHbHeN=F*AqR)ofM@Bk+RHO&HVbsK8BbI zHGu>yH!T@f#zt&-_6b-SSEUvi2SIvy5pk8^L#4-Dx@KP$RC-Q}@3*%=iE)Z>J)8?G z;~vRd8pDneIj$2@C{&P`=!A(Khx?7NrnY>!`6dWeb+!GuH%Jd(> z%jD06ed9U5Hy(xJB1`m))dYKJ4{}1H11IT$;z7B!kd8lzACx`($t{=hujL7F3hPFF z6?X--J)^)2@lTKr9KD(JK|=>c%a_mp_8Oqndur7csdL8Bl^4efO-NYtcl#f8kE#c1 zNkFZss9xEBNkFBiR-AGeXNLNlsOtd$91ENE>EFhNXYp<50IbdkNREldW6gHklm)!? zn1|DfWIgU z*A#CkJeod@I|p|uJDxV5m*6z&l;Ko(Ju5i{_tVp5O4Bwe~f`x&7G(VAa3Jq*53hfCCAus2?5; zivtj2kY>BH5C@1ooc2MSkkq+A1+iJR+QSQCTNnEuycK2x$^1|ER;ItK~g z4)HRzSV!?Sj~}%EKGKC&aUUEx3ULaL)!Gelv7oo(9Ym#w zBx&qtH>E;`$On~)e%lrXa(_$^6>VDvn>1SV)izJqq?<+8E0+ReLtkXprXWyfbPSag z1p)_)Kd|Fzk)YYGh(79lixyfIgUbh z1fgZk=z}BEMdJ(eQC4fQ=x%Ne+S_qTv^e)9oabqTEcCa7>S=YhNngdxGAreTApbi$gA~uLeo9%#Tqfm-^I2{&c@hj2J1@WSv_&jvAheR}k zKNP*~(~Y$8$`GdiR^ZEvMqUO!!CZL=f`p^En70cV5F3I2#=DNF+4b0W{5!}8ZVo%jQPD%w!!pU}+vtI0~K%vb4s7 z(||sNRj`;hOj-_m_3!*E$sORqk>i4MX`z7C>L^^75)FDgXnvp!*YK&Sg3oqc$}5~2 zpP6CwxV#WSLXIb4SC|7Z2MZFcg(mH0yS-pArx!LJ3A~||vvUC#uY#L&Zu_uhu}Th9 z#;znKq`X6429_w-rOQwxyjzu-ITIZa+phkQ`3`cLoV+RH8+4?oAo+3XBD9cjQ?F8= zLJbu6l6XmBpel8NI9YNHnCJYAd<*46L*Y)IzMulQl;v@+vVI0_6%Tky=AV#b8{rhD zJGfZKL~`P)A;(A2+3Z`8<2ZnGUqVhvFbi5|sc}r9hSSgCGN~976sK1sNwC^u6?P!f z8qU+xh^vuB7!rO+(iYW?4Tyapu8+>g)a+PnO3XlPq$osK8pi;I#466Y#GiqI;xWrd zD1j6&Va=6rF!P+@iDk*|Q1X~?2WE4j`%^L(IPXV$fHy&GBGX+e@(1Via0O&~? zfCcS;4K_g&$b%z;u^G^Q%xWEo?Pif0$_ZH06y+3ZDF55`8CsI=jqipb#Ww5L$Dc%R zW5$L-@dJPb?qFffI)~N}&2~-9b!Z`!I>*ApP_DRh!TNAlv>r;(^-(rxICk6TLJSv; zhC1WS_-E)$@ykF*mJcWuBjE!Q6kvdOKx|WD4wyiU6om`DfDx3i&qYJPZ&+386r>Ur zVhx2A!Y!x@b18cSLtv>OUs|l2s0{KoALbVaB`HlGNz$m{<1b5IYEGa>-7ikn%H8VC z`nC)1=J-??8+N!55<^9uk)Hdh5T2KVMXUP}pIdX9?X+C$fAzNaaH^cQ(y4oP=Yjzq zXRKygt@gbhJ)@}S3mQd2aa_Sv%}%2 zbIFarmgrKpz|EZd3XZyU9;XC{WJ_xX`Zltk^DfpI`mW_?%b5xilfvEef5 zGy;sSGqRUnBVJ5!uy`%$5o6OmoPvp)(w+953)T?pqz4wS_Gl#nq+tQKeU=dU()duO zKZ2i=dNN)HI^cJtVGJa^96v7&2pJG7BXXs7tJUm%;_sxXo+CxGrM=<>j)lZEnG+{c}O?uFxU zT4FUTL}8!^PWUnQfZ9Cg68{BninyV$4q1b?z*cHQcOgU9EUlT4)tyoeSLCnP4NN~+ z9kl<9Xu8lc?7@*<{&3+1Z&quy5Jz^c>g||-o(4`#lB7{XZhsR2)lMTd1c$2X(GWNZ z;Hq@=YbXkvl!d4s6!wjBQ{XRQ`k$8BgHq|sKx5eq@RL-{zAgC{T!94li0_~|P}E%` z&Y(}AP(On@1C_9$Fcpaeo^W2fhs=d5k+zC#K%&P06zW7BdJo#RUdfK3OgOI%Pmafa zRX;d_r*>euDXiAp$yK0N*4shTLy|OV2>&$D`rc1W(2)MBMMOwN6Lq_qhN(j$HtY8` z;3+4Aj16BjzEHYFI9SX*aEWWAZnm4Z-!z`c@o?JNSQ#x{*SX+$LwUGv@oJ9;^@gFr zrA+^<-JWY7Z+RK$xq}@reJc{K+*-eqxqUz^Z{sbGgc>!wx5(8oyl$lE@VY%#Qx6pq zC56E@f>r|sZ(Wf6j5APE$uo7h^qYCkwJdXo*ozH?$1<4qEx)^zxn+Gb+rOi&LXmgI zzVztQ8dcGcE?0J6tUI`QhWF}%p#95sZ;ri}_}~cIkS+gXHLG>kkq+e|MsG)F3n>ld zkR*+oD)`&`6nb|1P1S+VzqDEZm1>~)z#k44&s3d)uZ}j`-IwFsuMT@SeItc4ZF}c} zA7pP6dFNJpXg^0!wv(*VmVL5-&B=qLhKUiZZOYTkDU;{-Ox~& zOdulOmbMC8RVik*e`$@g`UoC(49*eC$M}IWLHke1N8{sfLhV%Ajvju%YBiD@V6E4B zJBpQ+IJ1K!Y1HuB&tyV%ReFw_!%-}#m1NHQ)gcZI6T zPsz&}Em&JckXk$ODO9&trz1=OR8_VWS}v(Vg7*8CX1glj#DBN=_<}%;)yl}L_H2XG zgmrrI3L&&r)2OMG&n6(MN$DXxlP5dZBpg2M16TL=0AcQrRotzYuL&mVuHjz4o-Np{ zAHmJK(FL3OF}LXQ31OX4I1x5&ZbC2#tHscLQwPJL_2hu?QuA6K626sp@;9|`K&&6H?l-Y;q)1mFtNsS6sF{MS zO+u)mZWUxyu7J|)1@HUj*`kKR65iG0OsJjD5cqCpLPgC{V7~2ZsG276I#2#63fe!6 zJLKX;;e#W$xJYM>kkz_f5ZihLE;dRK>!#W8bTb+?-1nKDDE6u7O!7gekR{9KK&{6J zZCum}7a0rC+(o%iPhE}Xc*em+MliZCfLdgX5H3>`M0q$JWtXJ93+`M%vPYvo2d(z- z;8?~E3%u=PoA^VNRv^=VXM9hjdmz+P({2QtL=K3}hSutd32OF%GXDs^XryS-`q2qX zRfR-Q<_KZXIs*kmeg=IBNAd=d9|)*0&*3JtVOix3g%Q!6gm$4z*~9Rm;^+*hBZL>D z6BJ8p>?2tTmC(pEAv!d~0fCDU1P?wAcgE4EVe>EB|7eY98WQPQAR0cb40*Y5gedn% z1{%5`KsZr%2g;l`NVr)aAk*iU!N#4!yXi9;YUiuii-IfhW;@IHL5Ztim%kHj2pDkH zyghO+7!7sY>_`v3FLK-GP}JSHDma&pWNwIv7a-vySlq~;Rt<>NN}L@r$4= z_*VQA(pF&+ZGeiPuFFrv!hV2_PV*78e;e-QItDJu-r*A$42L>yfv9w)0Gifl)YL+s zX^m=ZS|fTdcRM#mGJKd}(u&{XBygpflzHW*WTNijq+2(PrJME7B(1tJR9a_rJ?T_u zo!G(Rn(WmP132%Y83i?J-(@xEzt+_F1a_xsD}c`q!s% zkIsOqZcgfeQ$=Dm`@X8O@ws@UNJpVxFM!X@9vtyj47+eu+}m+g zHuT_K_#Tr+4cq-pc@*FD%KEUtV|gKLW{$9E(Nv*>#T;b4U#+m&ZWfZo_Y`_K)e6b- zTl~%i5BN{3RNU1b9bD5wWx{Qr?8IlP9ZaVGGS+p_8is_g406Scf(OKYSUI0?30AXP z*IdVPLq>|w@GSAzh(cmSEJywe(?Ib$VWRqBTve(YcXG~Hc0-{`R8T2^8i^zz>&xeVO$~gq|3QX6 z@Bi}-XR*Wj?f;|hQ4CYIzfW5NsEy3@3uCHdQ;t21K_38gN&)${&cIK06PS7P!I|WH zfWdf$>E-MPW(hKJ!Kuc^BTw2CwbJL+|_~y%q5Q%X>2x4}`~nQmgmRCH-ynPB}a<2GPzh?L#Aw ztDuy~AD1c{k156E#6YDZc3;ex`bO=CO_#LK1SwINr^L+rajFPoK^LHa{0~SXr(OML zAtv&TH_K$KKP_Aoo1br093q?&6S|2gwioVY=3Q96zDpP!YxaD&Y#(S9k|bRoEV}P~ znfO1(HVm-uxAk?!`g@X3x8ncuQvbj7j(z!YhL-|<|C|yf4Yzp zo5J;}tEu2VRjZ=I(f8>1mm#>cwm3iz-STi-IU9Epe63&dzt6zNNRAM_X?}DOP9lJX*^v>&@4-*L3 zDj`K%`5w?HO4Fes^Y5!(x_i*hJncg*#n-_EG=E%%@Ff@w30=$k6GQ>VR13~?upN3L z{F?X#+=oMZU&3S13qoag!hS?ID%#ciLsiHMm06}yh#r!zCW{{j9TF*%^Da1qOh&}& zyf^Ex9=Pl$NxB>jA^&w1OF29-2GIe7+lM-z%^#=JsZbqBCMVwMx+-6t&X}5b zp+Pbwt9|C33m$}Hj=8z$LLqi4H{Uku_fl{-&&>PgnLR*W0Qcl{lrTwksI2(3QFM;l zEVK1weAMWa&`popuSVZa&AU+5zK|7`+4g8w+c|NK@}C7LrlcF)b?HFD?L({ZZkDLb z{6`=>9PrzY{Bef-z(wN^k`uGJ!nto+8B+zUx3j-G(?0WT#FN>bXU)wAt+jHHoyoVo zvfSJ0$SKm%#QT~{ayw)GJ+F(dc5P#2g^&pg>JeWu5H!%8$84%gl)5=Gw*_XNTH2& zCwX;Y$h~ zSlmAJdE8S`8+QPK z`%46%|Ck6srhke22LndHxEwHK1&q5v9qvj0PapJ21^oFO>Ia6?u+}#V3^X?}v#~N_ z*qE5WC}|cJMpmW_7|_)s#KJ5P24%H`x&P1ivoAURXVw6v|5KmU=FdOB|6d*v6V*@s zr*2V?P^v$K|Kj~m-J=~%Xtnqn^cAQ6N*ZbWi@NL+%Swo)QhZF31J)=@z%>UNOB!h; zNgAo=KDx8?B^e|x5Wj{=Xe?=@ktAuB9Pm8MIAl_tJ|DQGNdq>&_P)TH>lLzPnC zwX|BW^>~W(ngfkpUccidLQHygr61nOS4vL>>3|1xoP}R=ps}QpMv|malkSr~^?A=Fosw3_uQ||I(n#Y!QPlei zq!ru$hrREBi|ShUB^o7QOGL4wu^<+Z-V{^>Q9}^{v4FrZ4B*g*se5*tJ%yQp8G0`| zpa>#TEZ7602E~F#z>ZNP5fd>+F~&G=?b(SX=iGD7`@Q$>J@-EG=lo}_S-XAvTkTul z`hUHy6mC08;eRmTeRmi&(Cl@kaNAJ||HA?3y8~CJ>dF1Zy{;5)J4zvNWYy(=@PO|; z7)fVVJ-z$2=R$8D>UE{Cd#!5s;1<8br&5OQN(+8?>}!})wJ{hyc2qjODkk{Gk%Q{b zs=9*no0|%}t8Rv@YO34*vZ^c;s;#Ms+OACimFq`{8G1FQdFXd z{JEwkxd=}`{A5Sdxgwj)pZm8RO)ci-Ebh~4Db!u}J+nWV9!7rXg?h~)28mu*3TtXw zH$}r=a<&~61^*(91v^abR-j$I(SP~u#RKBt`u%NN}*Ouq3)o!X6biFJ%O}2 zTR{b)aNAJ|wOR^wN4+!WzB>#!FL1vD7o%|7Q3|zM3U!CQHIKeKstpQZm4J&;xa}x~ zS}ldTqu!Yl-yNnU+H$sli%|#~A6*(WK04GL_MSQJ-EnKMAg(Ra>q_CaqZDej6q4mA z8_D0_ZTt`SkNneJDdgEd{(Y3df1LyXm72Yip{=2zF7mf~czi_U3_C+x8r|M0+=^~) zWo2ax2>WSZkZ5hk0K>&d%Wyku5E$If?jvjef8AUAc;$~0_`f3oz|N?v%8QtBG^!0C zqkqrv=Ff0rr=$Q{Met_};&s@B+k~6b?HnV_!>z+3%;^r+k#=@=^azF>8TsS?&b@p* z%SQ=(l)!(k1pcA@PsjhecDYZ0{|qy?z?CaM5&-}E5CH$5*Z>(cIvB$-Y|X6zZMZqj zhHh)la0s_Gw+y$kr&~rw(jy#bAKUc*+=}>LbhUq2|NU3_uAM;z|5@kZz42r9|2L}s z@AH5E5&K6+j19QGK_oVk+8dB#Tao0{R>0~HJ^_I>_yh#if|CF;aWaeKL(3U9)>gKbAhLit!yZJ2v$D3gHn)ke2TKxmwD3s#a7Qa!2B7Jl z;b2d$*w!*H|)-#}9q4oJ?eoE#(VY$8FJlkf;TFi&-& zncG>?BEZ@P&5>qr70C#vS$}NO{SU^rz_U0f^lr7ED#@?O=NYo7ssF*@Abc27f{D-lC z{>6j-(_#VHzuz2Kfg0{a)=)cLJ+=e2RY!eKR%$z4rL+eP14(jkN7hgf;DcN@1eKQj zbog+^&|wCsk5)ExuwZBt=>%#P-IfLnLN+w>2&V`KGJuekm6Pqqn)SCe>%T|NKdAq2 z<=#acUSvK0)Vg)r~t|vgP%^K z5ctIE(xf_tjaY<tB*-Q7P0IF(oF zI|}48`3weGzCd_7i9G%>*g`7kxy;>t3-27OX+@%$UFc82zVjxa-c?J(BYtbN$XwJ=%iou9&v87>fT^ZBKrS}ldTQ~t$EfMih%8!=0^KgX-3#rU~=WM1bHk@;7d(+WPXyXW{x z&YAqYDtGtaayI0dYASuxbJpbKmogYlnRnG5c|epIW~z*AAW9wT&i@y007{mWd*taP z3g35^{9yH;dY>-}3zoiuMCOTwnYLAedyc;rtg!i10IFfZEIUtrrEfzX$0?J?V4Tff z>am@BI!Tj-uZagtu@vfV;sfsh>g^2Wj=FRQYB5%Di>^$7fHLxi^o$XR%$2+?H;noB z9G~(o-B9t|-TnEmx?4eI&*JaAgn`QL%K!do40Jl_BJX9>G~hcD;HM_vaUCvya!` z<^Lp|#7VMm-M>FNUn*}jDn|?0g_9_JuivJAh6M$?PJ?cpLh>Z<_19Dpf7CE-8rZTx zp{~f=e){iSS6AX~|4fzGF1Pge1pNwy)MY;Gm#K2~L~U{rXx=E)mHV?>c)l#S{`C)%a728~p?%ljftEEs^?8E+#D)sh|9iTOj|kj|mv}D13L#zm`jbp06x}v|0+ua{qt*QNC?@dc}dm-tPYkNJvPDX2r$C zQqjN2pGculjKE%hm;Q?syveW+TLep89u@7&#n2TShFwPm<6vwmrUYA|GO!7l3)*aw zhP^}&B3-kX*deh0^tst`EE%5bKHs*pgWL67a1TVdV{Ln z)?lkJIkw014w{T5V;x>f6u}l?{eA?Rh<%0`1nozCu;-{*@Nej3tOfN9w**|aOTcDC zC#(<+L&VIv7ze!qS0sLprJ?hYUcn8(EKJ`2G1LUZFcV2N>VR#>mMHcDu3H(lS>1(h z!!}@ooKe^nY#~-yxD@NaMqvlbLa|Kj9@kUb7;{$X9m&P7#Hj4IE^mG(y&LJH_^=)4>RZt06?z6J)5G0p_<5ypUBJ-=elp>n3;EMJ5OJYfg}LGZ&)?$Kpkom=h6IYm~5tc}uwGjE3*U^cHq>*zxPPS_vO@dh=&) zwG|q4AK;H=@`YwM3k7-crtr$U-$NFxPm##)W8sn98$itWk^6il5c4P4CBTT)@?4EQ z7cK%_%R_7-;*0^2{@H`+YJg6}*F_gVbO^z?C89pCXRdHkD!vcx1)BaTHUa3^I16Lm zWkC_xOIObc;BQ6!9J@ISc_C<`OEEW|yB28nbMAERc%ZQ*oFiN-v{zujo5sC@EK$tl zUgs7eo7I-w?c7>KkaM5Af!mC9Ra_OubL)`jyUgKu?n59F6k5lf0YvhcXUbg+L}JK& z!I=s~(#N^TIRQkH$bHC(LpnP2xG0x}JnHP`7;+hiLAN{C0Hn=sVmu+Q6Nn&2xJ}rM ziu;*>|K>Rm0d@bquax+4Gy`bZ6TBM@q$b^OX~zCuJD?NNTY9buIvG(dMd75I*b+bl z)@(wh$5~YJ?U6npn?<}f_$H9eE$()>2i@bD!Lvf{qa9vUzClnPCl{&(wOhj}hxPzj zQTYtU256DU2op2gKv0Q^SVf`-d`~lIMHYtiGJ<4`?nB8lhtLOW1F4we6?vy)p5 zUqcPLskFGi2iXl;8O;E7UmqG6-i;*w>~2hk{VUP#$ld=Y!zlmz!bt;mojI35yLZw4 zan2pY&vE?Xi@A9~3t|=zNOV4un+l$|%V9+BL!{qt&|zrqRm3dVz&R#&B6#9m z&g$GHsF?ZENtEjYT9PA+hv%IGPbOOYYwihji9+ps3>+`WIpv_uxq%cG_Bp)FIR~_5 zsB;uJ{`oHIc)w=qcvUNP-92YA7XOypf_8NDIbX`nMa^#NJM`on1>-U5zIE^N-N=U& z0J%HTWEizSKrpT{B}sIMQ8=kJg(+%fG@DTQ<1G4wSFt5AHa4Mf3l~nbczW>R-2D0D+om@`JwjlkGVB86D zFggKjtlcTDL_?5flg-i#Ab4ZftR;#=sGjh-*$vf3^f`Z=#m1}~NCwZw=6+TVa+2rg zs8(-B1_g;OzbZ7y643q{q^gmfp#2>pOF^vQ4lhjR3|N5s{ieuo!=Hd|Z-cT7?iZN_ z*QW1+SBpKvGu1EQ2_gm~CnFxIg2YVEj7~(wu1E}1eToLAw6cFu-a%Gzdj-jgU}TO^ zi>ef7;Ax=yV=1@7Ux}6|eo=0LBSeCnsmk$4K3rJnowf#94Ruv$)tix({O7xRGm;TL z->9xIBMY$xd-lWCJCWCdgkv?zSY!dDYJH>7M?66LD@a4(y+EyMR8H`1{88r_^%_vI z4Z1g|b6^A5>}EvzXUJ60{$5ll5h-YV%2FqxuR!}dNtTCVh-QG2msV>lJCEQ(Ali33 z-QNxAs1XP6^zXboMS^ihPTowszpZeRdn+xzp`zL3w-Xm*X;odbwjW&|>rwmMtg-o5 zW=G>Vi-LWoTUQ(bZBBg)op#F4kyjJ2>6Z(ME&~+_zS3{iZsWHtT;tun$Me%|+%+$H zI=m#BVLqpN`u!g4XkOQO)gWkm^>;yfm&}5%?rsY?4oH6v)vbv9;TW(#G;WC>yI;&) zyYJ`Z&HE}6AJu;ncl<;vd)%&BG>eP9f**@w*Bf;Ks@|O8K4ZH~Bop$+uc37>Q9LOM zT0Q*QX7$fyR%;Tj335U!^?bs+3JW*vxwPK+{K2wk`#+^^{<^E;&Y`n0y#3F2xi>q+ zo~|>hV>GVY`g)Ib)BZgNqZaS;YnJShM0$UnaExD@9y0yBs`c}oZ+w-Vd(NESzIN5l zD;*u_njhB0^gQZ}ty~mfd)=TLu6(nR)djl3s&SjfTwHmVw|8>z?o*N9_Z%#bIC5Cr z&pM`#Gp*AM+&b|M=j8U5=hIr4NNYjwE2q|{VnG(LfSjD8xS6hdHSbx%$f{rs{+kRd z!L#?)<+>P7K)>D@S{6KJ0>UpISuGeh675iL*;hCz2wx@XIMHl!ny`SMp6#0TQhb-| zc=frNT5OSW_2xK>Oi^Fr{9YTI5vV+F*DXKC6@vDyH?JhROy(|RzG_puv4MT=<-R?h z)|hwl#VYWgWMwInH2r?hl#4h&?lcH8NZ-Omo6Lgal}xVhNzd>xsW5xwWd`F4k(jjO zrkGiZ4lo11149!?!YKQ$m7T&_Mt8g3E9gu*8kKNbix#KE#Vy`vBDo{Hm~?m75=Ez^ zDn(qmS$#v9#c9|n$Z=DL^Iq2#7WQW(2%L@{EQ`tbLKtzbt0FD^9^`QC`7UR97Vpq^ zMs*J2rO8R(TQ?0L@l4YPe$CJL4;TmTBpf@$sbV;Esah{`{g}s3?>X}`YM=aKZ%4-* z*5nj*iCOb-->fcKP@+AZ98 zTr-eEB%qtWZh78=yb*`B^u8hlUn{S)Xtl36>dc>7bnodsiz4}+$PM@=!_IPlPNC93CU6(E=+70P%ik9bH zp)RsTa=s1}(#fLs_H2B>N#&%M`dDzAW@}bceil z*TRV|6+%ts6fl^HghvrSf??cpxIxke1~uKn>FL2}zu#fuw<>qkAShdSGIJsr&Up*B z<+mc9;pd>)8Vj&6VhR=1@nJFZf?&q}X<)LZ2hC~-fLqy>P)nr&m_6(i_GOrX!A~&! zBXI`|ezM^+=tLm(SQr68pEs*V!QZClfk9A*aAx`@6wrSNLsTo#gJoQyan2wZ3{4gG z6-&U7*&6z0j}*3UdLp>fxES_p{sgjX@P`wQsSHxdoKnAAP_wzy;&3qJ0Uzc(chp^xJ4(pyd0J zhoKfg$<2@@Tc!dfw?JrN20+OxkkQeklK+CB>`!pDTL|ihUIR+riylLs10{FHY6P?K ze!p4RY5ok{Am}!T9cO}@1#{6Z)>L3IKZIOhm;fbLAQ2H0fs)rE-$hOaO5Tq=j5P*I z{ws2T`xGeoGsGKf0ZKj=l@bSlxO34Bcn?r=F7^g8#RWNf_&ol4ys&U0{+b(scU9a& zX^GyrQC%@&8D#{NJP_GK*9S_@M0Q0Q0wq6#v@yQ`O8yIy!TAL!`A8H&I)RegpeyjR zq>`g!(fdHj9kBU)3*78xH}+!^g0H;$8Lo}p1(aNhl`>^O$@iiAW8Hz0-$V?Pe+HAZ zY3O6l0Hx#~SdyqFg^I%JV)zBLioF-i*JOhJWCG0Bq6K&&4a`Y$1--GW(C22qL$e}q zWSoUes2^?wro^C9L@WU#_7hMV^D(eUEQEr2SwP9#1T9D#P;wW+aN#zfc28`^}1h#M^Q0_f^OdtiyZO$LgPXfwqz(2!_1IjJqmvdr( z1)>9LkNFl@AQlLJV&nr0#23N?QMJGVA%l*@O$8PRYbb}C4U~F31PPA8xQXl zff<8%#_@iUjm@{>$sq0LxKTWB_hq2mYlyte&7cKWfyfF{AnwB=Tj@R^?lA;EeF|v7 zR}ifGI4Rb`TY$I`fWg<{+ z2jW(SA5iWBk%CAE;!Y5GVPQbr8KS)+5fJxvk)8ZD5cg05NjCv4xCt>;Z4X-TAi^t0 zigi^SB1V@y11&jS+_Yy9D7i{}qR~y{*9?otH<*eNj+uz%I}o7cZp2ra>wuCU5OqrG zf!NbTvEclVI-5k5Adq#oI}S&?*nc4793%FB5JpI(PlIc+2eUu#6z|r z9bRoBNRSUEriG$xz8od%^_H=YNJq5JcYtdbiV$f#WV<(thpv~TZXK*}_J!f9x zYy<`74tLRfJP(*V(nR-=d|>W4C;F9_3(OrSMQ4-JK%31K^~Ul+o85_{Oiyy=f$xYl zL|UGo#oi?8gEm`&-{Gv%wb`VKfBgF>f&U2-_y_)f{=O@|@}J@673lHL{Qew((-8Uh z)~z-!oW(SFFZch6-=Bk%6YvTDr!M(#9ew}B@*g_-{)-3wr#bpMyzl613z&aMe_cl_ zN8KmM0_13I`Jo3d>5|W|rZFODbaMwsI~#L*8{o`KV=yAkoh&05PGAeCZMfw}7vBE~ zwfTSl8U9@Vv$gSGt~>hAFI2E^ufOsCBL@}~l?Y`U*NmVL`irfcr2p5`qgJ7Mhf+8g z_69wIJ^~T4zW}Rb&(U6j%5RZTs8N!?_oMV|HhK-wpdR^Op}9ypYFbQT`%W=hiDaTR zHJM;7FauT8Wum(fHOg#+(RxIMdL8gXzXs*a4=)9Di|Od-<5g&uC>?}zrckS;@V!;B z8M@nChVhh!m}Ufprc*AVmJ$rDnNLGs3FAddi?!$$s6-TN9fbM{x^ZuN0)4|jj#@Zh z1o1zugkxN%VjYk^Z(zAMM!-QSogSfB0_v39xH=N^##@rJy>aZ8NS~YHcLlpa%)}GNc}O zyXkdKsbmwOv|y|KaZDC#y|3^U5}xX9-&fuU%VZYLXSTh7QSlg8i_+1kEjF+m&Krdd z6LxwmQt>bzzj5_-Ndz`gknKGV^}sfvDSnAi6qct53_K25V{41;HhziRL0oE#LvpZ5 z@RxfB!;D3d!gG~9^gW_Bp)%u8G*=V_V^C4-5BPCdnE)jc06$~7@NDr2xKU*yT!62F zx8yFH6cTAC3Q>Q6sUe2;CFwwp>M3vq=^X|X}F2%nYO zxY`5_!VFWP0S4lVJ(Fp*6q09n8~*aGInSBQ)+<{#TjN@!89^a2rYiVg<+4J$(Tc5` zc70RmF<}i)T)U#!bjk_Bu3=BH{&Z7CW*v1@wVPfPM<0(yYUV$Hk`Lc! zDlN(qqxQbo6ljsv4uFBsk+1TkZ zbNSuqbD@o^mpggKDI&AI-E5X|otPHrS{xC6@a*Y@En_!{N!Uop^>BtkuI)ns8v z3_Uy1;~MWe%_F~O#RQJw7SrP2-L$NlP5R|K-71rb4SkwjZl5N{1hwyc;k|0Ih z(-rM3-;G`e*dBK|7F(XQELb&4m=tsA6on1#jw@4Gi`QqJpWqd-CDfVKc{cp^vRU&3 zu6AIL=2}_UUF$6Lo7(Sed`ni*G+DlMu(y1>-qe~UJ@={nLw)lzrcZ*G6+JGMj2%B> z4K(nDkUjS1I<1yM@{PuQxGHE*=IK$ZYMK!g5@WvRt1z!vj|oQN5r~p+I^}8V2Dk#% zpWcvGB3vczoB6FO2Ko*t7_8pTm&$79KT!p9lch?FnDma6+eEDOe5GabYt-AGpZZP0 zOM!*+Zppd0S1Ds$rxP1u=f)2#cg6Y4?Tl=12D*nC>yzT=k7#3Gtqcsb5Ml|w%j`DF zc!yXTXXB8U$!>xr_Jd)S%vnU0O%I(%f1^BTb0~Uos4Vl6eNpVKkjojXT%g23TDkly z@H$V#oyWU_TzGl>D~Kw_M3{&U718;CvsExtzLw}EPBC3F`y?$g*2aegGjNTv-*bNX zbq=AMWsndW&rR%|jYp0yx%N6>oxa+~?Q9k&AdG#lpAcZ3GE z?~GXBw<9X3rpC#9&JOV=uLDo*&R5)vG(X(y7+LWwdi3$Jivuc#GM}{QFS$^$mwD+F zg$?a#F29wv#Alt?TRuLwoW<(Q@MxB`as#e@vwEmth`{bzwAYfvW`XgoN!~GW7X>}{ zsWdb|Aqax0`<;Aa@5SvU+ktsxo*uQ@sTn~bF~&!B5PybwOz=|JNK1%zQ;)3O8P1BD z`Ed5Ntcht#i{Uvta(+&awLXyjI(MJi+dd+zBX4b{h4c4n{ruxuV_c2X%JV+S8Cc#S zmFF(V?ezE@yO156m+c*p6rw&}kmC1p>n~}l!oWZ~M!aGt=tFiz9Fe7gZt`VBzj$x% zU|3aDsAzvi58W{S2$rBY6#W}J1hW$nmHH_$ z9W@b7%(fCIgW#BZbBBqWrM(0@UrZ26jdEe(aM7C_dUj#qLF{y~Y4NVYG1zxyH8rfl zW!NvJqmKguZk$(m=~QQ-J04uv(9S9d#(&DsI{z>?315*LaP^{k4nA9LcdaG;AvQ1F z_}2XN%UDqwG(e@bS_(-?{t=)0oo$Tt4%XA7XpuD|DAb>R3*umBp?1?gJ{KD!R9f8S zJ7HQd&PanLs56@FT`ii5TB3Fvhf1cPPar6i7}sScdgjBg>ICbZdh*=5s{0hb)}C zx5LoS*pquzniDy zcC-oME z&+}Vk(;H$6l_eVGruyCj-gZJ=QFk<7SV_<3)Y9|ll^*%f?!H|A3e)2DJ-hQQEA`7M z?9+U&%ExL7+IM<}oeHtrSyL0?ZR~&igrbf@W@CY`)c@&WuLGm}n*7Dh=7$&iLH>!& zqmTdWYvkW?_(=;@?$Rj=8`>M_w>PY<1Ui*(z#g5=>TGNLNjxzh8lVuc-|5oio1=_+^xI*`#fvjMI` zm7phG2>jZ9S1K)z5YeIlFlI@UoDfw=yzQ-|kf>B_;k;IY;E7<&(n7SM4w8Z8dqnHt zVX{sSPr^-bS>Cw1P1?;#NYD26QE6C=%oM+_qS~ZuIe~$LyP@RJJiCnz$G+wK1jc`_ z&Q-uOGY7+FUM&2vs$RnR^0U+(*)6iX3!&1lzN2q`J<|6KwN6)@< z<^`IW>yf|X8yxA$HZ4}1+XjD^p)$SW!nI zvvEmMYp!>x*MUbYaZX&B`Qe!<-Z>d%qmS=S3dqebebPdeyL5`ehV~j2JM~xa-aL;o z1m?x8PQwcZyzfEXrx3)Z*QLofL^ZAN-W$0Jp{MsiumH2gH6th_#(WB+STasG^1|9N zACdlaM*0KnXKCNepv)WC!qj%t4LP6SEvYs0f6tZR(Wy#{+qnwSRcWksM)r8|9>Uwc zO7%>VidZ;fGL|fiJI0lT!?GEPpx$94q^p>n9$!j#6P{ZeS7)mZ0YhoFcVTuT92uYD zcS-d#bSx<_umN&{ucg>+><%4*x3i5yn%sv0wNb za=-0MWe6DQoO9MF&L-2d4=#P3+Q9P2Kj}70_F0N)vD1nLlB+EJatix2U0SSQItrPMU7_>J*AUPEmDXx0BqjTH{|niXQIi^uIE$MA=6MwM z(38Xy`SqBIP%Rd-PJ-!Zy7(mP0;*AR#cEKFP+LkME`rgsi{*2pwTMUl&Wve(Ux4Xo zU|zZFNRfVdSKdX(=i)xikjyjgVbb=UDmfbJpscBhk$EGPIf}a8v_+ZHQf4DBce3h; z#_K?Ou{*M7hxy^U5_v+>j?u?^^58fWDAg1u9u`f%JM|8CO6RKCcx zctX6nWCo#MJ~8oX>er$^O(p*ta5-z==_e~uLEY*DT zS)$j0Be22AZh`sXD=DJ=OU0v)i;^yGiIV+=H(!d{{k8N!Po2TzDUXFN`*`;qkk>DgZ)>tVXcBVP(1k-rd|7Q4V$ zzH_R6c{+Sd6RGUe3`Y*^I-lOYa|QCSI$m8b_mRcE^26iLJ&klm)70x2djl-Xeuxgf{M6jfv_nAe!_xc z{t%!deCRmm3{Z)FVvKP-IGVuzX}p2IJlQb6~=8izErUxvCD-Gj2u|0)#Bx(2g4 zKSe&9Jr@hO`V{%WIuEhC_5^D2U(PkY^+Qq|d^>TlHuZsqIgW~Nkl_+}dyTqz{z5p+eLamlU-DCY_6^x8# zpr^MGU&jeTG$SY^)C+MxkfvwP!UK72h-tAoJ{3&5^~*i+MfkViIV16x*ei_LSdTB` zHUPs?G5!TN2N;%Ucn1G6Ff5J6*PyB3d0H^KY&&?4z1W#TFLBoSN0?^2K*H*@z$aIE zO9QTY;bzsPQfPoet(HRFE&W@3`~&;{${9NTFZXpG{{AxpeAj+l1^BlK0ATz6Dga;w zaB#FDP5*$(hy0|z>qtES0SK9+3kd*-e8}Yik{HC%mLvmmv?ss6A0A+a6D`s{GQ!Hy zoMh)QcceKn%mH0W1mOONuyvr>hucRwI+2?`KmPp>kpRj6YYP}W7ez<%4Od0-zcjQ4 zsi1I9oj_qL^0*l*BjW&* zg<%BX2DYLDLMQu(NE`DA5N*QT5#*R#I@m=zI)HVi2uH?8!qERA%JcCt{@Y97AN2p; zGj#EP{8z1B7cj%!H^}GX0N~$d0ATn20Kf@^{vql0z`{P+s{^J!vd`B!1_0_W@Eve| zk)L)mES#JG0`4zMJJ9=+^nLI3{?;?XBW*1m933LeEv+1_%;`>mlFy0eWC@yaTbdoi z!O7B+{*mkXzkQYe-~s+z|9{aja+&UjKfnCB+m|dr#yfd$&77JLr(w%D39$Zj3foO- zSW=1=>}@{;qa~<-t-1>hjQ0eKo4e8b2`0j9ZvuUkVj>I(3`M^dE(Hs}pQB$DT@ePS z{D@R*o(N5Ztw?6I9#|U7MrQ9F0c(^D1ZgsY>Dem~&4J0VY4IXtPO}Nzr?E!hgVW&Z zngC?nK4X~Kn2!*9M#AQYe?-vAVQ@oxCYrAv1}4nkqUDkiu-&z>Sif*0JlMMy`;ub| zAL>(L_mj53&_FHrG*Jk+2&nrfpZ&cxa5A7Nxei<`To0C~C>-PZ0$mC@A)Oxg&~ZW| zB-{IIbT~2&2~N>N2UT~FUZNLS1@_v}vri&b`Q)VXFcMgB9O=^>Mn)7IMXGDgA*#Hu z5oY60$dT-G2&xd5J#x{pJB zL+~Q>Ab3V9t-JHTtU;4}JbHQ!+q% zw~$+jH~?M%s+{h8A8mVf}0F z9I(iuwD!yX4eFsA*g2+di7$#hZg)hY&kRoa&8|fp6w{~S+6rXuTdQm6wwqEznaoB{ z+voC>xF;xo7v+2-9PE8znUb4#h81JNL2rnS;8vl}LJ>NKFvGAs7 zs-TDd5VJ%Vo-F+SE`$F)8 z=rthEpwha>c*k`?N}F2RQYyO=YI*w8DIDYKtGt!a#qab`D{m)M@xVGjdSqgMY*FmI zbZO#YAl>*hNn(7gMrk1DCGuFN#ZP7G!~^U@ebvfK39Z0>MV&`?x%auE$O}?V3+JTk zrKfwc8egWczc6vNLZv{7C+1|+)Gy+#~4}H^xuJ~u! zMX~+oFEP*N2d8MxTWmd1*r)mQ0&na01=Tf{7a(R&zWL#!7rsk)l*Q_Na50QGAhWwx zbx|$2D<16CT88aFcyd9p zy1E9(r3IH&cGo6KScRLweUwNPg_XeNoI0=W`hVF$MzuMc;qllWaD58LxSGNzvGsVi zcPU(f@I=NT+u;X%1K>r`C>+UcmKMb}2pm#&r3R;PxjT}26um@A!kIX(TBBSWD~V-i z(zEAmtzq_MnHEosS;;KV?$eBk;WD||)iuAyFk;tbF&ob@UE-~QrI#Mxn;4VM>P+NK zgl>xMu5E$7K)we?L+U;b^_d_yuq9yg6O|_KpH!P6djBZ2)4I^WwSWUR)XT$@JV3{S z0l0Ba=n{YVAUSjN^kx7@x*5xd=y`>%TJGcimm(lp=ZT;2`uk^H`nRR2u-kv0_3!=u zf0XwR#{Vn-82^8@+GmE@irDbv@PAXr-+$3Z_kWfn@CPA4-x7e^0=DnB1mI-i-deo1cFxq#&9RP4LzJ;?O^X@ zAMw$0`8RC^IlL$TM=^FT)BX787jpa$GXBQ@mn=-JTvJ|>J|iildD^3!(ce%XFFP~@ zwZz+esI;@6GvKFr3f23sK-Zun*>l(X0Un-wX6c4V)B=4HYP1)I1TrF~+Z&{X6%ySDfv$O-t;Lg%D7 zZ69wK76 zU`oGu2)Z6&P7Rlh2m2dOP3e)32fGe`nBbE(1ATxPjCIaDi2j6pqFFV$+7S)$K!M*w`U&v{c$k_ZBFba)$gNp_VI1ru-d~Qb|uJ8nuE&2p;fk&bJVk$pe_7qmYJhVst z8~g~qh5Dp@iinVTSet4H5(s#coHKs~o2+MH_1RD1o7fxFH18+a6n}u06b!&q@$0Bj z@eBAmV4u>K4?|3_Cn#Gp61f5RUHU7>AZO4~Sa{XvNEhIV@~QnC8IH}vOkYs=$DEf` zd1}de9zF(3zHMBm7rt>)pc(WR*kHuLe}*q0Kc z#dGm5u`-_AWfndiTf=6%O~yxKU5PYLWBgOBFurs{G){o6to`&JoFkbS*A_oPbWqV0 z=bSW7v{M-pXUe&cKU4ZMwP-5-h0;B$O%;XLDRLvy!3%Q1v z4UnhLND8&nrF^<1RYIG~;XaZsmG{gW!JRG(2Dkp0GfQ?3+`1F@sdTEs*=j6LDg92? zU?b;$D!oUPI&OrWB`f(xiw)5)B|#~27dy;cQkbY+dJ|hIF^Ffo8RH=me(YoSml#uW zjM=k#6c!>G8*8)~5-uW!Bx}*5P`lt1u^3v0AAtFSb8=8^ER5cp@@HC>4 zOHl8}A;@)^8hxW>3qI$DQ!`l|s}T7_PUI8*;g$Vod($?$0C`NYa-_)PW0;+u$(qxOv*VMFGT$Xy28&^47QRNzJxQd-+OEc!$=c!he zMYRGX*2eV9Mn?en~yrH$(zyW8c=EtCZG ztfs33>a0yZUd~FjvNgEPr#bb+*j3G}%vS_AWg!Ez;&R`eNs#HBzN=_7WrLg@h-Uy7h}grLL*_z1vikAXM(l&Gp%{ z6oK+j>85#d`N-6P=#m0g*+xliyisuXJV|Qgrnd(LaS9DkN zu`F(Hs!#1rWDd7l-d6Wia8R%#-MJwM>}D^^u5UV!94E^zG(GTRVx)3zX-PAM{cYbR z8Od#m!yi)VlRvFUYLUg4zR~(7S8*-NNlE|Vf2#AwC*?n!%+#C6uR-qt`BV6Kb{Xj9@kDkLEBJx2j$FM5))fEAALM~a{Tr}LzFhRKC!Rx zFxoS3Q_`HGBbd*^Ly60ZB7jE3C(JA|#GS3Ea&zrZGM)3eoKnXnF_&^{l8qKu(K~aN zCdpmIA@_5Zu(V6}1km%&a@lTSUu`Tbfgih{bBikF6Ex53i`%y|QhQcEwcobmn$pMX zpiSN`Lsgs4H&$getJKbZ-IibPKC7wza$Zr0d%J%y^<-SyIHoL8E#?pyJml5ZN ztYuS*@21u_G2IG_*Jqj@s9kZi_~+a=S_;WG^`5^zsR)$6{>v0k(Thl*krXceVu8Sj zFr*nxv6CGle%YX%-klaed>zP{siz)IoDFz9Tb*%U6co@i?{ua}bRwY5!aB1+G&Hcm z=7QRcxUp`o{UKE=@#ET3#}Vm=#lwA#7DEa%=?-tXOOJF^>H=@JTRZHWw%6yedm#V! z^tEegp5<(B_2PA4xWby0;T+)OwJ(X6v3Px(Pe_tD!(oH7-;*T&j0K>nT*G>y9vfV` z;R^RBRc$CO>Wtzu<)=|?@rK#@a@**7*6(?@WRs#xc?$}M%Y38cuvy^|>D@>`rBHB3 z`V*ZkO3hm?>jKW?lXD)+ifQ4pn;HG7uHkK};o`CBUBS+oQ!z7DDric-5&o=-+hm$I zQNUAYZY(L7#yhG0ZG%zq_naH*eH-MZ1m_!d+Xij9J~vlwzmcsG@z$v)ZR)SohsLQz zo5QP?A`exQLb|K1unbj2uup9YaxtA5)Kg)i+s3T4kScFg;Kf zwkEI2rlgrd?FlvQPR<6q?)J$Mc+R(W{r9Q-H(CmHukoGDjeL#Nj;B#lTdWAwX9~ls zrsmsi@=<`kHDA4vq0k-nzU}N?L-33A5_&`EGx85ycnyui%AmRS{#X>Y1bXbgNqQSS zFL&{jsCY8jcY_f7k?95dW!B!z9GC!FfYiN_m^K0P#;Gg73TzAyJh zUXYkGbEG;i|E~D)>`&#+1wmkZa)tP|pia^=uMQ0;ye;urcv@Huu+8Et=yYL~#Mvqq zDlfE!<8 zG52cn%D9i+Paag|#c*k!uE%_HQ~5os*PbA?*?<4DqV70($jN;^B{f^Mq1pRhmrRLa(~j($So%p! zKizVFeTm)Ho|rQSqKj@Pw8h_Tw#YlqGvz!wax<+WwUl@EXtmre-3Z!o?6I^vLk_Es z|1SPLON-{7uonkqvqi06KNX8J`^CdsQQ6e=aGCYVd1-qjK555JmKHiEwy9pX>Xo}i zIcK`Ip5M-*)o1s#gleF0)4X#n?%QP%B?T{Ao|IjQ(w2YQdLU18#LLSzn?h z%@l^8A?jCT-9f@1Qt9p=D(bdo#Um$w3a|Id9tWlV9(0gVP|JLyozQGvcqE3@fcO2> zNxh(qTMdfUqTz);BPm?`#Z8$6FDRO8%qH^C&83fL(^8T-=c;<@K6Y($DifYirB$)7CR+T!+k|sk3cNi@WOnHnP)yQ3z z^P-3q<=UlBguODjgzdH$Z&98ue(b(WvR}2ijOIBq70p<^qh~dd`Y0p0-pA{N%sJ!S zkv5-T={41ltEMoa+0)xi4;bwh=MV2FX{JzqEV6o3 z-u!d@ZA*4d$xS=kf1k>KqYbZ_mviibP9NU06_ffv*@|yNb1-tmK;c|tE$?&84{ESk zA7?8p1f%NBTZ07KH#~N)iO%5f2NsYa1N?FgIm7>p1NF*n`!RJScpleF1`7{LI$f|DUX6SJ6F&(lItP@o}v9W26 z3CGIX;$NsgiTgauIf<*D5i?I#&q|S-hO2Q?jtf3=qZOEDCqjdPY{Ur4gpxLWi^$;s zbWX$%fOF-bXbxc0Ws6RW*2N2;esM3>7snQa%T}N#V=*u+QJ}_gBMG0h`8YfNOouoB7RI-cvU>VDDgEI=-v}%u`VlpYNN369I>*kZV2Je z9i8gjuv=WtJs_%YDwV`>#jxptq0)t1Ht2UKBqbmB)*qgH8!qYQnfVa_xOv$tXZzC7kl39+;UC)CFIDo80%S_| z(rj&L75ZD|!8|r?2^N{@2C8%$YLRar?uof6C@bAvmX7SyMcN6v5 zC!vY?Lc%of6L@{TfG{dvfy^x3O2|uN&<7=x32k{U?wwyQVrv$NYgB7Q{gq3lYUw1< z1&ov3$9Lo1)g4kXb_WsO_N?BI6mfpROE)|cdO z7+{7pLmFV1aNcRsF12Tb+bgH8>FaVOeOux{QwXRsqYDtSh0ltyt5#Y+bS5!I}eh$ z8-m@u6VMoQlMUm411af;gjo@d&?ra7#|0fi7kWC!^{@9w>b$JtH?4apoV(@`_sBY3 zLCLyxiD%b$^2Yh)B#sFE?f!ra(3$&w@_Y++oc@CU)tusKOj@m~;HSV{73+#Zb!8z&qkau9tVo`PIX*w0hJ zZG4=Q9y=~Fku(iI5cW9ox#Seng<%rKkbfU`hw)?7jg%`<@$63#Zs|U;HwCX*Fz>_( zN#D?gIXLH@uspOb?`c?WbSiVsMo#DeW-W6|VK^&{smmHy{3)uLjj>Wn+Tt!G{=rmj zHA##SE@UQF_1$$NCnJ`YcSYp5B zcv1>7GR1EDJGfaqFU`6xPMAn`%bO1F5ImP`B}O*=mdudv5Sbh`<=se`Ez)i}nCO z2!Vdfq8CjEA&_C#lkA%y1d7PGlvXKvUNtH`F4acF*l{DRBRyQ$RsCyPPc{icAX?fF z`SX?To#W>F)vCZssP`Re`!)jc$)g*5jr+(u}^606z7 z{G@I)c*QEXhv6!^;cP<0Bqxd%dhWz=*=y(qrFctf5AfpvzB*$tUb5~n?wr9P#`#So z+EdSyDgHka6H-YM1k$)k?hw)Q&=!oyTrLa_i^tZ0`rt;y2=s@TMG`mmNl{gJoBULC zlBgx(eagbPtD^ZFEUk{a9N7_lGks1{E;1+jAuPwYBYIKyGExv8QW$BPuENG5I_%#S z&m}(zHzrKP8S(JY>+v;9k!C&$J!x!gd2k)TBX}}du#7*W0q2u_OLGklLwxNr zrC|%6NE+IATlC;;a;THG{R=#dtX*l~{5u{@zH+f~A4T|*zMgR9pKv5;z8pU`F`J|@ zrHmcKd%})e8g_#?PprlpIPZvR!Xc%D`yE+I=%n}ZKM+-fG*yNDO4JcS@`F-J`sjgpQr$q`4f3mAXKTCgj54$C!IhX-Oez{-#V zF2)Ao%_e&Iad0})4JLLzf)kK_n}^s>xIeCKe;HeeFTxF+Td@uJd(6hY4P%0M$H(U? z7KZC#Qicj+;G@yfu$j0oz71*MEW_RLc%*|HjN9WP+q!6nLegx&3g8{_4u z^!`WeJ-CM|>1~6finH+gm-W~QECKK6--{i>%HdeiVXPi&!@oLcI$ce^b!e;joQOZe z2T71jLUIj^kZ_5WsM%x=az%VEx!Xb?$rpd*_1laQ4Xwe3eD55-O*1Lq%*7|C|h z#(j}!zvO0$kI$ciA=1rR9KW9gCDJvyQpOqnJlWCQ(y+~ZN7jQ*x7VJf~iUAyHw z(ItFnnttmoVJY!IVbfVCS|To%cwg&5v!ri<3=8pPvONOn{jKV8eK?bwpzc+gs$UUSh`2(=B&Ogy^Z}ngW3XT;I#`fv@H2lpYARG3juQxx z<4Cs!U$9h^Bk8yC6{Lw=<=XZO1nJ0Wg@LoX;0~&tV&hH|reart1o@(|_~BHJ-$2xu zc#v8eHV6M*{2--)BgZQxg(*(CF94NW3Vvw`+9aNl(p;y93CY{?{-z)>#QVKWyTusm z$IYbrtp(_4Vwc3G6O;sm58-`HUvv&%Ct`Q&h!QY=iS)j^=poXaUDCT$^hBgpQ2%nW zNF@3$x1;~1&`&ft?eY7gLLbo?ILMYObQA3o0++u!;7YmUn>6$x-i1}>icjcdwi(me@?(yIQFX6OR}F>eXBqDZWPPt+~|@ zDUKoQefygj;yXldfOg9Y@q9wc)NdUt-hj(EHl1t97W4qzjE%+XL@PG4yJ^@vA#JDh z{!r|R5LK4+8et!V#?>m7rhe+0|KmU{B^;hKbR1fWs(85u@5CF?r_fz0Wz}dl9GpEx zDA1q(F4O(XXp@0Dr-$|Ht#?tA5qm6Bw^mD>$C{Wkx0a_dCdthdTQ}wMXY`ov*t)wg z*I>BWxUGA(C=D~rR+TiBG@BHg=@yH(bXx?P9xs}-x!-1!@$}8hHfh`Y&cC?DI?urQ z(EQ-?(hM8-SqlbM87q8zT+GNShM42`(VVPY4CC8FizVe}gr#AFtW+h#`3)Sd)r`&4 zc^zDTD^m8v$s}DtkCo{q2Nfdb7DSbRU+Qh%iRp@Rs(9w{*gZt4>^ENp zE}@$1uFwe*BX2d#u(XnuN&A~_St-&|Q?y%tUTBwVn5o}dYLi&BDA%Sl)OJzXclqAe z{;(Oh-7uHk{btdSUE4ECdT&_!>^n+!^v|(++JN#^DkIC5!*20vivRr}FJ(Pv($HdQ zB#&g8jEGG6$Un(oOtQ~&!e+1M&zM;#Bc`p(HTbi*NL;#3X{cYiO*(x;vx%bAQ63i5 zZ4p({F5es6Z^JCEmK_Sxws+oKAog8v;G6>{##Qb%?%cu){HJz4K6?3k5?PiUzh{|2 zaor|ThJQ*t`;2jE*n9C&)@YLkjydKM)JW^#`Ut&!Z&~&7(d0XxrC`5pTawCofx|?Z zEU|CdZYPuUqJ$ULHmjU+r*b3ByjKMkI>&mwLVeO;?)TYN&UMT{P>*{-zMG3Y$s#+AWN*K?_b9=(jGDjGMFNN1IOjO;&R( zOuVnX*&H`x#Bz4`np|}HLNDokbFrggkb6n*BE*(%?Ogwoj@+^gbn57Tj?JTwUiJ9> zuj0!ljczK{MY)4fm8Y7#Uz8oxnng3aUP3op_~)987%pkBIGe$kG(LNRm93aRV`JVM zDRUsUpoV@afevqRY>x3O#+ z_xW4A{Z7XF_&DVIL`~sy{DkRC1-}cWj1bv7*(6bEm_7L*XOp0TGaBDlY?s`@&BrEh zt4QqSvvFq4`}m16w#4dWH^(VAA!YJaUU*QUeLCazc_zQqA#2#r+d^`xe$2hq>ldt4 zqWN#0%?)m@yIO#}un4);@S$M+i)LnjlS{$zrxuagE!qX|AGF2lw+`IYb=^MMrt@a; z@zWcj|FbKM_Dv+&-99_D$~KbH`xAC~<#C9TUc2h}%w;gEj;{*LT8nk`+mplK&*dPhjId1F=jsf@RBTephNV$?@ywo=r=aOBn^6@br?5(lCSY#Plwh zho>`Fq@OM5;D$3-r*A1xVMJtJdPDX^nJs%``m;=v^j)0&>8mrHa{FVnGM;1x6((@E zWH7S%rSXY>WW?pP1JGMK`P$a!?5`8x9D!-(63a2$~ z0l)rb1m{B9UFh~2da2vx-<4n#Ne$H`buZfKGCk8|1U={KlJ5l0V=oah+A$_$(*4A2 zj;8R5xqO|@3#8Z0M^Es7kKhAM@YrGsm;NmaJJRs8yH3)c$Y#gD@JZCsC?S}sV? zw!e3Z?)n4FNrQJ1fseM@xIeDo1pHF%<5RtlpbcI}o6O>2npC-hr?tiHlNa>%Chb`iS%rxV`SWI0E@8&0#kGbOg( zdyY!)>mujYt%SMj8OGeeE~hx{4lVZV~mJ-G(K8{U$` zt|$#%*UTlav^AU1S1-cHpX;{x$u$enYwx#_udw8wxB^41+x3J!*9@E&gqlT7Yq4>E z!=_qhZ$k0SBQ8@AR9$?wEE9ee)1Y z?6`jG-rR?)tqTd3~ zD05A=q1ISEF-pgA<=Xa0%ru5`k%9B*m74|4{xDx^VnnyWGjn zca<{Uci<;r_Ee>~PqDyvIK$1r!Wk2qX&8f=RkoO=UR;e@$8`6C= zy1A~F`)lXw$Xg9kB(8-Y(%;mJI~@9BgLX@i@MK-xdi~aMoCh`10NCNIcimeO>V2($ z)xi4o5$tZ&a*N&d5z_mAF7c`CjV$Rsw$!Hl3cLR0<`uiD{9-!#jXW{ssrbk5t^Bsu z>B5~EaY$IbRK4z9>+I#=?i%Q|)_t`@pyuo+Mea#Mji=ogcCb_`O{iwcXZ;feFj)=s zeO9!eYaDEivbp{P;<_zvW+rWjhB<9_ru}-ocpLZdOe>!>FlXtKq3|?G;P{$AiI41TQq18-f;T&M*F@&!K8ZsRkzv z-I!Q*Y$t5|IEgtYM50^+JW=a}T-0qbIB9ev9ICY$nsoR`k4W48eqvqY1(B3tpRnzm zHq6iy5`5a;2pc$q5>#z3gig8F5{91g5-F9_lDLOPAkB5QNhSvkkXsG2k_3lFA^lC; z5-pA%73sG==U!~TD75KBx%V&T3car#;Iccn3#Ip;C!A?66_xY`B%V6@yQu!Q4? z1w=yRjyR`WbNp=-%FU^2K*_iR@k%9*O5@#txydLi{$#?fhAi}CtSzse_7gUpeF?JyS7mu$i;gpAD-Hwqf3W+ga7HIoKUIEKIhg z-)3Y`E6c3J!1;V&7js6LjeB+ADAuELum>2lD9oag<2NaU8BtVO8ul!5W{gQi17~4$ zSX^*<2X{!UG=6z`FaK?fF*mW?Bt4ijn!C0H{QLRUwV(TD1J@3Qn@sGZemYa zb6q1xnY5)0x;k5*msZx_#Era=c(hErD3K68`OND{^fws*TFCLK7KzjbT-RbsZw1JSsM0n zyP67rx3e@&O@{G1X6@yXj7g7`iM7cjf5zrrk7{B_SPASRYQo8Gi<)ggHA~5U8}Ds< zYHUdmYHZ(D>r5IrNA0NG8v=``t$Qku=!>Nci`pSaeZ-|suEBmZE~q%M z+hQ6~&8Us-w>eB~VVsH9wjW1U21T(AoDniE;AyyxdyZJoF9~+vb0mpAb$%Q_D@mOz z-&4xCA+=hO<5(KDAO#l5whbIr+GV=~mK|K<^s9D}^j>~i+Ee?1787NmsYe~NtWDD2 zq#RnYaiLSLGUb-jxrO}FR|-QHLu;jSx8ktJRM^zCC`PVczVKGV;FJ!}RGa>$&=gm9 z*QMGmV^hwr)OXZxoh0jDG0xqlb07KBN>@McYvtsht9!!O-SLvH^@G{c`vKCQ0#CyX z5F`y_#71=VPnGNo-WU!Zyvh9F%y5b>-#*_c@Lz`ll>DFi;0;DqtUWQIi4yg`DC@)GBqOzt2sVcP+ zo6?v~(XWD!!o040dDD6dj0_{QM_JSQ@$22k(?LLt?j@ac@gAE7zRW2&&3ystmj>rss24I& z5;c!5Y7!ZU1MFQz-N-Q@W3JE?zlVDS1PeoPExa|VM8w6pz~$)(2X_W8??-Q<`>>kw zt@t}60-sxJLo7$^2;eT+hXex7F9|(tgjD+45!4_z!Hzu*TZ>LgQDg9xD3yw9THRex zWJGhL=Dkloi0?~&NM9~mOw17l*q_48rHkcV9+=oidL`8(;FK&|x;U*h>WRESs-I@Z zKO+-KKcqIvOr;kko+n-0#J0?tNBNr`VPY%!P`opF%YJLcC zf01i+LWuPB70a{S4@{Nn3e7`sT0ISZb^iPdnp)Y?qBz^AdDAn5MaGT~=^0r@<>jja z?2Xf>myP%C@^DI9R=UpHBVc^m#1d_<)+lLeTJaz+JHBtKY|9_sO)}$Dv$E0a>G^9@ znoEE5t0~V&bFY}YZf@=6^r7XKSC=(;XWA6awwu!Cozq`EDCHsb^^`_ThRjNen)gwngC->g*gr|lh5Kh+ z9tULva#`#Vz>~qzGHGj+kK6%SB(vi`RM;S$&{yi^CWt{wP5CnUGUT9qZf&W;4mkkb zcV$WlQbkT_o1MW&!eGQ(k^4^MBGG=7oHGo$Dwe)Jl$L{Bf*uhsIf@+k*CIR$G0l+7 z6R*K({HS?wjtftO&MZjD1%Gw{_S^B1!r5tE9`}<6@N3gN0*sS><-JR9jgs@OCRb$G z@wf2{_*c@KWHCsaaC`A(-+hc$AV&ka#?K<5jJ``bq4Iq;(9y=wMO+0B1QA9X5D z+7IK+`Sa(nlbKx}K^GcD?~6PF23^=D3N3Dpn%SY`zbLljBOMb(;YCfd#10|(Fpr*p zrDMD#D!Zn9#D&5vEDdOR-fUBAYFX2)Hs>wNdrnejru*40k826nGQHY8 z0`4V*WFETE8kLgNnpWOv$Dheto3^K;N%lNxTKcZ9anw9xbf_>1eMldGjS}XVkJjwC1jBW)=+d5@cv;hr$i_W) zVUduG_tv~d^15{JkjgWF z#_C~$Ui1~9>4^S+cj5g=+>Cv~uQ`)ZMb45Iu^;+>6U3fQ>gOxS; z2rxw#EE!z+yND5jJm@Y@usJ|JGhq*A1BVTU5r=V4b=#w825tki%;`Z-@2vFIn ze_O48u|}cNgqk#sXFrun6Ml8LYv@;51mit1Bisy}ZLbsE!H)@Pv@OCxM6cj_Ws~qv zTpPwMO_7l0v!w&7bjrfm=eG*RyMoWZErLTcenG}#$uV!|Z4!+|lR{{;!6FiQ;$Kvx z2`Q)V#Yk7cxEdePRb&7ovsoKSM61J2zRkmKgK5IQy-IC?fO^xgzrATGafrMC$Y#jo znZDo=yi*>Qa|0Ptj;53h+t-suxXM4gSx=44U_9JShpr zzn0ZwkC3sdKQy;0&Fg;((U>w;C6GI5 z;$Ld4nc~0Dc6RCl@Wex-wPYlc3paZnnv%gHKNYD|n)EMK{E|=8x_tQNa8Ob(ev@!5 zZVZml1Eg|1M@plekSF8YpbcokaWzq}hSHX)R2q1yy#J2Rm-#;=g3FDGr{;eXt<@f0 zv$&cBE*J>6GNCcR51-?<> ze+dQtP5XadPVOGFyquPKET1*c%V)jsH~WA8uD9|3G)LejUt0jT0yhq`jLprp%w`!G zo0)2v&oVXz(|N`wreGP**u+B1628r}tl%jCfrUV06Du$d2vOMWnL$nqEmN8%5we;^u0csRRI^X<5*LTo9vEbb^4YiUov}Wkm}*fIv|e@C-mK zXjGbox%!(z)C`KVRAWs2P$LG$+ARSAfVQGCf$2gkigKm^V5K3Q1=JCWv4+>u0NNVB zfQ?XJb+s%h1b`yof`h6agleu*34n_^Bv?R3Yf}iJIA{x&78C+hIy?u8Sz1B?z(Y%G zAegB&gj7wyhNc=DD|3pczy))vDM4sSS&6p-rWhGpT7EGX|82hiUou>Od+9d{{12tT z=ly@3QcI)%r+!e08e7KSEunUO|Ge@??REC5J;K8d_p9Ch-f?y4C|m{Gbxrt{{X|Wc zTr9Yt60&GQsv=+6E!2SghB|E73Lu(L4f2;Az|Zf^3)8%p3e^zm-Sy|HP?(j1WEA#YM z!d6cms=@xgo7I557n9=AHK5^EhZIK%s#Kbg zO8-|Lgf&HK_ObuYepUnedMlT7yslDdLUoz{I8?2H6X!oHd{Pe@Gj*tj`^TYR4V-2l z`t^SFZ=N?bKtmLlJ$2y5mnKvf_Lqi+g3v(SNzOP!f(S+vs-eDUNWOp>VJkvk0N*sB z8tgAk#TTfzvDk)n0Hq24dgYgGx8|L7vYK-oF*I3q_~o7dgxf8jw00D1|1}{6_kY6Y zul;{WOk#Axm)*Z6N~4Dco3kvLMrKwaW=0kkmexigOolbMgJYVSSg|ZwA(r2~dHtWz z48GOj|56J48~cBwSP(e#Qvk|AX!V*bMIHm;c9K@}IW-r+vNsH`B7F{CvR{ z?r#pgXtOLV&CNqX%|l_-Hm4b}n89=-Ce0E|?3*!|U=yEaVn+YAk^8^2=WD9{dH&Z@ z`~CEei4IF-x+f-#)UpCazXd(S!pM>ZJ{f{T8D>WGP$nDzFb5BD(1xr{gQ=JasS(S9MKiJt0q^kE7N)dN+P4Y)e*#m! z75R+<|49`1H|BrWSuXBtmd{%5yWDQI`&#uefYsmT|Nk;Bz|Fpv0Dz(xT>XKL9`xy; zzy-atDd_cKdj@KK>YGwYn}Qyk`lh4=rZlyd9bl>7YDm!0Q@_=aprNOJtDOf@>T$Xj z9Q~q_Ks!u*t23B`MjBM~7HTL9OX`6I<$&B&?O5E@Qk`N2hYaA10qDBb0c4?K>X(3G z8`RvEa3}zj)SzSsB{ZdDr$YdmYbv0yumsJ&5mXEm)}UpFxLV0>sn(HOTKz-fVLr<; zgh8_oHV=UvtEsh-xrH_5S&(IE#ImNbLQSX%%QE=ey5`H^@>yy2-FGMcI{#}Q&;Q%; z-xEtI`2RPDfS(mEdjI0Aj#9f(f0RcVy@8rzzwO|LOodtFW5x^t_i_ty+PGnOKb-jW znJ^N6j{Si1CQbmib-!UnlV^dOwsx#>sxf{IE5!8l82D|h3w1Dv#-G525!RdpxTu?n zD&}$F!fzU?G>nF@5N)5&f{VTT&^}{-2ydV|rfc9L@Anvuwi-9Y7h*p2HDE#02IEGpt^QZfagxs!66vj$Q?ylj=oUN8C2o48ZP{v zLX|5$;X?2UwB2Pr*up%C_PJ5-Z$NcC)A1K@zSPWXC*T=``uNmAy_TT7wVT1M-ZHev zHwph0TaPySt%Ldt(H?&vC^s8b1@ggdT_&mw;E4Yk+)7sLro@Am3s1_Q$QbC!Pta(o%daz6<;6c=iu!_GwtzK0<|s z3RKvbF)Hji7^n7`pu*_j%}6y^`^~ zpwU!VGZ-NCp{uX~5N`zkg;f&BzeI%<0i41~g&hGy_{xAbb@59K%d zsW3UjRe>tZ42)ao2CJ}}B;*6Ta0u+%9arCGlq0PokFz`UdgjmVUraa6vgn|!2#MYgcORW!QL!|Vie@J4^?4r zQ=l6BPuN2+POnmpC1|Ps8m04J$Hymqg$YH(aKMB&QH6OyP70%6vo%CCsnB5Lv0a6+ z=78O(9U6>8j)CHj~slsM%R$<<6p{ARt8UaPXRv1*_E9dg_zN!&xs0!Pc z2qi!>xfh~oCW*i@8Ytq|`)ZmCcu7*%xCY`ylU3M+4Vs#N0Boq{1J{_<&1W@FQ}ZEw zO*2}MtZ7E?!TTY_o8(9hC8#*Xqp&y)MJPCmBEitidTdo#5cFfJc}<7rL{TINp0~R| zO~76xMG*?#%nO<;JooW|vY%@YAII}4 zX99WyHOKIkS{=fLNOoHLh%=%?qP-b?qZXk8kwMm&F<$s4QE8UXgcj*x#36$>ag#g% zeUVx;xlaBR{ZY|4)l3?K(WO1p7h+GbWu%@S!ge9ie?GTrkL`zWA;u$h?P3y6Vh;!hhXnpjOh~XCHRULroY804AGmVm zJ>kz#Zl9Ybzm8`~lpI4yGp}jvG>l97_-N9Ld}kz{!*&y@z#}jken;pAClSA3`hz-Nw8RvyfaWgMYxQ+8{0xEBOk?;fp3#`_MdST*mkln+MZj3@yQ-UO#F%k5xS(U zi8!9MTCaC(=Qa+=P~a53W^SW1uC>&oL@wmrnLaO**QY+gfo_L8TIMsV)BDHNv{ zUXSkc=*t{vJv)KErm=XH)n%Lr)~$%KWJ?lQg@@ZrJ7ug0xZq*7Lot@E=FHy@+E}UC zqPZ^OF!)}=Yy6@=YiKAidbk%HU5e2{3ERF=u8~IQJww{KHs)Jw;dI_am*8wNczom3 z?7%hBaie>tuM5~CV~a}>5`vXTx^tFquHP0It zSqIEZSujGiL=$npDz7(IgJsEI3_+ylYqnYMCeYv6wI80~hKwSl%$f9*au&2t@3 zr+~ML*&~X4Uj!VG?a*%YyA$w8wpUvfXc!QZvUr4U@Zf-FDFGwwL-7D(#d~cQ%OvoO zG+A2_o)(xYvDVg!)5LYj{edd+Ozrk`b4G#a#HhZ^$BbRVj?p?f_8~Kcr^lG(&0xM4 zoEYz2U>CMb*fXi9C?#qrvc;gMs8!)DfhWLmnQL4X+K0)KT{>#lNWMSs(Ufd z1q5|~e}74hPJ5{((tSvHqFo$oW@&uxptPbU^l_EC;w zvbl{@D|aV`e2VXxzO1%_v5>2$cke)@|8I#7200A@8%mQ|bIv!+-k>c~%yVuKZWsWT z)<4wm+u#K=!-#{(hLb2$#y&)Ba7Pu2cnDolW$NXF*aiY^PuIZrW$xIYyx|sRmiJ;; z#)g$-QPJ7b;r>fxjm6%@*8WT6JtcjG{{AoJ;Oco3+kc@#x8l^M2!CsZQdzgz%->hu zzB_ocjenB7uSNr>vv=~wQvXgx;o)w{&cNR@8XJQM%b)>SJxBMW4MA73RL9SV^cnHl z_NQJ6jxmgK}C%^siae9Z}0R$LI4K zr~WLMi+c-trk?_PI)qS9&nF+noMFOAFABz1!-TVOlM|K+j8Nv;VCi7E+$e7z_C(M= z-#UK`wpGw)JSuMrMhbLHpQMFh;Q|`X7p6@ofe*cl=Y)p~c$UuM^Vm~Ck+mj&S-VPooqLjC19}zW-P{B`HFDooY&~ZWMyPr?sHU!-_Bm2GY&h$H{;Gu*@lf4@p#)M z=P@@?4?=_O0Z+)3H6cUs1c6G$MSj8dYpmmZo`}AjKU=A0(ZSYJ`9Bb>p&!pxXCVsc z8)xJ)uYxgS0#BbJE)^F|j%Zmc(y45mx~zGEVEXo+=^IX*OUhH~>7^afi#BU;FbF;L zQ`ER4F!LM=i@bS6F|X&aFzU)7rJ>c)4$g@KeZ~(?4d=G)(lK>Dd!2i=nnv4oHYonS z(ubaQ+AH?p9-ig6lUri$))iTAYPuH_xW91`_sH1T^aDM%8Aq_4DdGOcNB4C4UH zv7mWaRN8)pQ+SJAWY&JzF*WBz4mi-~#%cVObFN;;bKDVm^!$b*Uyr6v_UML2zv;(n zqW0GJ_`g0G6481<73gx-isiLmH+V$rQs$$5_Mv2pI6U(Ji#4)w9Y@xn2tQE2CYC&? zj5Ii~Anx43cJ``zJht+n8F$^$<5A8HJf881hY=U+dl2iB$HITF*U4FNJfCTE*t=lB zvGfqz#-bu&t!?=IW$)1u&4V?8Ir*tbe*Ir+p(=Ok+s={~0$S$SQzjUQT z{LS{=w#C<%8Fu&8EGrtnlzv-huVmAOrMG&#>rdwQIt1M>JUpOy$dZQc#>PKNeqDU_ zrs}wU)o6#t>$=Uywk=)0_nQ1%>W)6g(5uyL8+If*#$Ro3mu(k1oWI)FajQzXjCW1v z((;PIOG>YsUAbSn*>>X%?`uytXDq(boz&H_k!iQ=Ug1qNPw6=GVNcJ(oZZfc9;@!w z=iPReKGJ<)UbNo1_P+hYwPk0Vrrwr68C>_oDYB#b8QvgwUUJT?f9@%C&4B~nZ#SK3 zUq8S0_Q#U*SGkM9GJfIti^*o`>apM-$3A`4?aVaQn4D1LF!)H?XxyNRHPkEf12Sx{ zV))S7LNUZN>IW5#^i+9!JcGi}`Pil2~=PkbhD zDnE&8UhpO|t2T;UUKLHAxXoHL;n&8gRog5@^w&Mp=Wl;0I{#WvPiw~o{K;zvgQMHW ziU;>A4gaXjP)vE#KL2`UTgs$2ea43&G<~CEx}>s8UiF4Xv#l5{34iNDuPA>)w7un7 zI#$pnjJHMB_LaBbdE=srO8XSAH>xF!N*_ha8wUrh@{}U(4a-rJU*VKl;U@j@t#ajz zibLYWx9u)j6)UBNZ~NRd`E@*Hl{XcfH@vmxm6`H=Z&ZQPD>LMW-zdV#ib)dpw|&ul z71oluZ_T)&F8Uul>ZTC%`y)V(@=Lg<2+y9U(X=mQ> zt{*Bhzi_v!@bCfI#g29O#$%_9dYET= zGT*LSt+@DSg`~JsqWlMGYX7DY+i;BbGvLu$M7DO3|e)emJii;+Ptcmo9 zA{(dT-fgaKM9=hjULC81AmT_`EnnFy($f>KzTUF~ zVR25<|s%=`WjATVqI3`uCf4D-H;%bMg!9tA3BalYcRb zx&0{Ha^vk(`OY80-8bsVmCE+eeFXwZ`|kSS%lWX^soBLCneR_{*N+Q!&3lX%9v&W? zly?nnY_wyp%D+hV99_&dD?rm!#~b3mFR04XZI&h0s2wpX*Lc%6HvZ z-By`Ul|LfCy?svnumXOz*_FaDn}RT~0D33%W_~)J)I|&7=Pv=t($rw1{35ic$Ar)3YJ4*6P%n7{6T1RRXrA0I@S|QbooCzD%Zpl^lZ?HkNmH4sm;yRupscF<0 z!pEmr`aZ${HlQVvS?t%iBK)d!YNQ@<{>6Do3j23BV_qa~N{GYNNdE0uJLPgjZ(xMT zGiMnrXCI4Bq;3K!+EdZx)O=`qrJ@;GK_CFC6*?AQNA>h%f}eN#g0OS7pnR7t2s>8` zig(Wh;bxVyANpe4jKc;g!vx$Z%odn534hKs0wxW{k3=jalq+*^yNFF7 zMF1&Fm?L54^%Uz2H3nw=f%%8p5q-7>-Ks3{;vWC{jyqCr+QN^mH~4n&?m^B=~Tfz-}KupnwF$fyu<3 z1rZq_=wF2Uvs8$g*GRlB^cUz$gK@jiKcFua;j(Z$5afTrU$bJNFHOO9LkEE1{x&u$ zvXfp8m z{DZpx_u1Od*Ym)r(Dx|F7vkT7EILdDqhJJumTnWsz;PZ)A!K`Uy5 z@E?pZpNq$XIomL+-Qr86YR)km0vkeX{%*oDW5NLY<0X4}J*I{EoNRU`(A!c`@npBB z0=rxsja}`5zRI(XKD*{I5+W~%p5bSR{hp=8O9)KIrxtLr^C7n-WLmaFFFYpo6M0I^ zXJ5&P7Jet|j6Io|#S4?oNeoE4os=y__(|BvWCFR0jE&0^SaS`@F3uu>6NiwQb58QF zGH;}=jvkA|1TM%D#*QW2H%Rj=roGVWDP z<^PygdEBykOcFz;?9bfOO)60Nut$62$eiR84mJ&|knfSDCAmjJMDgT-r%#WyVS#wR zYH3R?x&TYl*>v_aJ{+^Qe0e?)cO~Q3J6s$jiOt|A6<>{!JTB->eSKpX@hQtB&#A|O zERg$UZ@u4!yJS}>etXo0tt$v7T%VbUN-|amEBi3s3Yo9ykJmT3Uf3+5=Q}~%X8tk$ z&JUh(Pm}&kR;kQ*6O#-@8s37E0{-?4|A21B=i-A$j=&V4i1iyY7@LWwV|ynYM9*RR z*zhS%*kSBF5;(&W%!0lWHR#>NbOn4GZe1@ewqLb8{O(HF#SyxF-F3&F>tlih}(k1_Y;*ww+v zNh7$I}Sc%m-sMyfklfZB*H$oh!R zLqaiW-gwLd*@xcVbP-#ER3fx3JZ!h9SCm>d3;#t(L+(|vup7uH#Ijl!oT>$4nR{*_ z&d6T;(Ox0;LUe_&X)wg^qb_9bkwEkmnn6B2b_%UR_~g=-C8)O0ncQ?X0lg`BgTFj~ z5gRFT#T+i)!Ehv7SbTL0vP3i@@%4=e^pfyFj8jh{_FO39Y`w3GiiCG#e|scE3`7eO zU7u}7FNsioWgmha5?LXCyza!_iSA>b?Lr9X`c8d9-|8M&OKMq+jZn z)u5M(ua$G9Bj%ctdt^TeLw+KNE?H&rkp*c)c~)K$bYXE}%CO`xt5~cj2gwuZ2k)g-Z(-%t!r{f*!3JSTN`c^$xIm|xRH7!-6i#rXhBwZT6YRxD9v+B{cr5O z2UJu^+wbd)I3UU}7*J6W0~kq?6%`Z|R1gFNZIIASZfNM7cIXT=IZDogC@QEZs3@o) zW{lvV!l)x=MMn%X_O0%Eof-Y+opa}|b-wf7@5owE>{Pq7@%4XRnP zQd%2lQ8X-YlRk(~D%*k)WCPs(s@=#&nY!3!M<*ID){>>xzeMh)J(b;W&_#vvdbxd5 z1pi|~pft0^S7?^>9DUe2S^QA8NbGQ82(_2~nvmByk$p&3neg;%CL^IhiaT9;!CF+i zL{xZn4ev<(T)|LJF*3A&2kLTbFLzDTCbV?Gkz2ldp6Jc}EgVIKwb<>Eh_|?$AgX+} z9bZ*=5qm#$LgKs4Q{Xuqmz1AtF0B0^O18`{1%1OU(xD7Bk+K>8*SKofw@|j$@QGF^ zV5-{XcX=A#42MO|lpXYXjg!K=)YCC)TDV{n=cjOjPCtGnk`>l4F-PP;`bgB!a}&*s zyuCG0|CJ;#N)pmEizAgrDr95ldPsKDv+9WpS4j@A&yrhAy`+0sF>X4R_M&iPyd>QA zTXYZadqJ!H0^w!0v2>y%0a?qt??ra*6^O7ATiacKlBLoTW2U%2N~nllB8uA>AbS<1 zme9FruZ&7Im*^AT$~=8%CeZ`uC7OAim!BnmpR_q3UOFpcRO0j)J28ixktCzt6kUun zPp)Bm3yc_}l6G-&1qkPnfXkmLzKTfr;W0lXFAy$5!l{~>0%-_J-8) zrd1;Kq?v`1tWkVqpoi&! zXmOXp4~5@npHtose|ax_Aw&P${XTi8e;*$Y?;Sjp+VP%PuhGa*Z&BRZnwK;;6%rmb&zUzT=p_e8xgmnDQ%-9E0N~4zrnRH@3&B4(MxxW zqGiOHHD1J|GTX4;Z3FWDD#N5}_>y#+9nHHf$nCt;`p$itps~B((A%I+0AKk{gr-uz ziOiUT--qgOV%32Sp}!>MbxuwD71K+9dUj{zrL2vJ)1@(_ONEQK z7G6CSJ5aRMb*RTKR#ts(jmxchQQueby}|o}>s>o`%bWX4sWJ8MLfjr5_MK2~8&>&j z)e6TQhoavPJu!cFMt!(7Ze!gxS2`uKb5mdDL4kL) zKA}6c5{ZhZ2YME@b5n_DiMvYG&>a6+5rndl_%44AIizH>@K?gcxVoYW>CCN0j63GZR&kzbbfBxQ7|V{4a~rtRz4D|KEb z$s(TL!L70C%L_l~!+hmyQ6wtS;G0D!mHAdY#kwN;tKJ{f!B_j)?09yv5n1DwTEF?k zc$U_N`wd}j8jOB#`=)EH*_@RdK~r&jH*>H1!&b@3H_Sc{hZA_a2HVCbuQTNAw?M3V zdUnp~2b@`JoG!gNY>$riD7?C32SG#+8tRc%ZcR!Ib-DGjj+SZaS2{4OIVvG|dgW@?X&d?QmF>5~ztfZkHY54oZ#V>`2VK=%!fM=)nayq4GyM{zjhSP@EU;#`c| zE2s*;$ueTh6qrRjauCidgiASrT}5t?QKCQS>7T{lJ8uBpgvQ9;;ODHKq{))q8$Y0? zY3F1083wo{OF%s7jgb5Dq$vfPhQt;{HaLB)M|x6Our$-w1w7rO5>EvmrP=J*C(jEW zOG~XE&h#W^)9*Lj&O8!i2%hc*iMD}@IGHW`lA}V@X%Ab&vlj%maU4!$7AOdT{Jc*4 zv^4>-oTq0S^1lhY%yYW5a91K(9V@)b*u9Yb1auQ*b%$_WiOa3_{7bBh!qR~XX%OuU z_s#v&Qdg=1aeH(k;Xv>Papkjb<+fWR6W$LUN&?{>6FrBg@!znsu-XqkcnwQA3C9_s zlLnSUey3Qo@nYhI?bHn{j zQ9K>X=WE|?)<_PwHFG(%IXb7+{;oq6AtP;~xoFB1 z_r$#N!UenIHa4UxD)O2;H{HrpRIRDkC#;tKSmU#u9(X8yb^Y3+v&0{Eud9E!4cr@D z((0N_4q0^m*v`2{7vs*Id$jLBsS)Gb)AJ9-mm-|-qXF#(g;$Xa*&ZjKlr0eMOT63g z-4TKGU}i=|^68!=3a_DI!fDgA{=}ubj&(}1W|wO3iai8c{!(%K=_-pNN~Pw3^;t<} zlO?|tEza$)atx_qtt_rL|RH>7PuT;dDto96kQ-Egoxv*n(j zrTdBUhpk=SrW^QW4kxy6^74F}lh-*!o)&x@d?|dNcajy6=yYj9fmZTQrG-~-C!H#& zXc+1l%-&dV>WIrNtGx7*pIS=?tObeXTaLfEUm%}co^sgjQBdiE%0&k&pIPlP+mpWQ z{m{7m1_zr9J%=myy*+XyqxOU5e)pq2DPW%7BjHC&)Bb4rzpU?|#ou5P+v_!^+6g(a zC)KpnEl%^sb`o?RE*UM|alB#T&x`A^S6v!<^~-){R2>Y|CkM{r;Wz6#@e`shiMeHezJG*r(6%N0$^mm2ZdN|myP4>3h+bkW;`mkrBqxZ5k z?7}*-^HR$!ZpglN*Qralayt%Aai_bH1PwKD8!`XIGGamJrem8VnU|~d3BhYVB=G9# zfoC1rlH>c%5(n086TYsS712p}hfmMvkbB5?@yw))ai^lMNj}Pr7`r1pMGw;uj$i0? z9K1*oSI0e^vhxdsr|dUVWjzAv{k3;lE4zAXio(IaOz~EM?p}@$_tk!(*1L z!|4+0p+i^SQ+PFkD35=?ZK%gA;ytT9*X5QS#Zho2vvlCcxShz2)HnCLNI&o{6}Ua} z3fhdAR8~HlAC!T0y`BHT34OAA7t<1NFzEF`F#d zO|yPtCt8`rkDY79N)Ap>CN5k-Ee}4J++ykw=4tTkY$} zu7O41tDq?KM?$^`yz1k^wsuL|U8#Yo5&MKw+^aWCj6a9PZJh2i4W9wpe$Sv+Y;T@E z!JNaOEs)Ryce4%R=L^mfvsm?8TYx8E7xjlAe+Gv<7Bz_F^DoBrh(7YJVMdHi$jf+5 z1mQdvjih406L5apc+@*>fiOGw*#=Pth|ep$vHp8$Pf~MwWTbDVY1*{xUdOrVlB^#& z=iR(HeR&G05oJ}XMbQQH75Q0CQkiAElte4)uc{1B4|$kpv*SSEd%qvkQ|l4m=^G~` z-*0I1eY%d7Y~NJ0#oH}cp4oDIYt6cI2@hMZ`POf6mpGhA-m=8&S#nav|KT@K_2wWJna9TQ?1Z=5jHW4tMg!p2>06*}?hp5oGhS2Ph>lKkerBlBr^ zN0Qs4e1>}1k)+CJ%Q)@CB-#6+dA#3(X}EAQV|<>DB@wscq@;sxJlaEd%5Aj| z5&S|wm_E_*Ew+yNQcQOK70nI}OlWtl7hPJLCz|3uEdF`^&%mYkQ6!mAk9KZS$JdQF z1O9*rlrU)~j~@5}Jw0bS_bgEhQFn|2l^w>8*!nY{Lk?p8Mos2jj623$&R@(mV!Ys8 zL96%(=Yl*%P$RpFn3eW1opTom-&Q8jUgQd-Q>qWs`|^8|6cw%1D@CSh?PVRYC(41_ zr}RpMZ&_d7?E;Uj2l6b6uRXSB=S+#e@zT*OXv zdsLtKa^<3|%4e&K{Je}a-w(N0B!@R;cn+ski>bGBYCl}8?qtN|fuLLYM(oiUs)zeC zSrXRvEab5C5peRoK>RYS_-b0cNO86XkD!x|p3Gd%YnZqT-IJw(Xy_#)O*O-uKz(!c z(h*PaXy;%}$7JZ(xrgwbdmDwsg^vY!B{TRfreg%xv#j_!mfzuJSueQZwoRCCx(Bb- zeuRLQF62&hT#Rd_pGV2gzM_w{qxtQwX$a}WLGaPmA!_((6DMw?NRaZT5$oLKCK>bF z227vut@!&lMFM(YiD=EeI^e){$BfQrgWCRpSni(5K;sgfPsyv{y!%&rZBr)SmB zH>Y%DPL~XL2YVOG3a=9R7LO>2Lp?w7ZV$)FTyCvL(?8gYN(XLZCtqoS+Fp*8-(|_% z9?ju6TplZ}e71zgKDHHoKZGLHWxsMghX?uJXCCC$ewfGC&)5kfcz<$f zlGCLZ{~g5u%{={uMDCvW(C|5s@#(z2E$ZULIk@a0*<)P9!6o zu=oyClXn#4IFX&N^{H--ReJ4!Yj`Zl>Q zPg88P`D2nr5gW_hI2SzJ50SV{-r(VWhP#C5Q*Czm3DTmzk4vo|jUK1nj=bN{jl5xT z$o5U1f_p5B_{DO;5(L5jC+548W3zwiq{=F2%NX} z;-K6sz>}XWNlEWVfEXw1$)I^L7cD-Dv6 zon!F_dj+5^cNc9u@5`Cu9w`QLmMA_qT`ESPvFo4R|r-N z++?36n#x9Aq=B~lr0`$^A2@Fh3pqJ?z^lQ2<5aTY2wxn4AF|JHBGvN{FrkxR{ z$OJ&J87-WY(IgT`=L@T|2gN-}`2x=@HHm3jkRT^>sz{Q>6r9;MN!*tw7yh!lNorBF zM{v6Juq>%;4IXi9l%T)L4Xbbbg>SQC7^$s@6Q;VM{}E5{^8?K7(K39}6>so~-YYO_j+VS1 z@)LwtFeIMCIKC!(E2!?}`1-V!pt`4G>G{i`C;tn~0Y2Uu;$iwVcD)8&bTW?4RMYas z3u29_1f5ejHRjuR(3lGs#ayCm=sgj8My>(%Jto1Dw2jp?D=H~AI+#Cp?oi5G-a3@H z@UY}3K@8erdJEwf_;7VB%aG+6Tbbduud$_R_3T#rJ=nyI>8y#42Skr^W`W214);c8 zGkClU@oZ@_bBenIzIrR0AGh%>UgG@_Jl;PDS8T9H^$9;96TG%i=z;D$FTcAHXNl9K z?vX{TSrNZxIMSko9CBppd5#89Zl%n%R@|E_tsoEXOphFF8+~n=8rEOYRZ)W%uRjCmx|ulP!vxrCs5_ zg2tR6n?Wo?`>P6NE2#%Dn;q_=Z&R9qv;HOSTdvE!-|(9-dHY$OeUpXwOl1Uc$;?f* zs0;+}?kmX#C2wU8CoB_>SIq(rSL4)CB^jWnW}oGsT$P zXSU46ha2f~>!2*x(}`RPOdhyVX?X%znU`Ty7aUtebc`TtJ?0HSLs=l}R0 z_V|M^8DD4r_}}vUf8zf>oEz()I>D!Z{*M2f3HEGL*JoVkjAn_n)#LNHmNe%{kdsrN z0XexYBPAyn6DT)?#sn58EZN+l2E~7hGEm$KrNyWa$XJKyE)tEI-LA93$fj|8)-YL)&>hnRKxQ(!1bP0Wl zd?&1skHXpje?|f@)GJgIGb*IuVYnM_u)-Lfi*?cK>fWQxXc;BC@jLtqY8PYN(uk7K z;<)E+W6`nL6M93JHFglQ=g_*p$4js`e5>9d{5DpC4fpTBF?=%KJXnXT2|RJ$iyinv zfgZl>Es7Har$G00B`y&ppnsY^1e!%%`b;vFFVZ$paahiiAcv(s19DhhMv%jjn?MfB z%^Gr8+RGt_)$I&9tSmRkVfpw#4of!xa#%ei$YB+bA&2Epha8qZAM#iIBFJA=$RU44 z%7XlrQ6c271}h+cRaXc3tLR3^Um3SR{_1%fFN?w3kC}soNQHOIdD^Tk`RN+>&kpf}D@s1adxZ){yhjUJf~*ZfD5( zWVu1k$Hxb9KDq&r^XVZ$&ZmG3IUj#IT9H5BZ+vLCE*;UKm@SIk-J&+1rga zI}TqX0#?lW{t>;u;(efbbPuhNy;Ph1+{N7^}0f?SOH49LZF89^>aZUVU&H*3hn zXfKCcOt&-SVzS&I7XwDMl1ITC$7bp$qaBv~I{-SoK2QVz?jj7R`f@x8S{SIaGPfJagIG1+DAbU*`fx&@q>`@8Iy& zFNSAB`%pY@;U$Me+K!I3oZ9owlOU&{J_B+JT}F^okefhG!Oa?S3fjvdr_k*TIfX1Y z$SL^vKu$q70CEaFB*-ZgkRhkwPlucW=yj?1gnkj^6Ds78PatJMKEbFE@(F_#kWZ+q zgM31CBjgi|TOgnCybbaR4PB5=pmjq&!KxSX3B&!6PXLdiicjFZm=&fbUR1v9Es1y= zJx~qCO?lOE^y>e36u-QG%#+@+qa-5ju5Dw`O=-@Ppsuez1M2!+Mo`z6n?POP%^K?Z z+RLG?-|Y-_{VX@A>x1!YDqUYU0P6ZZB&h2bkfE;cPlvj`J|F7&{UWI6SID8BPs)ON zzEL66^9L)So?ll7_5A2YsOKBEKt2C?8`Sd~x}cs<>xO#1RWH=@hx?(P-#iHQeBO(d z!jy;u;$?4hCDu_-K~GFKsfXF9awL8hg$dp~9r=D}K6tp6ly)=M3FNZpKw=>kiv)o#N4jv%#@qs$HZUEH5dq_|R2mLXX4(?BfI=DU` z>fik$sDD?;q5cipah3jUR0#F&!3wB<*VTn(9R>P#bR*Qijaxi{{(W5bybbE#4P8+G zrgcO8+o~7p-^2Y-|85?H`Zw=|e(_D-{B+PwO%LR?f-VI;shAoJz3QLo;m|r5L*2OB8S2JaZVQ2K{4>kP2kOSU z0Z=#YAwk`^fDCnGe>&8S_4!aQ?iWG5xIzx~Vp0~=i;W7QUOZR<_2RlZs24{!LcQ3y z-Y=ReL$q zS-YK~&YI<>)LB=6n+NKwx&csU?IA&(wSWwDR)0FwS%G;}`f9%j>Z=uUsIQW;puTEU z2=&#$3aGEv)j@qVx)JKD#x2?+C$H}^z*|nGuQqf+eU;V?^;N50sILz9Lw&V*5bCSE z7eCNQ7)!M5?K+kx(*fwMZ@4>RQlzjy8X5OxyE9ct6rPfbwD(Bu&|4DcNl=$mp8<8r zE+eQ*%1xjy>1GXeN$urOm+W?ix@49c)FpjCqZrAJoCp&m)ff_fxqmsNV?U*}B$8Qlo=NaGf$M?P;0g zg?i+0Khz_e2caIxdr>Rg1HLVny;bAwM)$K6ijU0wTv@!zI-d_qhBuF;C{DZp6=|Ol zX350h0~zXg>NB8@*JT8CJh=(f@!YJTj;FmG>UiDGP{+%1gF2p%57hB=1E7xALxMV9 z0U7Fe{&cA0>GPp}*Dr$lU4UVW@P``_Ag!-Lv3)JtPw?X}` zp$qDFv~H;1S@lBwZnz)ncg=%PzvI0yz^|tnOP0Mof>@V~kSP>Kyq9}xlT_yULeGQt z36;DMsL4dy8q$2BX`=HasC%i;fVx+g(VOJmK=+cHK;6sD8tPu!%c1Vo?F@CVEH|io zfe8y#x|ePM)V;u$vP$Rr4SAu=1jANbfinnp zEjG3dCE6L=gxHXbL+wa*#-WQXLv3w?ZS2e}mB;>fUFFyFe6_$=3;dM@evkhVM~RH3 zb0`cL0@OpfWDra@*g%g6rD}qQ*lID67;J299tr~M1%virF_?Va*pdV$jJFIkGq)yv z4FvL6=KZ?vs|Eg}E$|H}opQ`q;PYoIoY`^IP#P`Jn?j*S&|)IVp{k-QH!1&71^`h0 zDi2Y8$<;2NG`D>}X8C#l+dt4@Q{k*{RPY!iF`G!~a3Y&Ro|N2a)*B0Se=DLh~ zFjXjEsygH6BW-7>P6sDCw%6th`lN+~A9Z!J3gv4&IQoYwN6K|wCL4Lz-4d=@(PZ3S z97o?_?qhX~ZBL}me`Vk1GtF;>MW)lB2a{lLW48VhZ z3&#+73lg_NKtwsAUtZ=qPV7?VamF<$bJvpezQqJJ;d5<%xP`eyfON=)uaw>+6|VkanpIY z9n2<2YX!#-^*!D4IP!vJ#yyyViTqW|Dsz19X`iYx2b>_-UV9i@irq~5QP+-dhUsK; z^iPPI(4R9V8!gIYW2FgA#$!sq0r3EQtZb^ri5?-Z?2W6J2^Zj*P6;(H@M&VR^<#EV zz-P)Xc)i~H6Lwq{LvX6|!K)-=h;>y0e1aHEYhCA!Uz4~{;r0g{e!T)~mP!PVYK?HR zq&*$3SCn3?^~KMK?f1atn@vl1e~&jxIjtRab~r^csY6^p9mk|i=i&CDzS;F!_(*BS zJ(wyKFjZadm!5NYgTjei5aW~_7{l?z9s3rWF`3rBlP~r9moH*ROxb?b3V2Uhith_cS@dm!T(ECPv zI@}(#t&r>8ESOHFJ5bCA)9f?}6KIHtj@j7tikDw4&^t2M%1H)bWnEVOTj$~mpQGcHwEFA;z=E$J`s3`xamAx?|seJ>nS!M9O@hGeHb$m zX551*7#yxzR$iyF1^jlzzniHHM1DN>@8%m*Zd2y)>F3S7zJJYBHe$t_dDB+>`&>ti zz>ojCT$S5C<@m2um76lhC!V%{lj}b#E6h!q;{ezDZ*u)7W&M|+ng-QP=*MCyhPq`^`9N=dEO|Ji>tepv;$ zr`(h|ZamcbH@W_kvVO`o^3{xcD%29{&K?6D}jA0tLsZ1qU=kqXUWTI*V{^=g8duqXVx!1J7f7$dd=v_p zD$nqH*ZdC+X0meqh&GmNxk0kf9Hw%c1V+z_?6lHVMseF#@$zb2`(v}5jPe^dq*A_h zdY5-GIEZPq!aJoWJd#CRepmie%u%Lzd0fg9+DUr+ia8kyj7r(L@@)QSX++GORh~uT zl55FlR?R7zmRT7yV`b$wqiydgZ&ui5nU>6)A4sr1g7O zG8QeLmK=CQD{SftN_J%XzL2;TuDP_+hT$_-OwX2Hh2>NzV5+==m@j%()08VmkcEtx z)NG+SOyxFFSVD|-LC!Kc&phU4S?MaA-AqoesdXLCzDFLvyK%z=9w(N)?}(2z78+N( z?-apUSQNK;=f&XFqWP4jlAdsw&rdP7cq=(9^9hZN93|h+{e@G=?IzzYQD6l2ezI-N zXsJ6Zmpr?ETr$jeTINS?X3W-Fqit`|-Wa^Zv;^j3S2agGGKQYIvd&VhAPZ8~?|mpF z#~5b@9*ID2#4xKP+vAX(F}HTmPV>;Nn4Q(qtME950;b9NU)eu>uSKY5gkSwH@xFe#nPlBJ{LI~B)?^yB3$GYN?bra zt8?8Yd$M}Mw{i=`6`-E?a@$1C!Y8!*+|i;P++R3vxCe!XAn;T__gA3{XSB48+b#;l z$0b{EZ;P$uM%x~-*UBDLnU-K|%>;6-UDa{+oP_+Mm34U>xhyAP{oY`%NIH(asfoj+* z8q5Q8(0aSf{~G`EKPLXC*`MQo+5#QNz}UpxS`!4?G%>f-1Q9eXtTe3*Ow2W{!5TA5 zO%O&Cj0><;{sn@M8Hli{93=oEYJ!16CYIJ9+$T5$#Pd|f-URVJt&}^0MR=IGEr>0y z2||a0l~$UI&6FuP7Z_>)B91OrM(woGTnvJHg0P(+@Hi-e=3;PeGq6&1yp1M^E^2PF z*g_M;7gZe!B8-Akf%VGLfs%kIqsAr{AXOF+l;742ge*5V1DPnxqTJcs0{jDFk(z;G zgXo{eCf1g{B zz-}Nusd8^sfKqE~WsFm6Yo!&y0XC{DSPcGA7R(Z)z@U~En${pNs?v7Wwo2mxTUx7P zqgt!Nn_Am}S^`&R4I-t2i?LQ-ET~}{aCUQLt~TKGzzoWNlowz57decu%GI9JLPt3ORcUBj5b4## z9Hcg2Js5*!3#tX!+3JsA@?SN)|Km;g*8+UC!2f3peCq#?7-8n{uT0=SfA;n(10ej# z+pi2XP@}d6eIUGsywO^L?hu?po=%;H)*+>cy`dg@grADsoj)I~Mp6*D$r|)WEE7z$ z&qixR#mG&YR&-Hf9U`>X1+%v7L)dHm@VxB;#9%`Q{@X4;q|?h(Fs*SRLh(x$Xg4YN zx&gg{vCTL6VPT7ePmb*8AE3MzEI4+Bug3XCFs{u7d70hAo6+$Sb10E>`5nc0TFpUD z`>B`s?Y+}D7f$?&8@I68)Z>?M{)t^I$zeKv?wmGDUBNV*!n40)E#~H(MoSr zW9T@qBCQbd{g8!lq=x_v2d__QlRu5!P+Fo!?JnPfKMq`CCI1k87yqV>IXWkPHGjUN z0`sJN;IY@X2#!$>^9(ll3%%ouc%5Fyg`?;QPdDI$@DXnXPb>Pd@O9!{u0P#Rl#?Ol zs&V>6v$I!ly@W)`?OZv>HC;bpD3{N6D4CqJAm;(gW*0TdJ?j^iaf^3iS9%KD_X87| zk38lp6tEnsBK|&?uTspZQF{QwBwqnEC`?s*{62=_2h|=U1&#cpn85~^wg!JAzy$j- zHO?@eky?Pcra#0pN_bd){ykg@20jf|+{O1+#$z_S?&HjICMG@b1YcGtz^=8v!Q<0& zLCDjQ0&jjRM(h4gaFlrlm{&tkLcNT&-_#KJ#@)bl?r8`tK&nu{^QlVo#h4A{H)V}l z0Q(in;K@yM>e?R-<@B&^j_oXLb=I_CH?!fiY+Xr*QUS!YV8*X&tw#YdAV zews6%7Fs3)Lf9apP%SwutaK(`9jYt+uZmEOngwfy$eq1m^auJT;Q>a; zIBD!bEIsz=RJHJY#49}8AeHEWc#`Z5_XX`ko(A8Y-@f${x;#j3q6pZB2L@VLRBh#m zkieTZhk{&W=YoXxt;BDV&4}I3VNoOU9!Ij*u8!GIWEo?yp^8#dhEO`alH;+;*>M!V zd9=YQQ;KeYjBZr@6S+0GiXo{=h&({?WaXC8{na>!S({3w-d;im+pMh9-8KDt?&``) z*ZllnxeKc&xC~Z=@g{6Pu;%vOZ+R)z4zAZ)4{}CS65P(6`;CQ`{NUYwGlF>|Z%Kg0 z&`73wk~78kLkDxTq>BN*`&HMgGRNnN(0o;`XUcmiO~4$~l%>tE&H zvB)<4e{+V{w2?0lFq-!Vq?V*8M!+t~rNDW8tA*Ir})Sg?g zhr^o(rx%T2PmS{Z5SDr>&XB24K>Ml8`=vieDe~5+{U%u;XJPh+e!?iZ0~o^CB-E9A z;x}!65ql*a6|&bxCg&t1Cw6+Jrp`!sDc21+l6p_}LyABBiu|N(Ku+s^mSUH%8B96~ z^HH7X(*yj6|7ND@R8?KoOiI#*(c4c)Fe5YGXz6sCGN%@ljN5VYHiK5_JZWAVfpKNm z)2V_Zwe;-=vJLJw-J`LO+8b`)yOK8IPFJ0b;(T(tBAC7=PWEvlw{MjFW$8A zD#&9FTol@$%GKcnUYzfEFAL*UobPsKWss1IXV`0l(?ZZ$ot<7)$#w|8RX4yO*_q>U zKr4D-N*Q&i#-HAooJC10Qv=h;RmJWs_7XZI?2pOLcTK+|I1|Iq&Cl;Z*TrV!3|90Y zr(@q{+w3A@t0^(rxA)Ezq)_y;r3aEkw2{XqUJ2aZ#a^42O9*gkHP}GP2_XE~ z*y%Mb%YBQemf|-vef<`8scyje30#1R1x)5F5XN}U4d$hD%M3Qq*vNDol- z@VO*Sp+CI>nH(lZ)i{Z~jp2KEJCPHdo8hsX_tFncpYUv^Yx+z^eyE7yP%@G3NPNmj ztEr@aOZ<*?d#^w974bdWxMd5+GE9%pKM{w#kIctu-KnA~DmkJ3=31#HcOW_F>6U~! zNk=k$KOhN;?0q0~7_^tlo?rNvCaLV{gG|Ox2%b(oBH9}RT0%o#>F!XGKyLDzOmAxk zh__{&P_;z}-tU)Wm~REqEJK(Ke z2xt=B@o++WG#DF#QT<1ecOs@pq`!ak%j`?^XFij{9ZLAr0k3gkX*DtNOT2nQr3WhF z^SwL5_$PE|EWgQ-wC*6rtF41%jiCwrd(2%t-w!&74ABH{23TIz_56A6MlIFF%n;0G zhs4_(S_zW)qvE@r1BEvw<;+g652CZu(_F3SZds(j3{(O?VJw;~ewl42q0$W#r3b2n z%i`1%Xx+^?Jw_nVxo3lKjlIg%7(#F(YBB@doOl5f2XjQj{8cCaZ0v;6>szB%z#E^O z3tEYN+yz;dShsT`cT@HvtkY{OSGesO>Q7H%<=6Fs-dZyA?vnTK{4w4!Hid(<_@|G+jwfpshH}lpN~m^r`?EkvMX}$&QCxu zMiX+uyBS#)l$oLxeT!e<`%ttKVIbSRcHj;r?~oB5cW~(eXEfTACYp1)9KGdpQl@k7 zE-E6fO5gKlJXR5}o8$Yz8hgli1@0Vg>;k(8j>7sZ6qwR4S)(TBKB8fuor>TgaXASk z;}GOdc&F&;)cvR=_%fPpFo>oER`Bf&FQR2zJGpX`%gF4Yt!xX6Vg702KGsc}-*~2x z?ku5wB=35(0Sojnc!|;Lz&m9Ue=xL_6&7{?^$xV)9H6wJYy2nk)HoT)RKIGpLSDg2 zbe|}DnQcnHy*4Syp(HK7!lfZOtwt~I%$mFu=>dNV(S@9Htu=sRwDxW?t=lCY_Z*f- z-La)RcYF-2B8T+oxY zcjw!Qt2trdP0SXvxo#j>a++u!{*HFj#!Zl$I5kFSAC5((`-IJRoQpPPjtS~^9^miJ ztk}X{I}JIUn!3qg!!490|IxeCi-5mO;Ct%^I11-TQ@wT~sghBGA2z(q&XHvBg51+; zT*Sw?iSE(^mxQ*6ySs7Abm0Ybj{CLNlY-q?qT8I)1A-jPc>TF^p#nDkVm+;UI<6<$ zw_)n_nP^;sk5Bte5+6-B+!}RfH~(Jl{&1ap&8Tah38i`P7rY=jjT-bcO*n||r)dnK zqOlAD-S@*3NfX*bQz!_MZlOO-X+QXN`tRg_AV{rP>AZfuGH&f3KgW-O&}d4*>ewIa z;D1U13xq~I{l9bhul@gSN}k^-6cGJj?kfZD;MiybCkiX}Kf(JSYG!7pIeIja z8X8Qe{F8aPK>7dCG(O~D5K-Wt9u!WcFyaCuNuxn2RX@hczhC@;Ta=f9XpW`LA3^fi3CWRW4 zh(t?cD@)78#zb>luql*Cv0dAcXmKdf zJlM)O)P@*p9Bg9^u>TMTiZf8z60hfn;! zFNI3w1Uko1=x_jla+}gF|B?h&D3AFvBRF{XK}7p=qcLD}D;kFfio>@fY43^V#qCV!%{l(=bvg*X}tjy?y5{x zyZ$aWjQZ1_%9ESM!jmXd<&io7t5MornW~QbU53#4;>gzw1=eet6~S* zosH|NjKxc6VSX=mP7;iuxiQ-J_~CPDDSYMSU!lR0z2m}?DN|L+zK|o-Rpo;NLaNl) zqW3XJ;S*h-9CzGW*s6CkKLlSa)I0`L!^>9_r{K$hmlLL{{62X(e-D+X>^WT?2?vl= zspp##GUjubVDLBl-6x+)pHKoBPsXG(Emt$D!t}Mob`);EH8y% zWn@hHD81>YzTg*s3NM||YkY)cBD?Rm)BLr6950iASoxE*WdnX5cFN^!!mMQk(hstG zey<%~`^WQg2^~(#<)8hwxxDhH^708$>sv_XvS)tw9mj&gYq;o3Gs(rDSQelCNw1+_^o`R(aQ3#BAX4D-6+K~_ubr5Ro}ijyXEff?vvs;Zv9a_?Ur2?v%` zsecsS!Hy!#G566Skh*B=a5eB&L~ncoeJ0+9Jkh;MvBpn=X7EPL3j7qJc?_n8m*IXO zj*x19)rmgc_<#64;GGW#rc|kWU3{7DB4v&dU)wByCwI|i-JBy5X6lVU(szaReZdpm z>OS`v^RiaGNqvVR8Y?u9E$yp|*j{0H8SbZ0z*KdrFWltH0ScE#8YYwr1yiVmfWZ&Z_y8G*{bNwE->Rqc3PKD| z?vz}?93L58-cUFo*!xhSfT`+yU$~LsJq~fcD)rgOV~i$Zj=3c8#ByXV+J3S%=-5=f z@uyQf`E7YmbSGqEoNjPkHu;NKnN=D3$Fuub9aY3x`Nh@Y-<4Y#UCh48I#;E>z^Pa( z!lb;{_+oZ1>vGjjbEo355mx1h)rIUHR!^0OopbS+h^6JTmR-oc!Ma`b+QGS4Gs3aF z!|6iyb=JMAZ7$Bmqa)UoN3Fk*eU0^~%HCsju||Yj`S;!zvahmUR1Ns9E>@55D&I#q zpM8ZjTqOxyQ~X`T=5k-+`RvQA->T+^uPGiC5m^2q>U?%LYh?BLSeIh8h|uyf>iO(T ztkKm}rc3e2i0E=B?z!xXEbVGt)V28Ai1>2Q27_hY>K2J>@i!5y^5n!l*}t*oR0pQ4 zEglhpl$&Q{W^ZOMsUDfLwpbA^F2A zy2TcBE~uR8&F)Bxgusbzu?L-q3J$S}jjXHsjdNk|X{3G6MQ%$Kg4!PT<9#f8ff`;tE7#=nU;%(617$2220 zan%>-VAsRT`G{3IPZ&B!?qT}V)X?tc|}{& zWQf%m)XXm}!QP`4{ou1VQ4;tIHvj|-}C1N{24- zO-q_yTZ(iB$P=^odGi}X7ba*PyUXi{dMGu#9Kjomn++xag6Z#7`$zMp6kslo+#?cY z%m;VcX|d~e7AW3QiFy4Uf!_F1$>Y7+pv#~wUAup=(1{x?&2JV7N9N3zYBsGG))f69 zHQR40oL)CVo?r4aZg}~Dh?koV)E}6tPXEOeGO$w31wqV> z;1&A}>mjcZx@b?uh9k`-PjsUY(+H>JRy}=WT*RgnE2A95DZ)suz91Y)j|fagtfuhZ zMrmyGu*jU`?T&;3dXlAFUYrZu(0rQ_;$o!JBjfXf0OHq|5dJ^ z&h$0?YJvZu7Wixb|3*iz^#*SLivRJi0RSsqfztAI0D#$l@BjcMg${si0B$QOd*I?% zQr~9E5dbD;0QLq8i-8OP768|;M9aZA0Kj0$y2MF^0D^h`;eG$8a)q$vnkP{aau#YR@_ zg_X5;H*4Fqt>%A|?C#Iq=U3j(`##V6{+|DXe4H~gxifR;+&Opd-19x>>k82RCd~S8 zApqcbV+;TQ;~QfDJbsG-06qgFP~7uRApjt}z!U>ul)&SF$Nv@t0Kol5;5uL^G1bn~ z62t(w{1yNJHjGGp;J+}Q?LUJ5jUfvr@IR=43H)!Yh!MkXlmG<}@bJHb|J?xU-w4b9 zd)WVf0R0E?1}5k~xU>=a57Y$E_J0@qZ^Zl?QTRZopJD&O;eQ|d4_q>4^zO zg!~5=G9v#yj1d4vEb5EJg-g#QN(;0|IxjJSXBvkCSOqzU#9jsmK40ZY@5M$A8`h#B+`yhujKzp*XM zkpI8=iTV-$|37^d^xxM1Kn5aq3vva5$T3S$cI8u;D+KbM|D%SkpT{|hs%5?shcf~yBcO;MifxMXFJ?o z-G~%7PfsT=7k5`D7dJQXg2z)5os!&=NE8AHpC%-F^!Ek-QrZ0l`Ze%xrh(t_KaLiN zuc>OkuY8{E=dWNN7f{lF5E;FKIPOoOSw%&P`BCW6~1CK9J z-qtKjW)TSTm?B?lr}raxSRs*a@Y&B|ORTVO%Q0?GV_g4rxxZ~&8#UoNl; zP09+&J&uG&jL+JnBB0u6HT@E%6!yiVImxnHQajR0j$TqKqfiPsTd?^cf8Ja6Y2`7Y zo_dL|mpO@W^bELB^oxMSih-uVTiBgE4#yVC;2B^R>;cV({KOOqo_`3zu&?C{`81JE zby_h>kS?~;*ecTmZPM^!D`g&kja;jZSB~dDRrFQWDRX(Nl;nmUr5BH>)Nf-e*YIjo zvQwj!N4dc&tMeP6SzfrR>B%Bh6E9k+*S}Pr;k{MhUUkbo_$w65H!G!Q zc(IC(58uiXxKcR?FA#5HzfroG+5kXUz#lej9fC&>Ai9zB&|id;P;2Wo$Qa2$G-9kC zCP{l>?ZkWVk5WIVZ*mu$B>Tj-x37YSVx##KhZ5KUi{Ujpo`ch5O+3BxP^4V?6$sYe z1>>ayU>4y$^o4|sc6wvbd=Vrx_;|xxK){8+KZ>NlG!zRA6CQ-m!Mfn1LOQe>vI>1G z%obP(!XwH=vjso!w9#2&H|PPcFaCtM1-iz!Bi$2!4fhEs6q&da@FF&+T@!hr5UQuX z63rFeL2>j`Vy;+?vRES|e91ecllM`agrxzXR*twE%R>CbbHr4v3ADju(Ov8)qEp=w z8NjxcrdM-6veegh$FFBl({EbyaFSkot+8d5G)1N>6VM%OXScKilF6F;Mo!&ikq;xyS;4>A{7rkKl z`@d!Sm-fO~;0{JvnFXo~Mvx`ii(IQvQQp2X9y2`RdMQKe&(ubHmN`^RVfMwpFFIa! ziDpNtR)!X3F)0*n?jg+qUUOQH=5c;DXs*@D+qoeKj^2PKs?PCQtQ_RJa+IKxw^^f? zD}c$&!i3m9*iU?cnSmAn#%7hg>2l+F@swI3<% zvU+h})f2K7TQ9Mz%OZcmx+P7`_GCx-CJC->3OQIlSRy;+n6y>lFA2YVg``ykN}HZU zk>V7vM6cgKic+{qaIX|eLzKtG%r_P(4Juo4$A^2=3YDMOV2FX#>OPsVfBP(!-{_6> zA7r*5BSg*kXDadsxNWwn#01971da?Fc7^_PsJ~n{GLu~t=pnPV?%}^%wn`K+b^!NU zM1xp6F@Ww7L&f?gpJz`G?vUEsj}Qs`D^ZF=IqbZYD`<9{#PVN$7|}cP8O6&7V|e^* zm=Ls7$|A7E4gsm4(H8Mv1|CBVKAns$t5gbqe>^&I^ZXu;gf0w5+>A;>oR(v=CqrPOG&F> zJvEK5A$x*GOJY#64MG;{m-W*`G+`(2Ejxl6!8X8iKtt+je&TCflAtXU!|*&hR7thc zoEN<2JxvQQhM+d?%M`8lB)pkZN&&WnF3*u9>bHF*+)Ud|lAY3sw`Ng^O-~wRyRs<> zdi_{Tll{dy<{LLfW2PIac^Eke4T_42&f6=9m56|=yZ#-;E{Dz8QL7jfYz6_n} zOnch8^I2A!H`|gDFh+Q>&E{tbLpgm_KIMu8Ys9XuL?cL;AtpBrR%~3ylIgdN5CkWR zl(;rO)>QHhRcGhT^uUy!T*IM&lDhI^H}Xq{q+i20___uKrLf{qVQ7rw2X z4??~C{Eu~`k6t<^n(1zB%{*N#)4N2B z9l!rJ*6X32$UW{V+vC+Yx#ZFj$w*Iodq&T0bf^c#q3!4#A#q-_V{B)i44bcargSfo zM0n%z+zS-AavqB?;RFGxa_jVVImqD6bvO8=oj4+BT;T7&>1-MAj4u`_IB^WVK+pxh zJS1dOy{tklj+8O?`GrSVo*9&r5TuQMcap*QYEfT2aeoSn;$=trc27*kEPo2cfA79@ z-RkDF)T6oS4&i#Lc>i5$!Acx`Ve8!N;VW3I@0(5}CxvzLYFoCh%}zAHt9Mkab4&0O zckGBvX^F!y$DRHe7h`lP%eKwb)R^#M{H|t@VxX^zzgw6&DT3VKyT>haVOVG9yY@Zl z2g7BjUbGEKtBABZ@6Z~avN<;V^0=*AlJnP=U1w~rPX3m3qc3lxTk@wQ@`I|X5=vrP z(-V;1f)bXl*N-j}B*#&4uYM_lQd_c^Z`Nq0P+MsoAI21>(RO7R43X8paQx#;>n) zr?aVfwif@o+K-D33&{9&aRpU3a!dNNrCAx)*2(QZ#of<|7;CY2SnM9IcH&Re`73s1 z_D%k3Rc^>Kvb}wgR2@Dond0!~U{u5rTC<~bUhAqVrrx!z{QYCY)0TQ?Vu85Y+JJkYXI#-)wrGdSDwGb5C3Jz& zhj{7rpv`I8=s51rk&8jk_+iiKMIYpLBx>C>M;DAj*&R0kUzO6FHqB48m`~PIaVon- zljJ!1mfcqPvH})MQhn0)S4Ag}97V=AkPL8d*jxO-bU*RKBpc6dJPi9LW6y$cfllR@ zL0tGf&q@M1(>Lle&tRCz zdX@ZwV`_%Kf;j(Whxj*?lBcQ7t`twJ$!)-fxizFzb?w)U)RsTg=JZ%wpW~m_c$|zF zyDRHfwQ{?5;=A%~8+|+bCU@5?EnRZW-k#3>0-@feIDEVEP;TRaX2%*~XTzm7z4I?E zn<@^T#p9=`v&8!!u?SzR;;_Fx(&_C*cNCAcBJt!1C!(1DU@>= zv)0;nH>b^q-o?E5UQbnXmPD+$fuq+(@gq}Du~F?l&AOOkohmr^2ht;rmFAl;E@@qUc=4{70#c<)t38x>gcKy}tFp~_ zM!F-ktE*$`lX@X?Lkcu5S;K2;wvhCtu4CicEH%_@A)UFWw)7OEBinFjLOqQy$dsM> zY3qKGnyS;;?!sZ&RIBq1`)(^Mso|F^4h3oov&yc!bQP9hj2nH}DN6Zjw%wgY-LfiI zKKTLk-JKdswCTyLp0o9tQrxTU=YDA1kjs2C`J8UcB~8bNv){FBuPO!Uh;(~)o&USM zpafI9`Y&wftos2Q=I-;DS8zi&vcvZ%f8I4~>lX_l!I8@mWBul@VP8C}oyhm!%KPF% z-{kfs3;2|C_V!1YjD$ON6o(w&XWXDu&5pYkYgya7_0C@`cjVu_g2zV%;5a{=V-YTR z56rGP+3CH>YaBhO+u+j@AYj^E_xC>&NMxM7fCah)Wigmty5KHfpTvfJz!rT7$yO)A zBMKHrWBtJItm&1p-PoTCmor>EWz ze4Kvn7>>>j^G#c|gTORC%I^E33Rf)@6tj;&a1SBGj z;g>(fKP6qSE4%)V2$2%2ZuAu=g^?1pO2>+1p!EBU0>2C=T;^ z{OHGJ&5kodOQSv&>z%jz_pKY9kHhvB~E?s)3!r=2LY{Qy) za({ot`keR#6&ASf05jsXRu|l@Te9-k4OXGKtq+39Md1;9qRy{bCe=p!tSuorL4EPT z4dcm;Vms2$Ejj*oloZNLPGwLd7Auucrc=# z6!3N3C=;!gDD3L4K$BNlE65F(c#}e06-~{F+?UJGDfHWFIYXECC~$2T*jJY=RT>V( zF>?Y_6*`@S@m0Vrh1K~V7#I8&%FC|v8OY+R*p0sHj0KBDnB5(OaeGlAMt;!6s9Vg% znx6PEhxn^8y`INjv*b3$e1qpL4#dklKAg%~9v%l!jb{^=C++wxq$M@$k74H=lQA+2 zc0?alW5X5|jgHy?J{gK~udE%6SzGTA-iou+M2w|FzpR{Ipq;pl->}3})i-%3`?1di zrM>+MX1nL?T#5rXXQ6viUbACc&O^^hYQ1wP`^CZ_H6A~leR}Ds0v6#WyEJsErqla- z(f9EbwZX?r6Opt#*Wcf!Xj(L!iv>F5lY_T_#=9mC4qX;5(1^A!&St9Z9NQ7M)v3q3}z_gfA~^PV+7w5Dm-qRM%2Xij0q=lQavn zr^s2Xv^)lVE858mP||46!6!qcP)BoM`-w;MCUKwUV3-Yi1~Q1IQ!Qk15M8E~CYwct zRhi+%2%CtE&$6p4WiJw*$+9~CiTx5CO}D#qh@FlOrZ+tq$@Ui7(DnKi>?y*BG~BD< zyrU>gXTIr0YJ|4Tjt}pVH)21K@NAuk%by@N8O@)-)y7m%{)6)2%-v~hY}icr@Xj=j zZsbj9`j(I2>v{(IsBs(kP;-R0?3|5gCr%K|Ih-TtoBWiAow_ZwxBr&=@;ix);_wCg z^o9MhX2(`e$$6Dn?|h#>w;LAW@gv~T2d9ZygvaR1jTvAvq(_vlJ^?<|x`ZJzwb8ZPf%@rL$>%F3Z<-8mgD;V9}FqhuRTZNq1 zI*wt0-@?lp)pS4cbaYs`9RtIDK_;k9GIXjyWV37?!%7o@1Yuifc6E5v538Z+w;e|9 z6#-d#eHL1-Fl6CgeSr!T7r=}=Ke9l%oMABhBuuUyz%Xg|&!BPhL#<4D zENbIm!@f-P5x-*VMy6%EBOmzI))@jB=N=R>mMnZiUxjKXT1l2<6oBtwmaH)4klNn9 z8*?W6-)5`T;Sobs=+cA|ZS;4T<|PufZGGKIs*xpZF;9$>z&m3ti;4kj<2aDt$v zs8&ni_(C19U7at-8oCaFNeb=+{t1Zu;6CRdZx*E2let3Pco;m?+#@_+nE7TAi@{6f zcYOGP^@y7dA_nw~P-dceboVFFyZI9cNw-h#NXCYZrCnLSl%gBiLam}Yr(0WZPYEQg z&Wad&ni>$gIZHcnJ@?Y`L#cg}2gznfq>$|GMR^;Dt5U&~Q4x`x&T4j~69*9#N}$xF{i28yzq2@|V*4;%(%;%YR|nk$#X^t=4nF?1Lyh z@;;a{nu|5EfKv&WhHI_M&W~GruJz!>mt|T@J;yeSh5bq-&@NOjcRb7m{%CAbc zs~Z&6zzm8Z;syHoD^~2j$pY5+8%-Qw z6r6EAmK=&hEc-;4n%?ZVkE=RI0-r$jeB6zJd^|oP$MZ&*fJNBFdee0k^f?2NjLnK1 zgHIDe*wB>e@1H3+(fAD)3mlG&+%u1@3nn2mj!pwJdglCy$zuwx*EnMa*jcc=W~0JSj2AeSJ&1wG6zbpF|SbV z?_VKXE7}c)OS6zgtQ#U-@CayC#wsuo{!&mx`N#{8n1~KdQ3GehUdg!BZb@JK0@>rl zBd{GQRyvz}m`9Uy;6Nx>{03`zE?X(*tGK7Kaxz>qki7GOf0( zULm1t`>KR>UrO(5?dpnawn@CSP0hAd$3-`aac!^4Duun8&dzD29-`6tvQw0jY2wIy zy&fuCCOVsod-bXm5uE{_BL|DS;W9OFLgYW<)?o(23v4py25Nj)^#A+stbsrL|5?N= zK=f(y{I6aWHr2f8;)(Ylc@QX0fbo~)M8ShaqKgaB$<4zhDbdv(=Sp!W_WS++JGXy- z-T!B6;CKK36@#s>g>hm0Z`=uZtbD-Y?@RVJsisTd8RY>HT;y3Fy|##57*_Ko_m=008VFC zdPE7X!Zz$7^TtBw;MeS|1&2V=j+2~T-_`I5Si|-6KM5a)SMatjorHA3?|9>vuR>12 zMSL`bihKuyFz!$eauIF>Gs$eE7q)|D#>F8|;E&L+Bm(jd-V2i{#^fR3&tR6H`FV{I z=oS`>;Ggp^LOrQwIYrWmJg;p+UMLSCM>qB%T?Gj8MZ+`Trgle2o9-j&H6xMEx+}<- zZ6Dy-+FFEk`d4_Aa3u0q6&la{ng7z<3c2tQNd(-JYL*ygmRjv;s7mJXA6mh5ia-3D z#*Et*7REc-DeXx$%fEZkKm2sVOh;@+3}{16PpVmB*mcAv>nwbwelT*06e4VH{srzP zjuHxXe1!KUZAJg?8UNzf8)-VPj^z)>33^h^av9Xe^n(W?QzY}j{@2h8sH$RyBw35LP?D%2svx=9j(7oXin-fZ*kYxP0$STS_Z{5b2U@EccBw^C^YvMOp*BQ=4fUu z!mx|`vNhTMFO&~DPHH>?WAk$NHWj$8=qh->b5v1e*xZt#&1Z}4Vzt`oRgI-Z0bvt}Kd&LibAGw1PB2N9TR?d0u_a^g|pGL7ktP zdo%!zS6oPZ+cjSR%jiVX>B*cL@z|tp-K88Sp=C12vSN~^+y1j(YKrMOD$$(Uz6d-= zX6cooElJ$U`{#F8mK;p$6~Q-KUAubTU?~7vxF*H25l<|(KyIyoe3-#)D6tfY(a6&u6M`{$NiQ>z#^ zWLj;pY8RtKKA>ul$}>kIzf!+eHHuX&X>RsY+jHoMV8_?$M_i_$Tlc@jMmy0|g@uRe zl@^+wRI~KT(CiAgZfMS~7_-g$X!gbj1rK)(J^sA9JU4W&U6)6-OwrN#W0$yku4MY5 z9Ve1DL<@6|rk^aYtVG^+O*pln`~>JXkDtC&G#xb<%+h!(f8}N~-(LhuDhof+lWLY; z851D4;^tGM7@Ko`Q8j;ei<_EpNA#p;8T39i61F z*DOYdW=;ittRu?iN`()1-9Q%ce-VZ5-3(t76odOa0Qtf9il!gxhNF=g<+(?P3%^Cj zRK4vQD%p!ZsOr|uQn(0zE;AU+(sX}+@k5wxDMet@*(>i!HA}AyH^!KKHx>0+d)CLd z&%j18MkN=v`T{h`vE-7L^}JhLX~xnb2UERkJF(HV>#$h1@P~-IiX{8$PJ$ z$IVyjt78*2tLl6kTZuoaTPm8HNtC&YQ^jMp?Ml5ZjnW8qxTI6XxZH=k)>5wso0Xw^ zpJmwyIkJw@j`GirTw_RV0W3$hKaX|)1YC;8-HcL~b|1=+N zq$#nmILI7*?+CDdvs?x>v%Y@d!kHp*XC*&4&B3rr=CTJHxv_a`8GZLw3c3m$=?QnM zp}8ggwBXw+K>hJ1yYq+Z@PMj9TIu&x@OporbM|6}pt*S&S9N9^Pq1T@p#3m|7rJ*M zB;1YX=N@GV;7!j2q|*XEuXGA9Ap(DcCJ%H|W@)PBpXZg0X+10~tVF@YhJ&9Xi#nzh^g8~!qAENkFg4Qm%Vl(oXS4Rom%ti{eW zCWZ}Rjhp+L`9f*Sy5|xAMg;k+WCF~)QooZExl#jOfedbBR22m5&y>sr*q=$7uKwrw zMe~rT0;|6_ThfzimQy50v3n2JOI|3Ou;mY&C0zyS*noRy0fO$SOneg(U#U-#sxC!| zJ323m6g%i(z;IUdMfC)fbo!KtU0ML1oV}tv%^dLLn5F5mf0<7+CaSTpU<*E^y#Tju zmZKOQ{Jm)+!7^w#_eJV%c#6cIvo5(105O|66UZzuqP@odDvktcwL>8P$U;GS&2uO! zqMPsAsDy*pRPi6~Dnw{&Kk?soO+@cduK=@~i^ed+!2rfAO_%In^lPAB1OJyb@K5r8 zEcreEMRKKVa9|B?dSl=j=hl=j=hl=j=h#S5hUCYZ8mc!H!G zAomAIxnW$I5`+_s88;YYHY0~drcVWd;P|QTi5~6*5(&h86J3*>@GfLGC$c*U?}Q_e zT#0TV)L<4(xkjYLsFQS_p$Ra|- z<0$>}|NmQx??0$t1OEmZ_d%qBI*E5urbTywi0xl zu$X2uYbT->21cHAib6IE{Z`O%Gmtjn7lF2f@kqNcc}chTH$XmH!VNM1ejzd$*(lr* z;z%SS4Z^SxVcIIBN%&y7bEf(CUb+CO7nX-f*_)7Z;k_7F!Cs_Dn6-mjQYmzZQ5S7Ms)UlYn2TBquQ2_OHKFg&)12og52E+c ziTp}kH+l`N1j#4O_hE#Rndig-Hu1OUpvd7AWHE9d9{A-Wq!>94Zyz0r-a|IQvE#+) zH>e68`b`*$M@Fyj-S2SHm;Q3DU37#qgyK z-$Z(MTWseTUJ9V1NrA zo8@PIk*OxNnmJV`*qG&U+vzA-NPui+eT9ufz2K8h7ZoK40@HB{RTM&lyS<0z4ne%& zo}iieYak)w5VpA>Lm)&hM5+pH1P4$k&arR<-$&|59HAM{-=BMhJUjPG{^Js1+J0pr ze^jM&CSO4l=<9mvwu;k&lm;o=R&iBueUq!eQ85Ej)XAlD6-(g#W$sEhMF)JN=yu*x zg&v+D0a;x5s3l+a19B?A?BGUJB-D!i zkA)(;g+HRtPx`=@geTxiogBI&jD`ZwoA2`#axJN5vOu5$3k&eM58%#lO2gDa*tUFT_LaJJF(Xl3e#BJhXT0FIn$rpAxtaj zrH64jVw)0I!A9$RkKpeFR`vHdYRyhEGdJx78?%&4)6nbi z5!hX6-o9T|&1IoS5lvkV=Lq@|ncw({{R27#Eo!~S*^J&no}at{yCZMm!1Lzvf1m5t zJV$HhoSk4}mY}B+YWZ{WGpDDCukkL|+sxW1Y2*g%KIybZGL$PkPRFej8MrgQvn9xo ziGuR;-QI5n^I_;B%Qu!UL?&OF7Er_cfV{oP4f&Dx9Dy%5gcb0|prg-Uh~x<9=r|n| zr-EFCiw-#w*THxp1BMIM6u?H;A{Zdg(WypM*V-l)V-55|(mZ1r^XObQW7v zR|(A(_Aq_hYv4)9C(uKkN9GHRUY>uleOg8HmcIZ418Kwm|HzJnFrB`xNQzJib@S_8PIeh(OrAbpzt14R+6K zuI1gY8sT=c;Y*gL?g)No{dC6sMzPz4+A{j1mIil84T(|Iy3}Lq2Adq;_QUR#755m= z_U&{_2GLe!2QA#9OTGc`m_EF+n8~ix*}2O~{6R-x){ntpmPXC}#a}nmxX)q{YnF)0 zo?v5^$89grkBVQXW=?;Tc}>r{xo;VLOSkD#_l|kmn|VIOPZVR z)$Prtb5u$5rv=>R+{MN&=?S{T-zrlq9K!6;LDIo1E<~cpYssqRP~1sg zgluDwBXJvjuHs$b6>=Nxo^sFSA z{i>A(W2N|TO6H&G+{&xOweODlv1@POve{%<12CrLthD&8+3e z=CG5*C!J;;Ue4VEEd9GZmBP75xA*ruQzSDXmhZZ)Vx zxFKH@%a|Q>hp_qb_f!tu*mu4pTC0oEJA&tGPlxs~R%(bh$8XeASQk zv#SApVBKP;BaOZEj!jd&S{tS8-7O0PDr#K?>)P5voXY!9X~&)LYZ|%KaA;T57`eNW zf6Oy>HhMb`?uv|8@YF@fiE#;&*{$-l<#~Us|aXoSwpb3bHT!V~$=2lb+0+vlDF0^0;l8{6JZLMdtK@ z${FPkYHeoSRO%|S8c#ZfDXCSfn(4Sn^1B;WHrWz-rORu&H+6fLNwu{@>sY>Id3@c^ z)zboosqN~*s(XS&xh*wO)!dNViiRp?wL{o9=+)9ewHG4&I0ogiEl}JR;dbuBla54c z{Wsj2+gHf4txal&JHoWDD(~0gyPY!wOICKg0(xt$j6LdE3f?_6YYx>Gxe8|0>G!WK z>O-g3?b^S($X#jPuP)NpEI%@&*h{;rHtEQu;`>#xwI>gM0z)D5@ADgf-SE@?QALkh zGAr};(F==OL$yKegA2B@LStvh9eiSiMun%p{Dafx8 z%B8)m1*&o|YBB$QJ1>T(7H4wQMFqUE;&b4gZr=Z>C5F3Fx<4QG0)Gy_;~{}GcmO%XkQI6>lhd;uB{uB`7qKUY-;UF>bU3& zkz=Y|GK$wiadRraPm9WMB({`tk`E}ZkWUv_r8HFw(_C{mW!M%vXJ)80Ip1L5A;Ppw zqEyOWEW1pPl(`CGWx3hYus*a+R+8t zCFP>1s1;zSDZA0KgMP9dq*5^O2ouv139L%pIbn7>l@WN}eBb|=bJ)&gYFeJ9N&^NT zW_jH9758I44bGg7=N6a7!Zx$gIPI18U@$U?U0t03(Q%)0URNLI+Y)l<6RS>eS-y)i z4VufWX#ra@B88zZAQJM`k$JzsMcd)HQOxEFA(rYRO!rkl*B@=AS^0@6!$Regw z6&OsQr8NCUn^}l(V%D9OlTN=1YcoghpySS><7lq!wgf%0n>nhZ+xsKDoWJ%6%Xceu z5gFb+ExLdx;N>@;caOWWMao{lEoJ#_b`_lB=yTdP z^`Vtt3n6JPdh-w3V~Bc<5?7?n2)o&b8H^Ssw0o1#kHJi&3nfa1XO zX1y2;W@%F6pZvemOb?lg`;g91Ot3M_2#vuNdt%<1-m<5^XTlTI+tgPNeA<7V<6 zre`X;z2EbC*Pm9fd>eTM89ykd1&FyvvaTqA$uR}2Ll4u_z{K=v zHK>I`nD!+7XTGb#IrEpyGpqtdFa11iH+`c*%09~2k#$1hDj1S8FzdOZ5B-`Eob`)B zE=6cdvR^4~=Y5emFlVSzU34&GDC?ENOPi39!9J(BUp1Qgg599d)E&%x&Q4LxZ>*)= zU@cKRYWX$ClDSq<)VhZ~lz}RI+uw7x)8vY>gNt~PnH+`xu`N6am7#ckvXmE-nXRbQ zUE!9}7>dC2=KD7o%+jQ-Kl`iABd0lVnBpU_NwdtHKAZ1X76O406o*xNAK2bb_K;1p z`Q6?e_SQ{9`7GZicKgO>yq=&PoT)Y2cn)Dpd7;{P9u!y0e^bZ?pDMnBM$L1cFl{ZL zRy>FAoaxLvQZC?2+3#6HTMj|4f-Y9j){DTlT-eAKkS$00imhleAh+`#aVu&zqUxet zp0rAjdTAf<&Q^RYykGT(S6{JGsHtn`Sy!zWK5E&-Jzpmk7PZE4@f#V!XZyBto$DTg zp6LU3RgDz&Kc?lKtDFYD>^u2)N?MUhoftZ*b_63Q^ZgkNW@%E#U;OCi5&c@pcfwfu z1RJwFZo6Faf`1(JQL`oEnICd(X4y-0S$nHaI!%?_PPcBNb)8*pJE%BU0&2GF3jSsE&_VZpTyS4$__T= zy0No>UY%4YICnwMA1gn`r-Ggzl6s+8pyxO1$zU)`lLhuK`Zdt6f&U^6{1gBGim869 zjh_Fh0nq`ASFemT@qn!Uci;fW0S0ZrjQu~I1044`000jNIsngy7r_z_C^?LL8GzLT zq-Y0TedE&2v@{ZKxB>%G*NT70-S(x55dIy;buAjkVJr8 zZl(j=0CNSP2k|hK@-U(aTs@2gC!RudA-g+y5y(kS1TqQdL?(CvC=e;h#l-{X>Yhk)>jwb+ zcYg@_>;FGi17`jo7t6Wn#GK?5GXv0Ka(X&vYOraCt0xIhbR)Stk=#7+U{K}e=|pxT zd4M+qnMh0|cqF>J8d*SGEltIL;{pO;QZEv~ntB3ADBudgJCVr%ChD0)_HZJ*kdtuk z?f^xIC--+8|6?D!{u}={(ZJsy|F0mXlc)NV>8Z(7GHvQAB9%d(I&&$FOs4)@1OSlx zzyCQ|{N1cta!oZP@<4qycH@Fco=k-R90M$VxB!ix5v zx?cnR8u(AwfH~EV@wC3azn$FoxBNeceLxJrf5`u1MAR9RjswmaNzaKHAiUcwjR*gR z_cpE?A->o!9lQg+3)|c8K*Hfg2p(UA90H)-h=>93F0>S;P*%as=uQ~J&LQ^5B9IT~ z9#R4ag1GkxWH&q#(dm4kdUPQie)((Y3hDwbWrmd-&;MH<9OJ6FHqUt$)SIA)S(=KN z^4$EbghX_iW(-F+OH&C`UYoy_2w=^n4Fkc^W@#$%S)Q7|mHStDWsD__vF+F}=jFdL z$@%v7w*xD31{O3so(~Mo*-(JTZwm^_!58X+{X>Q`9~4DIybf8-q!;(amxW-=Paw_T zsle};BCVBX%hEked8J()x>Uf_RW&!`m#$`>sKm7uESbh!TJCo|F5qF#>jIr_LBNw7 zG%x&eX}~b%40T^0b;*2Ys>)z6U*A}t&+w+kO=BV-Y?wWR&bgMX3x1ycJ%^dx*?A?| zk^ekZr#qWGoBtp+{Bl>y&%7O}-qN^fZVRro znoI?_WwSID`y6Naw}SsNkBG6TF$DuQtiAKNDj2Xwt#6M}MS^^$d)s!YQdoHW%bl?L zXilg1pf*MBC%_K1udOEUW2P?n!7ePnA|oP#w)dwn5OImX?v*V*GJR8^cdf#Q$B$`flXJ4su zIXnEaZAY|{%^^SN>DZ{8$u=0w^)lA;^ZX#j?ZMzbHY_wZBmG2)U7fVhBV()9@A$^z z;*87X;g@F>voq?-0s6%ByRp1kmF9f?#%;4Mshp4uM}QYIOH;Yu^HUg2(QIe4#Vt%c z;01CQ@5q^o=z^)T`#G(^&gx}R%mW}*6-P9k8v^J)+CKzFy6iwp+ye$!!p;BSma8AlV?uw3>k zjvd_TeTApxqyeU>n>;?30<&1BSt{O9$N;ypF7nzTySnYXXM)$DQuh2r=sDEc*~7gC zwL{^Td925RG*CJ7c}!RN8*asT8DknPY*;j`EEl18d=>Ol`4*HyapW^AJcXT|mwBUV z77F|N`~-O$Zh>YnmouH_GrY&&&T}69vSBsU?6_3ex}_0R`xkWhRy`CEfkU^oeBvA6 z6==`aSs*OJT+Vo&{(t=%_FGhdATRKJGqb&J)Au7UN|R; zhdb~J5eRq}H!l$9?b09p`~R-#`mfxtf&XI-{BHmAoK{)RbIP*Bfv@;*K)sVgUYWx% z=8N$107)T0{$Csg%>EN`PVPx$k`vC2;7Py}C|<54V-MhJxthr3lE^d~$I^pB_Hd^p zxdM-|C&|gn3wR6@6G1YGBnr`!=;98%%5MD=|NqC$)_;P24g9BQ!07+;00ulaJu`EC zDm6JdGc(CD*t|Z~xM64N5*DW>hh?OulSf&Syl~`1SE4(>0VI;0hy)MN8<1Rpe=sqL zh2qzo zWzdmvNs2|RL~iysv*ll~?y~Id7sx&^KCmv#m?q859wrkyy%pbP5H{oSlcYa#p+k2) zgB4sTR9CogNv@TMan;|yJ-1!d{{8EqxV-sj-i?hbb8~g**6$-CM9Kr${;PvyT@}rm z)^qLahRBK9_R|!~9Qm`dwC-`~ZE_Fg%-mc~(4#V~6w8M~-v(7bmpO>WetfsF3_C1; z{;9dSnth!1z#^vAl2t>0V_~^FD(8E~M~lwRaa;=hsKxptKMH=y-}K4mWHD<|N$0OR z-8z0_KKRzZz~{ct5n0T+rcs5+ha39(+?De%cZ2$_Q1+9w*s$R7E-@zRHR$!B(iZfb zVZ-Y?(udF)L&*C$Y!2K8<~o&f16*S;axb+GHcx!68{vxdz;xK!`XGl7PX)T3$#91j z2(rH^VhEsDfX;-&kU>P~!i*yNZ0H~)bXrMIh1NrO{9&3KJPf+)c`G{+RzZaepJz{j z7sCGj_OuU>0)8E|g7yMB25(%MMY{=|fFmN7(fS}FJUDiD_8|Bb)V|I#I}5%6Q7D_U zRPbqNTsoY+0cOJ|vJcP_K+Xoh=0m>(Ux#N1NQ?>0{er8)J_dF5)(C&2s*LGa_4;1o#Gx-*6Gnf?`K^{i`7XBX>Wf}HdcLxeqqLo z+@1M1m_nzXLa)Lm4jz9%6Q4gDsC%)jQk^3yT6~08H#b~4_IGIRM?5^fzqXF zM+J+uPx4Vi5X}$>09(2@^{Vixz)wlbz_5oRx@siNGq+rsn0totsPLXfSMV#lO#8lY zd_g!js2Wn&=cWtaZM200R4=8?&G8I|bSoCqnnwFxJV$Q1do%5+!~^T>JVrk)@|CSW z;!SIZM_@K5pJ!MJER^8UroDjA$oUsuawf^7;yKqgKzijsW?!F7+9X?Y(t_a_}Z z{-#VarXqjjt2Z~!uh1**mW*{HY|v>u9}!?gL}7uT@W{AKXtn?=%l>9KenZ128 zTh6H{xG>`!C!N{O7dlnIY()SCk1rHP)_qI6>&b;zRUZJ2GlVb3EG7Q_o%|2N!^rEP zHbI0a4%k8?G({)?lc)sr1it zkv$Gd$DhV%l(?yQ{tIRX6G>nDIOIj@Rtc{ zsb$)4g_&ig$wAd@)PEBh*a9jzRCp9L&Mz6{OI+|i)dFMwt<)@U@z#`#u8Evjsb0X%VXjJH-1FKBoA~~WX|B&xgO7&3Uk`mReCx!+yfMRqLF_kHj8-TS_VANl8;v(GMTx3%_O zdo8cffvlx2J?GbL%`dp(`0{jJm}l8EyS(MI=uU0uaaGi$9l!tSUQna36}EpA?4 z8GPz7rLV7dRa)C%g8Dvgjp^~12`+6~50Z5~vr}1O*+=c}}qi9}}vTkEi={4@D|Phgny^v=S?3 zat}gAq?hSjehnc1`(4k4R=yn0=8AWGQ}0F4q>LpT=Y$*L+v*A2UQ%-M`ZOAEE!ilw1q+Mx@2f z#9}MS*Fk-KzZ7l|P2U2h@Ztj^+aQ<0>%|(;)PU#DYf4b0FB18N>B;|ByCfL->rhcb7&+2VkRm3GO_(xepK@JXZ$g{VkldBzoIsnsS;ixu ziJv#mUP6eI#dk0IRk|s3bfVaDx!h@63(?kgjf@4{ z+44A)HRMTWy&&x2RQR%)-^TTEgMvC~k@2p)F`>1LN3<~Bp~&U@My4E)|Fm#1b1G>4 zdw5mU9{3C`oqr_h43oogRb0rCsv%A}_FB!s#?1t+pGqGl&F2|+e8ui%#Ddm;ly{9fLm=!P4P8zUa4c?)5!NL-f@x-s z@CHT0QQu!FjHjh=Tn1yHZ>inDZ?;<4PC>b)LuSx7ly!MP|DHW%it4c6@dL^>Ba4RV2P>qs*$#EWS|{ba zc}E+ns!;wzeZL1nurgyYD#Iy`uV7W|r&WtA2Hn1#8w^ zy1b}~P&DKja%oTFg~AG7j|(0R4uwHMw@)heZ!Q7cZu6hjcPbA>w$;k(2WmlG)zlvn zHyDvt9AAGZX!jXf*O}eNEDJcC@7wluUd&qrEkBgkv`2PB+*IdTKS`dPoL|>m!%THd z>ulP%%Q0;rOLN+F|-t})H6s%r119C}r_wYKOI zwP{0f-kJe--O)t+qq_3+}HPb1ae>HiJ=Pu}~?NJE>*SMrSAQ>Hxe`!37kM4M5^h67nh z7j5>kwSgId7w65JwdSPyeP{Qg=Npc}wk=}IpF@>|eLHMziLn(4`MLKU&BzxM76JOt zV^q>#rmtC}qA!3aW(;}CY4O6UtP0;<^bFy6up|EmuN}Um9uvw&5NUhHp~&lSo5ZSy zKsX@?5{+m!B5h=Sg)Rp4Ph_SER_1Uxza?s+qlJs0gWkVqUGKOdPF~N>+z0$Sj9Rl% z6Vc_EwsY-TCF}F+JsMPlpnGE?(mCN8x+N|Ve;FlUDPgPFIEFF50_(Va^_8EQ@ z$5aFW|Kim64e~zBzqo#=`aAu1QsyN1NFxm&iVDbQmB6v0Dv$O`)n??7-b&-EX|p$E zO(h>mpEu7kb8;*>yL(X`G;h@Fs4{ zlo~Xy;%yJLlr}e;h_VUpfa@|*tzmox8+XKO4l=C47W#?gYczWxiA)y?i4q`*#4_t* zjD>xD7R;$JZ}{r_@7NOw8iC88tss}UO8ES_m0%~uRZu$A%KwA02aHwbTnEZVphw`x z^(JlM>f8T^{e9gyg$+JGOKTX>ib%sE#7&f_nliKFvK@Y`I^yk>7&Y+O|sKC=Js4jvHVa^PwWkwV&rK=m(yQeffq+ z7hsc$g*@>}xWac9lqwQZi|&&5^*v-}C*zXU_wRFD z(_WU73I�sEslb`{_o+p3MRGmyKk7StqW&>O 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 literal 0 HcmV?d00001 diff --git a/indra/newview/app_settings/static_index.db2 b/indra/newview/app_settings/static_index.db2 new file mode 100644 index 0000000000000000000000000000000000000000..a5440f96f2b16bd93bb9eeffe561ea1db8b93c25 GIT binary patch literal 9894 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

KmtC<(>d4qd0zj` zNBA}wKNj4ZH;-@6%SnwtK3avL$9m(m%?PchW{s<8AOuQ)3X=FMu|7gCWpaB{xKn&Y zgN8f)-NikUnAaM*SEqewW0L7zN#bo~1J3nYNWcgAvB2o9eCFlUmg6mEdVFTz#|dyB zMXZ_B_~(4*J;l1^-{~W{7IWNZ9q?|el_VBd5Fd1%)$0f3Ja~V$eTH>?X;!w=5u$=( Tu1Tg_{HGecd?G%d`H%P?%$X-n literal 0 HcmV?d00001 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ea0b950e62..f518704e06 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -98,7 +98,6 @@ #include "lllogininstance.h" #include "llprogressview.h" #include "llvocache.h" -#include "lldiskcache.h" #include "llvopartgroup.h" #include "llweb.h" #include "llfloatertexturefetchdebugger.h" @@ -116,6 +115,8 @@ #include "llprimitive.h" #include "llurlaction.h" #include "llurlentry.h" +#include "llvfile.h" +#include "llvfsthread.h" #include "llvolumemgr.h" #include "llxfermanager.h" #include "llphysicsextensions.h" @@ -339,6 +340,9 @@ bool gUseWireframe = FALSE; //use for remember deferred mode in wireframe switch bool gInitialDeferredModeForWireframe = FALSE; +// VFS globals - see llappviewer.h +LLVFS* gStaticVFS = NULL; + LLMemoryInfo gSysMemory; U64Bytes gMemoryAllocated(0); // updated in display_stats() in llviewerdisplay.cpp @@ -427,6 +431,12 @@ void init_default_trans_args() default_trans_args.insert("create_account_url"); } +//---------------------------------------------------------------------------- +// File scope definitons +const char *VFS_DATA_FILE_BASE = "data.db2.x."; +const char *VFS_INDEX_FILE_BASE = "index.db2.x."; + + struct SettingsFile : public LLInitParam::Block { Mandatory name; @@ -961,6 +971,10 @@ bool LLAppViewer::init() // *Note: this is where gViewerStats used to be created. + // + // Initialize the VFS, and gracefully handle initialization errors + // + if (!initCache()) { LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL; @@ -1355,6 +1369,7 @@ static LLTrace::BlockTimerStatHandle FTM_TEXTURE_CACHE("Texture Cache"); static LLTrace::BlockTimerStatHandle FTM_DECODE("Image Decode"); static LLTrace::BlockTimerStatHandle FTM_FETCH("Image Fetch"); +static LLTrace::BlockTimerStatHandle FTM_VFS("VFS Thread"); static LLTrace::BlockTimerStatHandle FTM_LFS("LFS Thread"); static LLTrace::BlockTimerStatHandle FTM_PAUSE_THREADS("Pause Threads"); static LLTrace::BlockTimerStatHandle FTM_IDLE("Idle"); @@ -1588,6 +1603,10 @@ bool LLAppViewer::doFrame() work_pending += updateTextureThreads(max_time); + { + LL_RECORD_BLOCK_TIME(FTM_VFS); + io_pending += LLVFSThread::updateClass(1); + } { LL_RECORD_BLOCK_TIME(FTM_LFS); io_pending += LLLFSThread::updateClass(1); @@ -1595,7 +1614,7 @@ bool LLAppViewer::doFrame() if (io_pending > 1000) { - ms_sleep(llmin(io_pending/100,100)); // give the lfs some time to catch up + ms_sleep(llmin(io_pending/100,100)); // give the vfs some time to catch up } total_work_pending += work_pending ; @@ -1612,6 +1631,7 @@ bool LLAppViewer::doFrame() } if(!total_io_pending) //pause file threads if nothing to process. { + LLVFSThread::sLocal->pause(); LLLFSThread::sLocal->pause(); } @@ -1673,11 +1693,12 @@ S32 LLAppViewer::updateTextureThreads(F32 max_time) return work_pending; } -void LLAppViewer::flushLFSIO() +void LLAppViewer::flushVFSIO() { while (1) { - S32 pending = LLLFSThread::updateClass(0); + S32 pending = LLVFSThread::updateClass(0); + pending += LLLFSThread::updateClass(0); if (!pending) { break; @@ -1765,7 +1786,7 @@ bool LLAppViewer::cleanup() LLKeyframeDataCache::clear(); - // End TransferManager before deleting systems it depends on (Audio, AssetStorage) + // End TransferManager before deleting systems it depends on (Audio, VFS, AssetStorage) #if 0 // this seems to get us stuck in an infinite loop... gTransferManager.cleanup(); #endif @@ -1832,8 +1853,8 @@ bool LLAppViewer::cleanup() LL_INFOS() << "Cache files removed" << LL_ENDL; - // Wait for any pending LFS IO - flushLFSIO(); + // Wait for any pending VFS IO + flushVFSIO(); LL_INFOS() << "Shutting down Views" << LL_ENDL; // Destroy the UI @@ -1917,6 +1938,15 @@ bool LLAppViewer::cleanup() SUBSYSTEM_CLEANUP(LLWorldMapView); SUBSYSTEM_CLEANUP(LLFolderViewItem); + // + // Shut down the VFS's AFTER the decode manager cleans up (since it cleans up vfiles). + // Also after viewerwindow is deleted, since it may have image pointers (which have vfiles) + // Also after shutting down the messaging system since it has VFS dependencies + + // + LL_INFOS() << "Cleaning up VFS" << LL_ENDL; + SUBSYSTEM_CLEANUP(LLVFile); + LL_INFOS() << "Saving Data" << LL_ENDL; // Store the time of our current logoff @@ -2003,6 +2033,7 @@ bool LLAppViewer::cleanup() pending += LLAppViewer::getTextureCache()->update(1); // unpauses the worker thread pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread + pending += LLVFSThread::updateClass(0); pending += LLLFSThread::updateClass(0); F64 idle_time = idleTimer.getElapsedTimeF64(); if(!pending) @@ -2077,11 +2108,28 @@ bool LLAppViewer::cleanup() gTextureList.shutdown(); // shutdown again in case a callback added something LLUIImageList::getInstance()->cleanUp(); + // This should eventually be done in LLAppViewer SUBSYSTEM_CLEANUP(LLImage); + SUBSYSTEM_CLEANUP(LLVFSThread); SUBSYSTEM_CLEANUP(LLLFSThread); +#ifndef LL_RELEASE_FOR_DOWNLOAD + LL_INFOS() << "Auditing VFS" << LL_ENDL; + if(gVFS) + { + gVFS->audit(); + } +#endif + LL_INFOS() << "Misc Cleanup" << LL_ENDL; + // For safety, the LLVFS has to be deleted *after* LLVFSThread. This should be cleaned up. + // (LLVFS doesn't know about LLVFSThread so can't kill pending requests) -Steve + delete gStaticVFS; + gStaticVFS = NULL; + delete gVFS; + gVFS = NULL; + gSavedSettings.cleanup(); LLUIColorTable::instance().clear(); @@ -2163,6 +2211,7 @@ bool LLAppViewer::initThreads() LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); + LLVFSThread::initClass(enable_threads && false); LLLFSThread::initClass(enable_threads && false); // Image decoding @@ -3164,6 +3213,10 @@ LLSD LLAppViewer::getViewerInfo() const info["GPU_SHADERS"] = gSavedSettings.getBOOL("RenderDeferred") ? "Enabled" : "Disabled"; info["TEXTURE_MEMORY"] = gSavedSettings.getS32("TextureMemory"); + LLSD substitution; + substitution["datetime"] = (S32)(gVFS ? gVFS->creationTime() : 0); + info["VFS_TIME"] = LLTrans::getString("AboutTime", substitution); + #if LL_DARWIN info["HIDPI"] = gHiDPISupport; #endif @@ -3255,9 +3308,6 @@ LLSD LLAppViewer::getViewerInfo() const info["SERVER_RELEASE_NOTES_URL"] = mServerReleaseNotesURL; } - // populate field for new local disk cache with some details - info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); - return info; } @@ -3907,7 +3957,7 @@ void LLAppViewer::forceQuit() void LLAppViewer::fastQuit(S32 error_code) { // finish pending transfers - flushLFSIO(); + flushVFSIO(); // let sim know we're logging out sendLogoutRequest(); // flush network buffers by shutting down messaging system @@ -4096,6 +4146,39 @@ void LLAppViewer::migrateCacheDirectory() #endif // LL_WINDOWS || LL_DARWIN } +void dumpVFSCaches() +{ + LL_INFOS() << "======= Static VFS ========" << LL_ENDL; + gStaticVFS->listFiles(); +#if LL_WINDOWS + LL_INFOS() << "======= Dumping static VFS to StaticVFSDump ========" << LL_ENDL; + WCHAR w_str[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, w_str); + S32 res = LLFile::mkdir("StaticVFSDump"); + if (res == -1) + { + LL_WARNS() << "Couldn't create dir StaticVFSDump" << LL_ENDL; + } + SetCurrentDirectory(utf8str_to_utf16str("StaticVFSDump").c_str()); + gStaticVFS->dumpFiles(); + SetCurrentDirectory(w_str); +#endif + + LL_INFOS() << "========= Dynamic VFS ====" << LL_ENDL; + gVFS->listFiles(); +#if LL_WINDOWS + LL_INFOS() << "========= Dumping dynamic VFS to VFSDump ====" << LL_ENDL; + res = LLFile::mkdir("VFSDump"); + if (res == -1) + { + LL_WARNS() << "Couldn't create dir VFSDump" << LL_ENDL; + } + SetCurrentDirectory(utf8str_to_utf16str("VFSDump").c_str()); + gVFS->dumpFiles(); + SetCurrentDirectory(w_str); +#endif +} + //static U32 LLAppViewer::getTextureCacheVersion() { @@ -4122,19 +4205,6 @@ bool LLAppViewer::initCache() LLAppViewer::getTextureCache()->setReadOnly(read_only) ; LLVOCache::initParamSingleton(read_only); - // initialize the new disk cache using saved settings - const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName"); - - // note that the maximum size of this cache is defined as a percentage of the - // total cache size - the 'CacheSize' pref - for all caches. - const unsigned int cache_total_size_mb = gSavedSettings.getU32("CacheSize"); - const double disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal"); - const unsigned int disk_cache_mb = cache_total_size_mb * disk_cache_percent / 100; - const unsigned int disk_cache_bytes = disk_cache_mb * 1024 * 1024; - const bool enable_cache_debug_info = gSavedSettings.getBOOL("EnableDiskCacheDebugInfo"); - const std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name); - LLDiskCache::initParamSingleton(cache_dir, disk_cache_bytes, enable_cache_debug_info); - bool texture_cache_mismatch = false; if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion()) { @@ -4181,21 +4251,10 @@ bool LLAppViewer::initCache() gSavedSettings.setString("CacheLocationTopFolder", ""); } - if (!read_only) + if (mPurgeCache && !read_only) { - if (mPurgeCache) - { - LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); - purgeCache(); - - // clear the new C++ file system based cache - LLDiskCache::getInstance()->clearCache(); - } - else - { - // purge excessive files from the new file system based cache - LLDiskCache::getInstance()->purge(); - } + LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); + purgeCache(); } LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); @@ -4205,18 +4264,172 @@ bool LLAppViewer::initCache() const S32 MB = 1024 * 1024; const S64 MIN_CACHE_SIZE = 256 * MB; const S64 MAX_CACHE_SIZE = 9984ll * MB; + const S64 MAX_VFS_SIZE = 1024 * MB; // 1 GB S64 cache_size = (S64)(gSavedSettings.getU32("CacheSize")) * MB; cache_size = llclamp(cache_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE); - S64 texture_cache_size = cache_size; + S64 vfs_size = llmin((S64)((cache_size * 2) / 10), MAX_VFS_SIZE); + S64 texture_cache_size = cache_size - vfs_size; S64 extra = LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, texture_cache_mismatch); texture_cache_size -= extra; + LLVOCache::getInstance()->initCache(LL_PATH_CACHE, gSavedSettings.getU32("CacheNumberOfRegionsForObjects"), getObjectCacheVersion()); - return true; + LLSplashScreen::update(LLTrans::getString("StartupInitializingVFS")); + + // Init the VFS + vfs_size = llmin(vfs_size + extra, MAX_VFS_SIZE); + vfs_size = (vfs_size / MB) * MB; // make sure it is MB aligned + U32 vfs_size_u32 = (U32)vfs_size; + U32 old_vfs_size = gSavedSettings.getU32("VFSOldSize") * MB; + bool resize_vfs = (vfs_size_u32 != old_vfs_size); + if (resize_vfs) + { + gSavedSettings.setU32("VFSOldSize", vfs_size_u32 / MB); + } + LL_INFOS("AppCache") << "VFS CACHE SIZE: " << vfs_size / (1024*1024) << " MB" << LL_ENDL; + + // This has to happen BEFORE starting the vfs + // time_t ltime; + srand(time(NULL)); // Flawfinder: ignore + U32 old_salt = gSavedSettings.getU32("VFSSalt"); + U32 new_salt; + std::string old_vfs_data_file; + std::string old_vfs_index_file; + std::string new_vfs_data_file; + std::string new_vfs_index_file; + std::string static_vfs_index_file; + std::string static_vfs_data_file; + + if (gSavedSettings.getBOOL("AllowMultipleViewers")) + { + // don't mess with renaming the VFS in this case + new_salt = old_salt; + } + else + { + do + { + new_salt = rand(); + } while(new_salt == old_salt); + } + + old_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_DATA_FILE_BASE) + llformat("%u", old_salt); + + // make sure this file exists + llstat s; + S32 stat_result = LLFile::stat(old_vfs_data_file, &s); + if (stat_result) + { + // doesn't exist, look for a data file + std::string mask; + mask = VFS_DATA_FILE_BASE; + mask += "*"; + + std::string dir; + dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); + + std::string found_file; + LLDirIterator iter(dir, mask); + if (iter.next(found_file)) + { + old_vfs_data_file = gDirUtilp->add(dir, found_file); + + S32 start_pos = found_file.find_last_of('.'); + if (start_pos > 0) + { + sscanf(found_file.substr(start_pos+1).c_str(), "%d", &old_salt); + } + LL_DEBUGS("AppCache") << "Default vfs data file not present, found: " << old_vfs_data_file << " Old salt: " << old_salt << LL_ENDL; + } + } + + old_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_INDEX_FILE_BASE) + llformat("%u", old_salt); + + stat_result = LLFile::stat(old_vfs_index_file, &s); + if (stat_result) + { + // We've got a bad/missing index file, nukem! + LL_WARNS("AppCache") << "Bad or missing vfx index file " << old_vfs_index_file << LL_ENDL; + LL_WARNS("AppCache") << "Removing old vfs data file " << old_vfs_data_file << LL_ENDL; + LLFile::remove(old_vfs_data_file); + LLFile::remove(old_vfs_index_file); + + // Just in case, nuke any other old cache files in the directory. + std::string dir; + dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); + + std::string mask; + mask = VFS_DATA_FILE_BASE; + mask += "*"; + + gDirUtilp->deleteFilesInDir(dir, mask); + + mask = VFS_INDEX_FILE_BASE; + mask += "*"; + + gDirUtilp->deleteFilesInDir(dir, mask); + } + + new_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_DATA_FILE_BASE) + llformat("%u", new_salt); + new_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_INDEX_FILE_BASE) + llformat("%u", new_salt); + + static_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "static_data.db2"); + static_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "static_index.db2"); + + if (resize_vfs) + { + LL_DEBUGS("AppCache") << "Removing old vfs and re-sizing" << LL_ENDL; + + LLFile::remove(old_vfs_data_file); + LLFile::remove(old_vfs_index_file); + } + else if (old_salt != new_salt) + { + // move the vfs files to a new name before opening + LL_DEBUGS("AppCache") << "Renaming " << old_vfs_data_file << " to " << new_vfs_data_file << LL_ENDL; + LL_DEBUGS("AppCache") << "Renaming " << old_vfs_index_file << " to " << new_vfs_index_file << LL_ENDL; + LLFile::rename(old_vfs_data_file, new_vfs_data_file); + LLFile::rename(old_vfs_index_file, new_vfs_index_file); + } + + // Startup the VFS... + gSavedSettings.setU32("VFSSalt", new_salt); + + // Don't remove VFS after viewer crashes. If user has corrupt data, they can reinstall. JC + gVFS = LLVFS::createLLVFS(new_vfs_index_file, new_vfs_data_file, false, vfs_size_u32, false); + if (!gVFS) + { + return false; + } + + gStaticVFS = LLVFS::createLLVFS(static_vfs_index_file, static_vfs_data_file, true, 0, false); + if (!gStaticVFS) + { + return false; + } + + BOOL success = gVFS->isValid() && gStaticVFS->isValid(); + if (!success) + { + return false; + } + else + { + LLVFile::initClass(); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + if (gSavedSettings.getBOOL("DumpVFSCaches")) + { + dumpVFSCaches(); + } +#endif + + return true; + } } void LLAppViewer::addOnIdleCallback(const boost::function& cb) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 902b94d495..5332fe2deb 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -1,5 +1,4 @@ /** - * @mainpage * @mainpage * * This is the sources for the Second Life Viewer; @@ -83,7 +82,7 @@ public: virtual bool frame(); // Override for application body logic // Application control - void flushLFSIO(); // waits for lfs transfers to complete + void flushVFSIO(); // waits for vfs transfers to complete void forceQuit(); // Puts the viewer into 'shutting down without error' mode. void fastQuit(S32 error_code = 0); // Shuts down the viewer immediately after sending a logout message void requestQuit(); // Request a quit. A kinder, gentler quit. @@ -382,6 +381,12 @@ extern BOOL gRestoreGL; extern bool gUseWireframe; extern bool gInitialDeferredModeForWireframe; +// VFS globals - gVFS is for general use +// gStaticVFS is read-only and is shipped w/ the viewer +// it has pre-cache data like the UI .TGAs +class LLVFS; +extern LLVFS *gStaticVFS; + extern LLMemoryInfo gSysMemory; extern U64Bytes gMemoryAllocated; diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 84ffb3551d..9b1c0d1f8b 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -502,7 +502,7 @@ void LLAppViewerWin32::disableWinErrorReporting() } } -const S32 MAX_CONSOLE_LINES = 7500; +const S32 MAX_CONSOLE_LINES = 500; // Only defined in newer SDKs than we currently use #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4 diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index 5d010a6f1e..3aaaaf52f5 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -52,7 +52,7 @@ #include "lldir.h" #include "llnotificationsutil.h" #include "llviewerstats.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "lluictrlfactory.h" #include "lltrans.h" @@ -116,7 +116,7 @@ namespace } // *NOTE$: A minor specialization of LLScriptAssetUpload, it does not require a buffer -// (and does not save a buffer to the cache) and it finds the compile queue window and +// (and does not save a buffer to the vFS) and it finds the compile queue window and // displays a compiling message. class LLQueuedScriptAssetUpload : public LLScriptAssetUpload { @@ -134,8 +134,8 @@ public: virtual LLSD prepareUpload() { /* *NOTE$: The parent class (LLScriptAssetUpload will attempt to save - * the script buffer into to the cache. Since the resource is already in - * the cache we don't want to do that. Just put a compiling message in + * the script buffer into to the VFS. Since the resource is already in + * the VFS we don't want to do that. Just put a compiling message in * the window and move on */ LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", LLSD(mQueueId)); @@ -283,11 +283,11 @@ void LLFloaterCompileQueue::handleHTTPResponse(std::string pumpName, const LLSD LLEventPumps::instance().post(pumpName, expresult); } -// *TODO: handleSCriptRetrieval is passed into the cache via a legacy C function pointer +// *TODO: handleSCriptRetrieval is passed into the VFS via a legacy C function pointer // future project would be to convert these to C++ callables (std::function<>) so that // we can use bind and remove the userData parameter. // -void LLFloaterCompileQueue::handleScriptRetrieval(const LLUUID& assetId, +void LLFloaterCompileQueue::handleScriptRetrieval(LLVFS *vfs, const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus) { LLSD result(LLSD::emptyMap()); diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index a9bac345b5..adb854875a 100644 --- a/indra/newview/llcompilequeue.h +++ b/indra/newview/llcompilequeue.h @@ -135,7 +135,7 @@ protected: //bool checkAssetId(const LLUUID &assetId); static void handleHTTPResponse(std::string pumpName, const LLSD &expresult); - static void handleScriptRetrieval(const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); + static void handleScriptRetrieval(LLVFS *vfs, const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); private: static void processExperienceIdResults(LLSD result, LLUUID parent); diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 04ba4416d7..2fc496a144 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -136,7 +136,7 @@ public: S32 getFileCount() const { return (S32)mFiles.size(); } - // see lldir.h : getBaseFileName and getDirName to extract base or directory names + // See llvfs/lldir.h : getBaseFileName and getDirName to extract base or directory names // clear any lists of buffers or whatever, and make sure the file // picker isn't locked. diff --git a/indra/newview/llfloaterauction.cpp b/indra/newview/llfloaterauction.cpp index 9813156bf2..957b2e1e8e 100644 --- a/indra/newview/llfloaterauction.cpp +++ b/indra/newview/llfloaterauction.cpp @@ -32,7 +32,8 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "llparcel.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "llwindow.h" #include "message.h" @@ -201,9 +202,7 @@ void LLFloaterAuction::onClickSnapshot(void* data) LLPointer tga = new LLImageTGA; tga->encode(raw); - - LLFileSystem tga_file(self->mImageID, LLAssetType::AT_IMAGE_TGA, LLFileSystem::WRITE); - tga_file.write(tga->getData(), tga->getDataSize()); + LLVFile::writeFile(tga->getData(), tga->getDataSize(), gVFS, self->mImageID, LLAssetType::AT_IMAGE_TGA); raw->biasedScaleToPowerOfTwo(LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT); @@ -211,9 +210,7 @@ void LLFloaterAuction::onClickSnapshot(void* data) LLPointer j2c = new LLImageJ2C; j2c->encode(raw, 0.0f); - - LLFileSystem j2c_file(self->mImageID, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); - j2c_file.write(j2c->getData(), j2c->getDataSize()); + LLVFile::writeFile(j2c->getData(), j2c->getDataSize(), gVFS, self->mImageID, LLAssetType::AT_TEXTURE); self->mImage = LLViewerTextureManager::getLocalTexture((LLImageRaw*)raw, FALSE); gGL.getTexUnit(0)->bind(self->mImage); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 687d820a18..131d9b077b 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -32,7 +32,7 @@ #include "lldatapacker.h" #include "lldir.h" #include "llnotificationsutil.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llapr.h" #include "llstring.h" @@ -997,9 +997,10 @@ void LLFloaterBvhPreview::onBtnOK(void* userdata) LLDataPackerBinaryBuffer dp(buffer, file_size); if (motionp->serialize(dp)) { - LLFileSystem file(motionp->getID(), LLAssetType::AT_ANIMATION, LLFileSystem::APPEND); + LLVFile file(gVFS, motionp->getID(), LLAssetType::AT_ANIMATION, LLVFile::APPEND); S32 size = dp.getCurrentSize(); + file.setMaxSize(size); if (file.write((U8*)buffer, size)) { std::string name = floaterp->getChild("name_form")->getValue().asString(); diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 481a7dab66..b9c03f66a3 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -59,7 +59,6 @@ #include "llspinctrl.h" #include "lltabcontainer.h" #include "lltrans.h" -#include "llfilesystem.h" #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index f99d0e6150..526214a617 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -107,7 +107,7 @@ protected: void onBtnOK(const LLSD& userdata); void onBtnCancel(const LLSD& userdata); - void onClickClearCache(); // Clear viewer texture cache, file cache on next startup + void onClickClearCache(); // Clear viewer texture cache, vfs, and VO cache on next startup void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above void onLanguageChange(); void onNotificationsChange(const std::string& OptionName); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 0375c15467..ec1909d02a 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -36,7 +36,7 @@ #include "llglheaders.h" #include "llregionflags.h" #include "llstl.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llxfermanager.h" #include "indra_constants.h" #include "message.h" @@ -2229,9 +2229,10 @@ void LLPanelEstateCovenant::loadInvItem(LLInventoryItem *itemp) } // static -void LLPanelEstateCovenant::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPanelEstateCovenant::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_INFOS() << "LLPanelEstateCovenant::onLoadComplete()" << LL_ENDL; LLPanelEstateCovenant* panelp = (LLPanelEstateCovenant*)user_data; @@ -2239,7 +2240,7 @@ void LLPanelEstateCovenant::onLoadComplete(const LLUUID& asset_uuid, { if(0 == status) { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 file_length = file.getSize(); diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index c34dbb62e8..75d0c3ea5c 100644 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -55,6 +55,7 @@ class LLRadioGroup; class LLSliderCtrl; class LLSpinCtrl; class LLTextBox; +class LLVFS; class LLPanelRegionGeneralInfo; class LLPanelRegionDebugInfo; @@ -356,7 +357,8 @@ public: static bool confirmResetCovenantCallback(const LLSD& notification, const LLSD& response); void sendChangeCovenantID(const LLUUID &asset_id); void loadInvItem(LLInventoryItem *itemp); - static void onLoadComplete(const LLUUID& asset_uuid, + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 5d0e2bbc55..7bfba2a6d7 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -44,7 +44,8 @@ #include "llnotificationsutil.h" #include "llstring.h" #include "llsys.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "mean_collision_data.h" #include "message.h" #include "v3math.h" @@ -898,9 +899,12 @@ void LLFloaterReporter::takeScreenshot(bool use_prev_screenshot) mResourceDatap->mAssetInfo.setName("screenshot_name"); mResourceDatap->mAssetInfo.setDescription("screenshot_descr"); - // store in cache - LLFileSystem j2c_file(mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType, LLFileSystem::WRITE); - j2c_file.write(upload_data->getData(), upload_data->getDataSize()); + // store in VFS + LLVFile::writeFile(upload_data->getData(), + upload_data->getDataSize(), + gVFS, + mResourceDatap->mAssetInfo.mUuid, + mResourceDatap->mAssetInfo.mType); // store in the image list so it doesn't try to fetch from the server LLPointer image_in_list = diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index 1aeb727172..bd403f68d7 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -40,7 +40,7 @@ #include "lltextbox.h" #include "llui.h" #include "lluictrlfactory.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "message.h" #include "llstartup.h" // login_alert_done #include "llcorehttputil.h" diff --git a/indra/newview/llfloatertos.h b/indra/newview/llfloatertos.h index 7c2f0705b7..85033acf4d 100644 --- a/indra/newview/llfloatertos.h +++ b/indra/newview/llfloatertos.h @@ -36,6 +36,7 @@ class LLButton; class LLRadioGroup; +class LLVFS; class LLTextEditor; class LLUUID; diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index 9f2119281d..950a6cfaef 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -42,7 +42,7 @@ #include "llnotificationsutil.h" #include "llstl.h" #include "llstring.h" // todo: remove -#include "llfilesystem.h" +#include "llvfile.h" #include "message.h" // newview @@ -548,7 +548,7 @@ void LLGestureMgr::playGesture(LLMultiGesture* gesture) LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; const LLUUID& anim_id = anim_step->mAnimAssetID; - // Don't request the animation if this step stops it or if it is already in the cache + // Don't request the animation if this step stops it or if it is already in Static VFS if (!(anim_id.isNull() || anim_step->mFlags & ANIM_FLAG_STOP || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION))) @@ -1038,9 +1038,10 @@ void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step) // static -void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLGestureMgr::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLLoadInfo* info = (LLLoadInfo*)user_data; @@ -1055,7 +1056,7 @@ void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, if (0 == status) { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 size = file.getSize(); std::vector buffer(size+1); @@ -1158,7 +1159,8 @@ void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, } // static -void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, +void LLGestureMgr::onAssetLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -1170,7 +1172,7 @@ void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, { case LLAssetType::AT_ANIMATION: { - LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status); + LLKeyframeMotion::onLoadComplete(vfs, asset_uuid, type, user_data, status, ext_status); self.mLoadingAssets.erase(asset_uuid); @@ -1178,7 +1180,7 @@ void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, } case LLAssetType::AT_SOUND: { - LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status); + LLAudioEngine::assetCallback(vfs, asset_uuid, type, user_data, status, ext_status); self.mLoadingAssets.erase(asset_uuid); diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index 91ab445273..402bdf6039 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -40,6 +40,7 @@ class LLMultiGesture; class LLGestureListener; class LLGestureStep; class LLUUID; +class LLVFS; class LLGestureManagerObserver { @@ -153,13 +154,15 @@ protected: void done(); // Used by loadGesture - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); // Used by playGesture to load an asset file // required to play a gesture step - static void onAssetLoadComplete(const LLUUID& asset_uuid, + static void onAssetLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index 2966ca1f10..b4236c406b 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -33,7 +33,7 @@ #include "llappviewer.h" #include "llagent.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llviewerstats.h" // Globals @@ -118,6 +118,7 @@ LLLandmark* LLLandmarkList::getAsset(const LLUUID& asset_uuid, loaded_callback_t // static void LLLandmarkList::processGetAssetReply( + LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void* user_data, @@ -126,7 +127,7 @@ void LLLandmarkList::processGetAssetReply( { if( status == 0 ) { - LLFileSystem file(uuid, type); + LLVFile file(vfs, uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length + 1); diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h index 0e4859dbc9..2e7bd25610 100644 --- a/indra/newview/lllandmarklist.h +++ b/indra/newview/lllandmarklist.h @@ -52,6 +52,7 @@ public: BOOL assetExists(const LLUUID& asset_uuid); LLLandmark* getAsset(const LLUUID& asset_uuid, loaded_callback_t cb = NULL); static void processGetAssetReply( + LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void* user_data, diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 8e5bdc0225..3e8731dfe6 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -49,7 +49,7 @@ #include "llsdutil_math.h" #include "llsdserialize.h" #include "llthread.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" @@ -294,6 +294,8 @@ // * Header parse failures come without much explanation. Elaborate. // * Work queue for uploads? Any need for this or is the current scheme good // enough? +// * Various temp buffers used in VFS I/O might be allocated once or even +// statically. Look for some wins here. // * Move data structures holding mesh data used by main thread into main- // thread-only access so that no locking is needed. May require duplication // of some data so that worker thread has a minimal data set to guide @@ -1334,8 +1336,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + //check VFS for mesh skin info + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1368,7 +1370,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) delete[] buffer; } - //reading from cache failed for whatever reason, fetch from sim + //reading from VFS failed for whatever reason, fetch from sim std::string http_url; constructUrl(mesh_id, &http_url); @@ -1430,8 +1432,8 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + //check VFS for mesh skin info + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1465,7 +1467,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) delete[] buffer; } - //reading from cache failed for whatever reason, fetch from sim + //reading from VFS failed for whatever reason, fetch from sim std::string http_url; constructUrl(mesh_id, &http_url); @@ -1527,8 +1529,8 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check cache for mesh physics shape info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + //check VFS for mesh physics shape info + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; @@ -1561,7 +1563,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) delete[] buffer; } - //reading from cache failed for whatever reason, fetch from sim + //reading from VFS failed for whatever reason, fetch from sim std::string http_url; constructUrl(mesh_id, &http_url); @@ -1632,8 +1634,8 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c ++LLMeshRepository::sMeshRequestCount; { - //look for mesh in asset in cache - LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); + //look for mesh in asset in vfs + LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH); S32 size = file.getSize(); @@ -1647,7 +1649,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) { - // Found mesh in cache + // Found mesh in VFS cache return true; } } @@ -1711,8 +1713,8 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check cache for mesh asset - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + //check VFS for mesh asset + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1747,7 +1749,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, delete[] buffer; } - //reading from cache failed for whatever reason, fetch from sim + //reading from VFS failed for whatever reason, fetch from sim std::string http_url; constructUrl(mesh_id, &http_url); @@ -3206,7 +3208,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b } else if (data && data_size > 0) { - // header was successfully retrieved from sim and parsed and is in cache + // header was successfully retrieved from sim and parsed, cache in vfs S32 header_bytes = 0; LLSD header; @@ -3245,16 +3247,31 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b // It's possible for the remote asset to have more data than is needed for the local cache - // only allocate as much space in the cache as is needed for the local cache + // only allocate as much space in the VFS as is needed for the local cache data_size = llmin(data_size, bytes); - LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); - if (file.getMaxSize() >= bytes) + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); + if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) { LLMeshRepository::sCacheBytesWritten += data_size; ++LLMeshRepository::sCacheWrites; file.write(data, data_size); + + // zero out the rest of the file + U8 block[MESH_HEADER_SIZE]; + memset(block, 0, sizeof(block)); + + while (bytes-file.tell() > sizeof(block)) + { + file.write(block, sizeof(block)); + } + + S32 remaining = bytes-file.tell(); + if (remaining > 0) + { + file.write(block, remaining); + } } } else @@ -3306,8 +3323,8 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); if (result == MESH_OK) { - // good fetch from sim, write to cache - LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3370,8 +3387,8 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) { - // good fetch from sim, write to cache - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3418,8 +3435,8 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) { - // good fetch from sim, write to cache - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3465,8 +3482,8 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S3 && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) { - // good fetch from sim, write to cache for caching - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 441264d42f..81e49cb1d8 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -48,6 +48,7 @@ class LLVOVolume; class LLMutex; class LLCondition; +class LLVFS; class LLMeshRepository; typedef enum e_mesh_processing_result_enum diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 90f6d23a61..272e7ae351 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -32,7 +32,7 @@ // llcommon #include "llcommonutils.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llaccordionctrltab.h" #include "llappearancemgr.h" diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index ce5c090134..6dd8a6298f 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -38,6 +38,7 @@ #include +class LLVFS; class LLOutfitGallery; class LLOutfitGalleryItem; class LLOutfitListGearMenuBase; diff --git a/indra/newview/llpostcard.cpp b/indra/newview/llpostcard.cpp index 071fc31d27..d5775042c1 100644 --- a/indra/newview/llpostcard.cpp +++ b/indra/newview/llpostcard.cpp @@ -28,7 +28,8 @@ #include "llpostcard.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "llviewerregion.h" #include "message.h" diff --git a/indra/newview/llpreviewgesture.cpp b/indra/newview/llpreviewgesture.cpp index 4318a55704..70ce275734 100644 --- a/indra/newview/llpreviewgesture.cpp +++ b/indra/newview/llpreviewgesture.cpp @@ -30,7 +30,7 @@ #include "llagent.h" #include "llanimstatelabels.h" #include "llanimationstates.h" -#include "llappviewer.h" +#include "llappviewer.h" // gVFS #include "llcheckboxctrl.h" #include "llcombobox.h" #include "lldatapacker.h" @@ -47,7 +47,7 @@ #include "llradiogroup.h" #include "llresmgr.h" #include "lltrans.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewerstats.h" @@ -841,9 +841,10 @@ void LLPreviewGesture::loadAsset() // static -void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPreviewGesture::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLUUID* item_idp = (LLUUID*)user_data; @@ -852,7 +853,7 @@ void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid, { if (0 == status) { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 size = file.getSize(); std::vector buffer(size+1); @@ -1137,9 +1138,10 @@ void LLPreviewGesture::saveIfNeeded() tid.generate(); assetId = tid.makeAssetID(gAgent.getSecureSessionID()); - LLFileSystem file(assetId, LLAssetType::AT_GESTURE, LLFileSystem::APPEND); + LLVFile file(gVFS, assetId, LLAssetType::AT_GESTURE, LLVFile::APPEND); S32 size = dp.getCurrentSize(); + file.setMaxSize(size); file.write((U8*)buffer, size); LLLineEditor* descEditor = getChild("desc"); diff --git a/indra/newview/llpreviewgesture.h b/indra/newview/llpreviewgesture.h index 19bccf35bd..3ba4f56295 100644 --- a/indra/newview/llpreviewgesture.h +++ b/indra/newview/llpreviewgesture.h @@ -39,6 +39,7 @@ class LLScrollListCtrl; class LLScrollListItem; class LLButton; class LLRadioGroup; +class LLVFS; class LLPreviewGesture : public LLPreview { @@ -79,7 +80,8 @@ protected: void loadAsset(); - static void onLoadComplete(const LLUUID& asset_uuid, + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index a7bb5c8236..1b60610668 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -46,7 +46,7 @@ #include "llselectmgr.h" #include "lltrans.h" #include "llviewertexteditor.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llviewerinventory.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" @@ -327,7 +327,8 @@ void LLPreviewNotecard::loadAsset() } // static -void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid, +void LLPreviewNotecard::onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -338,7 +339,7 @@ void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid, { if(0 == status) { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 file_length = file.getSize(); @@ -445,7 +446,7 @@ void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", LLSD(itemId)); if (nc) { - // *HACK: we have to delete the asset in the cache so + // *HACK: we have to delete the asset in the VFS so // that the viewer will redownload it. This is only // really necessary if the asset had to be modified by // the uploader, so this can be optimized away in some @@ -453,7 +454,7 @@ void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, // script actually changed the asset. if (nc->hasEmbeddedInventory()) { - LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); + gVFS->removeFile(newAssetId, LLAssetType::AT_NOTECARD); } if (newItemId.isNull()) { @@ -478,7 +479,7 @@ void LLPreviewNotecard::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUI { if (nc->hasEmbeddedInventory()) { - LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); + gVFS->removeFile(newAssetId, LLAssetType::AT_NOTECARD); } nc->setAssetId(newAssetId); nc->refreshFromInventory(); @@ -557,13 +558,14 @@ bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem, bool sync) tid.generate(); asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - LLFileSystem file(asset_id, LLAssetType::AT_NOTECARD, LLFileSystem::APPEND); + LLVFile file(gVFS, asset_id, LLAssetType::AT_NOTECARD, LLVFile::APPEND); LLSaveNotecardInfo* info = new LLSaveNotecardInfo(this, mItemUUID, mObjectUUID, tid, copyitem); S32 size = buffer.length() + 1; + file.setMaxSize(size); file.write((U8*)buffer.c_str(), size); gAssetStorage->storeAssetData(tid, LLAssetType::AT_NOTECARD, diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h index 063a01ca47..d9c14815c1 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -83,7 +83,8 @@ protected: void deleteNotecard(); - static void onLoadComplete(const LLUUID& asset_uuid, + static void onLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 379bc9871c..5e81fa6402 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -51,7 +51,7 @@ #include "llsdserialize.h" #include "llslider.h" #include "lltooldraganddrop.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llagent.h" #include "llmenugl.h" @@ -1685,11 +1685,8 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) { std::string buffer(mScriptEd->mEditor->getText()); - LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, - [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { - LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); + [](LLUUID itemId, LLUUID, LLUUID, LLSD response) { LLPreviewLSL::finishedLSLUpload(itemId, response); })); @@ -1699,8 +1696,8 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) } // static -void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPreviewLSL::onLoadComplete( LLVFS *vfs, const LLUUID& asset_uuid, LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS() << "LLPreviewLSL::onLoadComplete: got uuid " << asset_uuid << LL_ENDL; @@ -1710,7 +1707,7 @@ void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType t { if(0 == status) { - LLFileSystem file(asset_uuid, type); + LLVFile file(vfs, asset_uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length+1); @@ -1737,7 +1734,6 @@ void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType t } preview->mScriptEd->setScriptName(script_name); preview->mScriptEd->setEnableEditing(is_modifiable); - preview->mScriptEd->setAssetID(asset_uuid); preview->mAssetStatus = PREVIEW_ASSET_LOADED; } else @@ -1974,7 +1970,7 @@ void LLLiveLSLEditor::loadAsset() } // static -void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, +void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -1988,10 +1984,9 @@ void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, { if( LL_ERR_NOERR == status ) { - instance->loadScriptText(asset_id, type); + instance->loadScriptText(vfs, asset_id, type); instance->mScriptEd->setEnableEditing(TRUE); instance->mAssetStatus = PREVIEW_ASSET_LOADED; - instance->mScriptEd->setAssetID(asset_id); } else { @@ -2015,9 +2010,9 @@ void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, delete floater_key; } -void LLLiveLSLEditor::loadScriptText(const LLUUID &uuid, LLAssetType::EType type) +void LLLiveLSLEditor::loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) { - LLFileSystem file(uuid, type); + LLVFile file(vfs, uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length + 1); file.read((U8*)&buffer[0], file_length); @@ -2171,7 +2166,6 @@ void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAs if (preview) { preview->mItem->setAssetUUID(newAssetId); - preview->mScriptEd->setAssetID(newAssetId); // Bytecode save completed if (response["compiled"]) @@ -2242,14 +2236,12 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) if (!url.empty()) { std::string buffer(mScriptEd->mEditor->getText()); - LLUUID old_asset_id = mScriptEd->getAssetID(); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mObjectUUID, mItemUUID, monoChecked() ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, isRunning, mScriptEd->getAssociatedExperience(), buffer, - [isRunning, old_asset_id](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { - LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); - LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); + [isRunning](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { + LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); })); LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 18c10ab365..3cf22a0e6e 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -48,6 +48,7 @@ struct LLEntryAndEdCore; class LLMenuBarGL; class LLFloaterScriptSearch; class LLKeywordToken; +class LLVFS; class LLViewerInventoryItem; class LLScriptEdContainer; class LLFloaterGotoLine; @@ -142,9 +143,6 @@ public: void setItemRemoved(bool script_removed){mScriptRemoved = script_removed;}; - void setAssetID( const LLUUID& asset_id){ mAssetID = asset_id; }; - LLUUID getAssetID() { return mAssetID; } - private: void onBtnDynamicHelp(); void onBtnUndoChanges(); @@ -190,7 +188,6 @@ private: LLUUID mAssociatedExperience; BOOL mScriptRemoved; BOOL mSaveDialogShown; - LLUUID mAssetID; LLScriptEdContainer* mContainer; // parent view @@ -237,7 +234,7 @@ protected: static void onLoad(void* userdata); static void onSave(void* userdata, BOOL close_after_save); - static void onLoadComplete(const LLUUID& uuid, + static void onLoadComplete(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); @@ -298,13 +295,13 @@ private: static void onLoad(void* userdata); static void onSave(void* userdata, BOOL close_after_save); - static void onLoadComplete(const LLUUID& asset_uuid, + static void onLoadComplete(LLVFS *vfs, const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); static void onRunningCheckboxClicked(LLUICtrl*, void* userdata); static void onReset(void* userdata); - void loadScriptText(const LLUUID &uuid, LLAssetType::EType type); + void loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); static void onErrorList(LLUICtrl*, void* user_data); diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index 58cbe0e20a..1e5b893cbc 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -57,7 +57,7 @@ #include "llinventorymodel.h" #include "llassetstorage.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "lldrawpoolwater.h" #include @@ -292,18 +292,18 @@ void LLSettingsVOBase::onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, L void LLSettingsVOBase::getSettingsAsset(const LLUUID &assetId, LLSettingsVOBase::asset_download_fn callback) { gAssetStorage->getAssetData(assetId, LLAssetType::AT_SETTINGS, - [callback](const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) - { onAssetDownloadComplete(asset_id, status, ext_status, callback); }, + [callback](LLVFS *vfs, const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) + { onAssetDownloadComplete(vfs, asset_id, status, ext_status, callback); }, nullptr, true); } -void LLSettingsVOBase::onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) +void LLSettingsVOBase::onAssetDownloadComplete(LLVFS *vfs, const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) { LLSettingsBase::ptr_t settings; if (!status) { - LLFileSystem file(asset_id, LLAssetType::AT_SETTINGS, LLFileSystem::READ); + LLVFile file(vfs, asset_id, LLAssetType::AT_SETTINGS, LLVFile::READ); S32 size = file.getSize(); std::string buffer(size + 1, '\0'); diff --git a/indra/newview/llsettingsvo.h b/indra/newview/llsettingsvo.h index a1baf02fe7..65136ad2f5 100644 --- a/indra/newview/llsettingsvo.h +++ b/indra/newview/llsettingsvo.h @@ -38,6 +38,7 @@ #include "llextendedstatus.h" #include +class LLVFS; class LLInventoryItem; class LLGLSLShader; @@ -80,7 +81,7 @@ private: static void onAgentAssetUploadComplete(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback); static void onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback); - static void onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, asset_download_fn callback); + static void onAssetDownloadComplete(LLVFS *vfs, const LLUUID &asset_id, S32 status, LLExtStat ext_status, asset_download_fn callback); }; //========================================================================= diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index 8134187c21..8369def968 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -31,7 +31,6 @@ #include "llagentbenefits.h" #include "llagentcamera.h" #include "llagentui.h" -#include "llfilesystem.h" #include "llcombobox.h" #include "llfloaterperms.h" #include "llfloaterreg.h" @@ -51,6 +50,8 @@ #include "llviewercontrol.h" #include "llviewermenufile.h" // upload_new_resource() #include "llviewerstats.h" +#include "llvfile.h" +#include "llvfs.h" #include "llwindow.h" #include "llworld.h" #include @@ -1005,8 +1006,7 @@ void LLSnapshotLivePreview::saveTexture(BOOL outfit_snapshot, std::string name) if (formatted->encode(scaled, 0.0f)) { - LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); - fmt_file.write(formatted->getData(), formatted->getDataSize()); + LLVFile::writeFile(formatted->getData(), formatted->getDataSize(), gVFS, new_asset_id, LLAssetType::AT_TEXTURE); std::string pos_string; LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); std::string who_took_it; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index f281b30a8e..17777c3ceb 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -82,6 +82,7 @@ #include "llversioninfo.h" #include "llviewercontrol.h" #include "llviewerhelp.h" +#include "llvfs.h" #include "llxorcipher.h" // saved password, MAC address #include "llwindow.h" #include "message.h" @@ -267,7 +268,7 @@ bool login_alert_status(const LLSD& notification, const LLSD& response); void login_packet_failed(void**, S32 result); void use_circuit_callback(void**, S32 result); void register_viewer_callbacks(LLMessageSystem* msg); -void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); +void asset_callback_nothing(LLVFS*, const LLUUID&, LLAssetType::EType, void*, S32); bool callback_choose_gender(const LLSD& notification, const LLSD& response); void init_start_screen(S32 location_id); void release_start_screen(); @@ -579,7 +580,7 @@ bool idle_startup() // start the xfer system. by default, choke the downloads // a lot... const S32 VIEWER_MAX_XFER = 3; - start_xfer_manager(); + start_xfer_manager(gVFS); gXferManager->setMaxIncomingXfers(VIEWER_MAX_XFER); F32 xfer_throttle_bps = gSavedSettings.getF32("XferThrottle"); if (xfer_throttle_bps > 1.f) @@ -587,7 +588,7 @@ bool idle_startup() gXferManager->setUseAckThrottling(TRUE); gXferManager->setAckThrottleBPS(xfer_throttle_bps); } - gAssetStorage = new LLViewerAssetStorage(msg, gXferManager); + gAssetStorage = new LLViewerAssetStorage(msg, gXferManager, gVFS, gStaticVFS); F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); @@ -1003,6 +1004,13 @@ bool idle_startup() gViewerWindow->revealIntroPanel(); + // Poke the VFS, which could potentially block for a while if + // Windows XP is acting up + set_startup_status(0.07f, LLTrans::getString("LoginVerifyingCache"), LLStringUtil::null); + display_startup(); + + gVFS->pokeFiles(); + LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); return FALSE; @@ -2581,7 +2589,7 @@ void register_viewer_callbacks(LLMessageSystem* msg) msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message); } -void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32) +void asset_callback_nothing(LLVFS*, const LLUUID&, LLAssetType::EType, void*, S32) { // nothing } diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index d4fc6f3de2..a9f19dc32d 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -55,7 +55,6 @@ const S32 TEXTURE_FAST_CACHE_ENTRY_OVERHEAD = sizeof(S32) * 4; //w, h, c, level const S32 TEXTURE_FAST_CACHE_DATA_SIZE = 16 * 16 * 4; const S32 TEXTURE_FAST_CACHE_ENTRY_SIZE = TEXTURE_FAST_CACHE_DATA_SIZE + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD; const F32 TEXTURE_LAZY_PURGE_TIME_LIMIT = .004f; // 4ms. Would be better to autoadjust, but there is a major cache rework in progress. -const F32 TEXTURE_PRUNING_MAX_TIME = 15.f; class LLTextureCacheWorker : public LLWorkerClass { @@ -1552,6 +1551,7 @@ void LLTextureCache::readHeaderCache() if (num_entries - empty_entries > sCacheMaxEntries) { // Special case: cache size was reduced, need to remove entries + // Note: After we prune entries, we will call this again and create the LRU U32 entries_to_purge = (num_entries - empty_entries) - sCacheMaxEntries; LL_INFOS() << "Texture Cache Entries: " << num_entries << " Max: " << sCacheMaxEntries << " Empty: " << empty_entries << " Purging: " << entries_to_purge << LL_ENDL; // We can exit the following loop with the given condition, since if we'd reach the end of the lru set we'd have: @@ -1564,7 +1564,7 @@ void LLTextureCache::readHeaderCache() ++iter; } } - + else { S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE); for (std::set::iterator iter = lru.begin(); iter != lru.end(); ++iter) @@ -1578,19 +1578,30 @@ void LLTextureCache::readHeaderCache() if (purge_list.size() > 0) { - LLTimer timer; for (std::set::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter) { std::string tex_filename = getTextureFileName(entries[*iter].mID); removeEntry((S32)*iter, entries[*iter], tex_filename); - - //make sure that pruning entries doesn't take too much time - if (timer.getElapsedTimeF32() > TEXTURE_PRUNING_MAX_TIME) + } + // If we removed any entries, we need to rebuild the entries list, + // write the header, and call this again + std::vector new_entries; + for (U32 i=0; i 0) { - break; + new_entries.push_back(entry); } } - writeEntriesAndClose(entries); + mFreeList.clear(); // recreating list, no longer valid. + llassert_always(new_entries.size() <= sCacheMaxEntries); + mHeaderEntriesInfo.mEntries = new_entries.size(); + writeEntriesHeader(); + writeEntriesAndClose(new_entries); + mHeaderMutex.unlock(); // unlock the mutex before calling again + readHeaderCache(); // repeat with new entries file + mHeaderMutex.lock(); } else { @@ -1713,7 +1724,7 @@ void LLTextureCache::purgeTexturesLazy(F32 time_limit_sec) } S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; + S64 purged_cache_size = (sCacheMaxTexturesSize * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; for (time_idx_set_t::iterator iter = time_idx_set.begin(); iter != time_idx_set.end(); ++iter) { @@ -1809,26 +1820,21 @@ void LLTextureCache::purgeTextures(bool validate) } S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; + S64 purged_cache_size = (sCacheMaxTexturesSize * (S64)((1.f-TEXTURE_CACHE_PURGE_AMOUNT)*100)) / 100; S32 purge_count = 0; for (time_idx_set_t::iterator iter = time_idx_set.begin(); iter != time_idx_set.end(); ++iter) { S32 idx = iter->second; bool purge_entry = false; - - if (cache_size >= purged_cache_size) - { - purge_entry = true; - } - else if (validate) + if (validate) { // make sure file exists and is the correct size U32 uuididx = entries[idx].mID.mData[0]; if (uuididx == validate_idx) { - std::string filename = getTextureFileName(entries[idx].mID); - LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; + std::string filename = getTextureFileName(entries[idx].mID); + LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; // mHeaderAPRFilePoolp because this is under header mutex in main thread S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); if (bodysize != entries[idx].mBodySize) @@ -1838,6 +1844,10 @@ void LLTextureCache::purgeTextures(bool validate) } } } + else if (cache_size >= purged_cache_size) + { + purge_entry = true; + } else { break; diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 5b76d57196..54f80a2995 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -28,7 +28,8 @@ #include "llviewerassetstorage.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "message.h" #include "llagent.h" @@ -103,8 +104,10 @@ public: ///---------------------------------------------------------------------------- // Unused? -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) - : LLAssetStorage(msg, xfer, upstream_host), +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs, + const LLHost &upstream_host) + : LLAssetStorage(msg, xfer, vfs, static_vfs, upstream_host), mAssetCoroCount(0), mCountRequests(0), mCountStarted(0), @@ -114,8 +117,10 @@ LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager * { } -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) - : LLAssetStorage(msg, xfer), + +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs) + : LLAssetStorage(msg, xfer, vfs, static_vfs), mAssetCoroCount(0), mCountRequests(0), mCountStarted(0), @@ -152,13 +157,13 @@ void LLViewerAssetStorage::storeAssetData( if (mUpstreamHost.isOk()) { - if (LLFileSystem::getExists(asset_id, asset_type)) + if (mVFS->getExists(asset_id, asset_type)) { // Pack data into this packet if we can fit it. U8 buffer[MTUBYTES]; buffer[0] = 0; - LLFileSystem vfile(asset_id, asset_type, LLFileSystem::READ); + LLVFile vfile(mVFS, asset_id, asset_type, LLVFile::READ); S32 asset_size = vfile.getSize(); LLAssetRequest *req = new LLAssetRequest(asset_id, asset_type); @@ -167,20 +172,22 @@ void LLViewerAssetStorage::storeAssetData( if (asset_size < 1) { - // This can happen if there's a bug in our code or if the cache has been corrupted. - LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the cache, but it's not! " << asset_id << LL_ENDL; + // This can happen if there's a bug in our code or if the VFS has been corrupted. + LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the VFS, but it's not! " << asset_id << LL_ENDL; + // LLAssetStorage metric: Zero size VFS + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (VFS - can't tell which)" ); delete req; if (callback) { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::CACHE_CORRUPT); + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::VFS_CORRUPT); } return; } else { // LLAssetStorage metric: Successful Request - S32 size = LLFileSystem::getFileSize(asset_id, asset_type); + S32 size = mVFS->getSize(asset_id, asset_type); const char *message = "Added to upload queue"; reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); @@ -194,7 +201,7 @@ void LLViewerAssetStorage::storeAssetData( } } - // Read the data from the cache if it'll fit in this packet. + // Read the data from the VFS if it'll fit in this packet. if (asset_size + 100 < MTUBYTES) { BOOL res = vfile.read(buffer, asset_size); /* Flawfinder: ignore */ @@ -207,11 +214,14 @@ void LLViewerAssetStorage::storeAssetData( } else { - LL_WARNS("AssetStorage") << "Probable corruption in cache file, aborting store asset data" << LL_ENDL; + LL_WARNS("AssetStorage") << "Probable corruption in VFS file, aborting store asset data" << LL_ENDL; + + // LLAssetStorage metric: VFS corrupt - bogus size + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, asset_size, MR_VFS_CORRUPTION, __FILE__, __LINE__, "VFS corruption" ); if (callback) { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::CACHE_CORRUPT); + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::VFS_CORRUPT); } return; } @@ -234,7 +244,8 @@ void LLViewerAssetStorage::storeAssetData( else { LL_WARNS("AssetStorage") << "AssetStorage: attempt to upload non-existent vfile " << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (cache - can't tell which)" ); + // LLAssetStorage metric: Zero size VFS + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (VFS - can't tell which)" ); if (callback) { callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::NONEXISTENT_FILE); @@ -267,6 +278,7 @@ void LLViewerAssetStorage::storeAssetData( if(filename.empty()) { // LLAssetStorage metric: no filename + reportMetric( LLUUID::null, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_VFS_CORRUPTION, __FILE__, __LINE__, "Filename missing" ); LL_ERRS() << "No filename specified" << LL_ENDL; return; } @@ -291,7 +303,9 @@ void LLViewerAssetStorage::storeAssetData( legacy->mUpCallback = callback; legacy->mUserData = user_data; - LLFileSystem file(asset_id, asset_type, LLFileSystem::WRITE); + LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE); + + file.setMaxSize(size); const S32 buf_size = 65536; U8 copy_buf[buf_size]; @@ -525,20 +539,21 @@ void LLViewerAssetStorage::assetRequestCoro( // case. LLUUID temp_id; temp_id.generate(); - LLFileSystem vf(temp_id, atype, LLFileSystem::WRITE); + LLVFile vf(gAssetStorage->mVFS, temp_id, atype, LLVFile::WRITE); + vf.setMaxSize(size); req->mBytesFetched = size; if (!vf.write(raw.data(),size)) { // TODO asset-http: handle error LL_WARNS("ViewerAsset") << "Failure in vf.write()" << LL_ENDL; result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::CACHE_CORRUPT; + ext_status = LLExtStat::VFS_CORRUPT; } else if (!vf.rename(uuid, atype)) { LL_WARNS("ViewerAsset") << "rename failed" << LL_ENDL; result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::CACHE_CORRUPT; + ext_status = LLExtStat::VFS_CORRUPT; } else { diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index 972c89de34..ef01d179b7 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -30,16 +30,18 @@ #include "llassetstorage.h" #include "llcorehttputil.h" -class LLFileSystem; +class LLVFile; class LLViewerAssetRequest; class LLViewerAssetStorage : public LLAssetStorage { public: - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host); - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, + LLVFS *vfs, LLVFS *static_vfs); ~LLViewerAssetStorage(); diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 1923e7d6ff..bc07821ccd 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -45,7 +45,7 @@ #include "llviewerassetupload.h" #include "llappviewer.h" #include "llviewerstats.h" -#include "llfilesystem.h" +#include "llvfile.h" #include "llgesturemgr.h" #include "llpreviewnotecard.h" #include "llpreviewgesture.h" @@ -467,13 +467,15 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() setAssetType(assetType); - // copy this file into the cache for upload + // copy this file into the vfs for upload S32 file_size; LLAPRFile infile; infile.open(filename, LL_APR_RB, NULL, &file_size); if (infile.getFileHandle()) { - LLFileSystem file(getAssetId(), assetType, LLFileSystem::WRITE); + LLVFile file(gVFS, getAssetId(), assetType, LLVFile::WRITE); + + file.setMaxSize(file_size); const S32 buf_size = 65536; U8 copy_buf[buf_size]; @@ -505,7 +507,7 @@ LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType: mContents(buffer), mInvnFinishFn(finish), mTaskFinishFn(nullptr), - mStoredToCache(false) + mStoredToVFS(false) { setItemId(itemId); setAssetType(assetType); @@ -519,7 +521,7 @@ LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLPointerrenameFile(getAssetId(), assetType, newAssetId, assetType); } if (mTaskUpload) diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h index e56ba7d8f7..d9eacf3167 100644 --- a/indra/newview/llviewerassetupload.h +++ b/indra/newview/llviewerassetupload.h @@ -197,7 +197,7 @@ private: std::string mContents; invnUploadFinish_f mInvnFinishFn; taskUploadFinish_f mTaskFinishFn; - bool mStoredToCache; + bool mStoredToVFS; }; //------------------------------------------------------------------------- diff --git a/indra/newview/llviewermedia_streamingaudio.cpp b/indra/newview/llviewermedia_streamingaudio.cpp index d3e24aece5..3ccf3070ab 100644 --- a/indra/newview/llviewermedia_streamingaudio.cpp +++ b/indra/newview/llviewermedia_streamingaudio.cpp @@ -33,8 +33,10 @@ #include "llviewermedia_streamingaudio.h" #include "llmimetypes.h" +#include "llvfs.h" #include "lldir.h" + LLStreamingAudio_MediaPlugins::LLStreamingAudio_MediaPlugins() : mMediaPlugin(NULL), mGain(1.0) diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index e59a263adc..4b231c7067 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -53,6 +53,8 @@ #include "llviewercontrol.h" // gSavedSettings #include "llviewertexturelist.h" #include "lluictrlfactory.h" +#include "llvfile.h" +#include "llvfs.h" #include "llviewerinventory.h" #include "llviewermenu.h" // gMenuHolder #include "llviewerparcelmgr.h" diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 6ef452bd92..458fc3b13d 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -44,7 +44,8 @@ #include "llteleportflags.h" #include "lltoastnotifypanel.h" #include "lltransactionflags.h" -#include "llfilesystem.h" +#include "llvfile.h" +#include "llvfs.h" #include "llxfermanager.h" #include "mean_collision_data.h" @@ -6843,15 +6844,16 @@ void process_covenant_reply(LLMessageSystem* msg, void**) } } -void onCovenantLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void onCovenantLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS("Messaging") << "onCovenantLoadComplete()" << LL_ENDL; std::string covenant_text; if(0 == status) { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + LLVFile file(vfs, asset_uuid, type, LLVFile::READ); S32 file_length = file.getSize(); diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h index 1e5a69ae13..78829a6a56 100644 --- a/indra/newview/llviewermessage.h +++ b/indra/newview/llviewermessage.h @@ -47,6 +47,7 @@ class LLInventoryObject; class LLInventoryItem; class LLMeanCollisionData; class LLMessageSystem; +class LLVFS; class LLViewerObject; class LLViewerRegion; @@ -188,7 +189,8 @@ void process_script_dialog(LLMessageSystem* msg, void**); void process_load_url(LLMessageSystem* msg, void**); void process_script_teleport_request(LLMessageSystem* msg, void**); void process_covenant_reply(LLMessageSystem* msg, void**); -void onCovenantLoadComplete(const LLUUID& asset_uuid, +void onCovenantLoadComplete(LLVFS *vfs, + const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h index e378c2448a..bbbacce8fa 100644 --- a/indra/newview/llviewerprecompiledheaders.h +++ b/indra/newview/llviewerprecompiledheaders.h @@ -101,6 +101,7 @@ #include "v4math.h" #include "xform.h" +// Library includes from llvfs #include "lldir.h" // Library includes from llmessage project diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 35fb4de5a9..05f88b0a75 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -33,6 +33,7 @@ #include "llfloaterreg.h" #include "llmemory.h" #include "lltimer.h" +#include "llvfile.h" #include "llappviewer.h" @@ -144,6 +145,7 @@ LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"), VISIBLE_AVATARS("visibleavatars", "Visible Avatars"), SHADER_OBJECTS("shaderobjects", "Object Shaders"), DRAW_DISTANCE("drawdistance", "Draw Distance"), + PENDING_VFS_OPERATIONS("vfspendingoperations"), WINDOW_WIDTH("windowwidth", "Window width"), WINDOW_HEIGHT("windowheight", "Window height"); @@ -381,6 +383,7 @@ void update_statistics() F64Bits layer_bits = gVLManager.getLandBits() + gVLManager.getWindBits() + gVLManager.getCloudBits(); add(LLStatViewer::LAYERS_NETWORK_DATA_RECEIVED, layer_bits); add(LLStatViewer::OBJECT_NETWORK_DATA_RECEIVED, gObjectData); + sample(LLStatViewer::PENDING_VFS_OPERATIONS, LLVFile::getVFSThread()->getPending()); add(LLStatViewer::ASSET_UDP_DATA_RECEIVED, F64Bits(gTransferManager.getTransferBitsIn(LLTCT_ASSET))); gTransferManager.resetTransferBitsIn(LLTCT_ASSET); diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 72ce336664..04870e0c26 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -186,6 +186,7 @@ extern LLTrace::SampleStatHandle<> FPS_SAMPLE, VISIBLE_AVATARS, SHADER_OBJECTS, DRAW_DISTANCE, + PENDING_VFS_OPERATIONS, WINDOW_WIDTH, WINDOW_HEIGHT; diff --git a/indra/newview/llviewertexlayer.cpp b/indra/newview/llviewertexlayer.cpp index 4c2fbcf837..c501dd0035 100644 --- a/indra/newview/llviewertexlayer.cpp +++ b/indra/newview/llviewertexlayer.cpp @@ -31,6 +31,8 @@ #include "llagent.h" #include "llimagej2c.h" #include "llnotificationsutil.h" +#include "llvfile.h" +#include "llvfs.h" #include "llviewerregion.h" #include "llglslshader.h" #include "llvoavatarself.h" diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index d69ab1b110..20a22ba45e 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -39,6 +39,8 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "llstl.h" +#include "llvfile.h" +#include "llvfs.h" #include "message.h" #include "lltimer.h" diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 07997e02a5..69568cc825 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -54,7 +54,7 @@ class LLTexturePipelineTester ; typedef void (*loaded_callback_func)( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata ); -class LLFileSystem; +class LLVFile; class LLMessageSystem; class LLViewerMediaImpl ; class LLVOVolume ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 38fccba169..561319ca5d 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -41,7 +41,9 @@ #include "llsdserialize.h" #include "llsys.h" -#include "llfilesystem.h" +#include "llvfs.h" +#include "llvfile.h" +#include "llvfsthread.h" #include "llxmltree.h" #include "message.h" diff --git a/indra/newview/llviewerwearable.cpp b/indra/newview/llviewerwearable.cpp index 9a4607bb60..9c4dfd1ca2 100644 --- a/indra/newview/llviewerwearable.cpp +++ b/indra/newview/llviewerwearable.cpp @@ -107,6 +107,7 @@ LLWearable::EImportResult LLViewerWearable::importStream( std::istream& input_st // Shouldn't really log the asset id for security reasons, but // we need it in this case. LL_WARNS() << "Bad Wearable asset header: " << mAssetID << LL_ENDL; + //gVFS->dumpMap(); return result; } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 414a1da4fe..f69b9b3861 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1140,6 +1140,7 @@ void LLVOAvatar::initInstance() //------------------------------------------------------------------------- if (LLCharacter::sInstances.size() == 1) { + LLKeyframeMotion::setVFS(gStaticVFS); registerMotion( ANIM_AGENT_DO_NOT_DISTURB, LLNullMotion::create ); registerMotion( ANIM_AGENT_CROUCH, LLKeyframeStandMotion::create ); registerMotion( ANIM_AGENT_CROUCHWALK, LLKeyframeWalkMotion::create ); diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index 73eeec813c..ce400a3498 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -218,9 +218,10 @@ public: void updateSculptTexture(); void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;} void sculpt(); - static void rebuildMeshAssetCallback(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); + static void rebuildMeshAssetCallback(LLVFS *vfs, + const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); void updateRelativeXform(bool force_identity = false); /*virtual*/ BOOL updateGeometry(LLDrawable *drawable); diff --git a/indra/newview/skins/default/xui/da/floater_stats.xml b/indra/newview/skins/default/xui/da/floater_stats.xml index d07f9e48ca..fe3fa9626e 100644 --- a/indra/newview/skins/default/xui/da/floater_stats.xml +++ b/indra/newview/skins/default/xui/da/floater_stats.xml @@ -32,6 +32,7 @@ + diff --git a/indra/newview/skins/default/xui/da/strings.xml b/indra/newview/skins/default/xui/da/strings.xml index 814305c1bc..ec6ba4800d 100644 --- a/indra/newview/skins/default/xui/da/strings.xml +++ b/indra/newview/skins/default/xui/da/strings.xml @@ -22,6 +22,9 @@ Initialiserer tekstur cache... + + Initialiserer VFS... + Gendanner... diff --git a/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml index a3749f1cfa..dff462a594 100644 --- a/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/de/floater_stats.xml b/indra/newview/skins/default/xui/de/floater_stats.xml index 1838c2081a..4e6f56cd94 100644 --- a/indra/newview/skins/default/xui/de/floater_stats.xml +++ b/indra/newview/skins/default/xui/de/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index f021e03dc7..43327c132d 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -31,6 +31,9 @@ Textur-Cache wird initialisiert... + + VFS wird initialisiert... + Grafikinitialisierung fehlgeschlagen. Bitte aktualisieren Sie Ihren Grafiktreiber. @@ -70,6 +73,7 @@ LOD-Faktor: [LOD_FACTOR] Darstellungsqualität: [RENDER_QUALITY] Erweitertes Beleuchtungsmodell: [GPU_SHADERS] Texturspeicher: [TEXTURE_MEMORY] MB +Erstellungszeit VFS (Cache): [VFS_TIME] HiDPI-Anzeigemodus: [HIDPI] diff --git a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml index 35d4385487..62cce3a1e3 100644 --- a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml @@ -206,6 +206,12 @@ bar_max="1024.f" tick_spacing="128.f" precision="1" + show_bar="false"/> + diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index f9d44b2c69..e4f735740b 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -198,6 +198,10 @@ stat="messagedataout" decimal_digits="1" show_history="false"/> + diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 619c869a3d..f9f12e7f5c 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -17,6 +17,7 @@ Loading [APP_NAME]... Clearing cache... Initializing texture cache... + Initializing VFS... Graphics initialization failed. Please update your graphics driver! @@ -55,9 +56,9 @@ LOD factor: [LOD_FACTOR] Render quality: [RENDER_QUALITY] Advanced Lighting Model: [GPU_SHADERS] Texture memory: [TEXTURE_MEMORY]MB -Disk cache: [DISK_CACHE_INFO] +VFS (cache) creation time: [VFS_TIME] - + HiDPI display mode: [HIDPI] diff --git a/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml index cfc5e524b5..f625d5257c 100644 --- a/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/es/floater_stats.xml b/indra/newview/skins/default/xui/es/floater_stats.xml index 0aec786f25..d1c5e867db 100644 --- a/indra/newview/skins/default/xui/es/floater_stats.xml +++ b/indra/newview/skins/default/xui/es/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index ebb4ceaa7e..f5e7d0bf4e 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -22,6 +22,9 @@ Iniciando la caché de las texturas... + + Iniciando VFS... + Error de inicialización de gráficos. Actualiza tu controlador de gráficos. @@ -62,6 +65,7 @@ Factor LOD: [LOD_FACTOR] Calidad de renderización: [RENDER_QUALITY] Modelo de iluminación avanzado: [GPU_SHADERS] Memoria de textura: [TEXTURE_MEMORY]MB +VFS (cache) hora de creación: [VFS_TIME] Modo de visualización HiDPi: [HIDPI] diff --git a/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml index 3889b13f0c..62830054bf 100644 --- a/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/fr/floater_stats.xml b/indra/newview/skins/default/xui/fr/floater_stats.xml index d0f7f42939..fae17e3608 100644 --- a/indra/newview/skins/default/xui/fr/floater_stats.xml +++ b/indra/newview/skins/default/xui/fr/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 9fde703d6c..f26eac545a 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -31,6 +31,9 @@ Initialisation du cache des textures... + + Initialisation VFS... + Échec d'initialisation des graphiques. Veuillez mettre votre pilote graphique à jour. @@ -71,6 +74,7 @@ Facteur LOD (niveau de détail) : [LOD_FACTOR] Qualité de rendu : [RENDER_QUALITY] Modèle d’éclairage avancé : [GPU_SHADERS] Mémoire textures : [TEXTURE_MEMORY] Mo +Heure de création VFS (cache) : [VFS_TIME] Mode d'affichage HiDPI : [HIDPI] diff --git a/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml index efceb05298..ca18812eb7 100644 --- a/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/it/floater_stats.xml b/indra/newview/skins/default/xui/it/floater_stats.xml index 6434c3b66b..7ef13aa37f 100644 --- a/indra/newview/skins/default/xui/it/floater_stats.xml +++ b/indra/newview/skins/default/xui/it/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 3049828f46..f0466cea81 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -28,6 +28,9 @@ Inizializzazione della cache texture... + + Inizializzazione VFS... + Inizializzazione grafica non riuscita. Aggiorna il driver della scheda grafica! @@ -68,6 +71,7 @@ Fattore livello di dettaglio: [LOD_FACTOR] Qualità di rendering: [RENDER_QUALITY] Modello illuminazione avanzato: [GPU_SHADERS] Memoria texture: [TEXTURE_MEMORY]MB +Data/ora creazione VFS (cache): [VFS_TIME] Modalità display HiDPI: [HIDPI] diff --git a/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml index f348ce3c4d..f6edce026f 100644 --- a/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/ja/floater_stats.xml b/indra/newview/skins/default/xui/ja/floater_stats.xml index 1da0e5ebc9..3bc343639b 100644 --- a/indra/newview/skins/default/xui/ja/floater_stats.xml +++ b/indra/newview/skins/default/xui/ja/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index dcd6e65d34..52d6fb0c2b 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -31,6 +31,9 @@ テクスチャキャッシュを初期化中です... + + VFS を初期化中です... + グラフィックを初期化できませんでした。グラフィックドライバを更新してください。 @@ -71,6 +74,7 @@ LOD 係数: [LOD_FACTOR] 描画の質: [RENDER_QUALITY] 高度なライティングモデル: [GPU_SHADERS] テクスチャメモリ: [TEXTURE_MEMORY]MB +VFS(キャッシュ)作成時間: [VFS_TIME] HiDPI 表示モード: [HIDPI] diff --git a/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml index 8f5d0c5c70..6fdc7e19f6 100644 --- a/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/pl/floater_stats.xml b/indra/newview/skins/default/xui/pl/floater_stats.xml index 21e37717c2..5dd7d19bab 100644 --- a/indra/newview/skins/default/xui/pl/floater_stats.xml +++ b/indra/newview/skins/default/xui/pl/floater_stats.xml @@ -55,6 +55,7 @@ + diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index cf033df3c9..91fea234d2 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -15,6 +15,9 @@ Inicjowanie bufora danych tekstur... + + Inicjowanie wirtualnego systemu plików... + Nie można zainicjować grafiki. Zaktualizuj sterowniki! diff --git a/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml index dbaab1d782..027e1ef311 100644 --- a/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/pt/floater_stats.xml b/indra/newview/skins/default/xui/pt/floater_stats.xml index 3253984268..f41fe17778 100644 --- a/indra/newview/skins/default/xui/pt/floater_stats.xml +++ b/indra/newview/skins/default/xui/pt/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index c72a41fd3a..ee982b5b22 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -22,6 +22,9 @@ Iniciando cache de texturas... + + Iniciando VFS... + Falha na inicialização dos gráficos. Atualize seu driver gráfico! @@ -62,6 +65,7 @@ LOD fator: [LOD_FACTOR] Qualidade de renderização: [RENDER_QUALITY] Modelo avançado de luzes: [GPU_SHADERS] Memória de textura: [TEXTURE_MEMORY]MB +VFS (cache) tempo de criação: [VFS_TIME] HiDPI modo de exibição: [HIDPI] diff --git a/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml index c4f432023c..a101e62627 100644 --- a/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/ru/floater_stats.xml b/indra/newview/skins/default/xui/ru/floater_stats.xml index a7d26029c2..10e9f5a7f4 100644 --- a/indra/newview/skins/default/xui/ru/floater_stats.xml +++ b/indra/newview/skins/default/xui/ru/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 43a87b2b18..e9592a0476 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -31,6 +31,9 @@ Инициализация кэша текстур... + + Инициализация виртуальной файловой системы... + Ошибка инициализации графики. Обновите графический драйвер! @@ -71,6 +74,7 @@ SLURL: <nolink>[SLURL]</nolink> Качество визуализации: [RENDER_QUALITY] Расширенная модель освещения: [GPU_SHADERS] Память текстур: [TEXTURE_MEMORY] МБ +Время создания VFS (кэш): [VFS_TIME] Режим отображения HiDPI: [HIDPI] diff --git a/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml index 7d5f4adb02..ae0a94595d 100644 --- a/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/tr/floater_stats.xml b/indra/newview/skins/default/xui/tr/floater_stats.xml index bd36d4916f..1ae42ad382 100644 --- a/indra/newview/skins/default/xui/tr/floater_stats.xml +++ b/indra/newview/skins/default/xui/tr/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index 982de76a5b..56fad978f5 100644 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -31,6 +31,9 @@ Doku önbelleği başlatılıyor... + + VFS Başlatılıyor... + Grafik başlatma başarılamadı. Lütfen grafik sürücünüzü güncelleştirin! @@ -71,6 +74,7 @@ LOD faktörü: [LOD_FACTOR] İşleme kalitesi: [RENDER_QUALITY] Gelişmiş Aydınlatma Modeli: [GPU_SHADERS] Doku belleği: [TEXTURE_MEMORY]MB +VFS (önbellek) oluşturma saati: [VFS_TIME] HiDPI görüntü modu: [HIDPI] diff --git a/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml index 20344e299f..1a5c20abeb 100644 --- a/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml @@ -29,6 +29,7 @@ + diff --git a/indra/newview/skins/default/xui/zh/floater_stats.xml b/indra/newview/skins/default/xui/zh/floater_stats.xml index e233ece527..f06eb5e78f 100644 --- a/indra/newview/skins/default/xui/zh/floater_stats.xml +++ b/indra/newview/skins/default/xui/zh/floater_stats.xml @@ -56,6 +56,7 @@ + diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml index 3221cde3b7..e6c61a5d94 100644 --- a/indra/newview/skins/default/xui/zh/strings.xml +++ b/indra/newview/skins/default/xui/zh/strings.xml @@ -31,6 +31,9 @@ 正在初始化材質快取... + + VFS 初始化中... + 顯像初始化失敗。 請更新你的顯像卡驅動程式! @@ -71,6 +74,7 @@ 呈像品質:[RENDER_QUALITY] 進階照明模型:[GPU_SHADERS] 材質記憶體:[TEXTURE_MEMORY]MB +VFS(快取)建立時間:[VFS_TIME] HiDPI顯示模式:[HIDPI] diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 8eee583a48..6d231040f7 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -69,6 +69,7 @@ class ViewerManifest(LLManifest): self.exclude("logcontrol-dev.xml") self.path("*.ini") self.path("*.xml") + self.path("*.db2") # include the entire shaders directory recursively self.path("shaders") diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index b3bff3611a..87536e146b 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -8,11 +8,12 @@ include(LLCoreHttp) include(LLInventory) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLXML) include(Linking) include(Tut) include(LLAddBuildTest) + include(GoogleMock) include_directories( @@ -22,7 +23,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS} ${GOOGLEMOCK_INCLUDE_DIRS} @@ -88,7 +89,7 @@ target_link_libraries(lltest ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLMATH_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt index 1b187d624a..86aa655f03 100644 --- a/indra/win_crash_logger/CMakeLists.txt +++ b/indra/win_crash_logger/CMakeLists.txt @@ -8,7 +8,7 @@ include(LLCoreHttp) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLFileSystem) +include(LLVFS) include(LLWindow) include(LLXML) include(Linking) @@ -23,7 +23,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLFILESYSTEM_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} ${BREAKPAD_INCLUDE_DIRECTORIES} ) include_directories(SYSTEM @@ -76,7 +76,7 @@ target_link_libraries(windows-crash-logger ${BREAKPAD_EXCEPTION_HANDLER_LIBRARIES} ${LLCRASHLOGGER_LIBRARIES} ${LLWINDOW_LIBRARIES} - ${LLFILESYSTEM_LIBRARIES} + ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLMATH_LIBRARIES} From bfd4abbf920a238724ffa0500a8a782b069facae Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Mon, 8 Mar 2021 13:58:35 +0000 Subject: [PATCH 118/125] Increment viewer version to 6.4.16 following revert of DRTVWR-519 --- indra/newview/VIEWER_VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 9cc52c876b..da32c8395e 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.4.15 +6.4.16 From d78797a1ea7955496d87f86c5c0ac5f297faebad Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 8 Mar 2021 19:35:05 +0100 Subject: [PATCH 119/125] Fix the fix --- indra/llwindow/llkeyboardmacosx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp index 6eba84af5a..0e9ad2e7a2 100644 --- a/indra/llwindow/llkeyboardmacosx.cpp +++ b/indra/llwindow/llkeyboardmacosx.cpp @@ -136,7 +136,7 @@ LLKeyboardMacOSX::LLKeyboardMacOSX() // Change to U32 for SDL2 //std::map::iterator iter; //for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) - for (autoiter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + for (auto iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } From 89ffb9fde7a5b782d685a53f5388d33a7473a975 Mon Sep 17 00:00:00 2001 From: Nicky Date: Mon, 8 Mar 2021 20:15:39 +0100 Subject: [PATCH 120/125] FIRE-30811; Fix SLD2 input. --- indra/llwindow/llkeyboardsdl.cpp | 1 - indra/llwindow/llwindowsdl.cpp | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 7416b4f7b2..0fee20afc7 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -79,7 +79,6 @@ LLKeyboardSDL::LLKeyboardSDL() mTranslateKeyMap[SDLK_RIGHT] = KEY_RIGHT; mTranslateKeyMap[SDLK_UP] = KEY_UP; mTranslateKeyMap[SDLK_DOWN] = KEY_DOWN; - mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; mTranslateKeyMap[SDLK_KP_ENTER] = KEY_RETURN; mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; mTranslateKeyMap[SDLK_BACKSPACE] = KEY_BACKSPACE; diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 1d842755b0..df699a2af2 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1659,7 +1659,12 @@ void LLWindowSDL::gatherInput() case SDL_KEYDOWN: mKeyVirtualKey = event.key.keysym.sym; mKeyModifiers = event.key.keysym.mod; - + if( mKeyVirtualKey == SDLK_KP_ENTER ) + { + mKeyVirtualKey = SDLK_RETURN; + mKeyModifiers &= ~KMOD_NUM; + } + gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers ); // Slightly hacky :| To make the viewer honor enter (eg to accept form input) we've to not only send handleKeyDown but also send a @@ -1677,6 +1682,11 @@ void LLWindowSDL::gatherInput() case SDL_KEYUP: mKeyVirtualKey = event.key.keysym.sym; mKeyModifiers = event.key.keysym.mod; + if( mKeyVirtualKey == SDLK_KP_ENTER ) + { + mKeyVirtualKey = SDLK_RETURN; + mKeyModifiers &= ~KMOD_NUM; + } if (SDLCheckGrabbyKeys(mKeyVirtualKey, FALSE) == 0) SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 From 589defd48ab71853063e90ba14defb585c211e8e Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Tue, 9 Mar 2021 03:24:00 +0100 Subject: [PATCH 121/125] FIRE-30813 Russian translation update, by Romka Swallowtail --- .../skins/default/xui/ru/panel_preferences_graphics1.xml | 2 +- .../skins/default/xui/ru/panel_settings_sky_atmos.xml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indra/newview/skins/default/xui/ru/panel_preferences_graphics1.xml b/indra/newview/skins/default/xui/ru/panel_preferences_graphics1.xml index fc0ac4abe2..942327fe5b 100644 --- a/indra/newview/skins/default/xui/ru/panel_preferences_graphics1.xml +++ b/indra/newview/skins/default/xui/ru/panel_preferences_graphics1.xml @@ -107,7 +107,7 @@ - + Сглаживание (рекомендуется перезапуск): diff --git a/indra/newview/skins/default/xui/ru/panel_settings_sky_atmos.xml b/indra/newview/skins/default/xui/ru/panel_settings_sky_atmos.xml index 13fb79c219..ca6ac3b0b8 100644 --- a/indra/newview/skins/default/xui/ru/panel_settings_sky_atmos.xml +++ b/indra/newview/skins/default/xui/ru/panel_settings_sky_atmos.xml @@ -11,14 +11,14 @@ Дымка горизонта: Плотность дымки: - Уровень влажности: + Уровень влаги: Радиус капли: Уровень льда: - Множитель плотности: - Множитель расстояния: - Максимальная высота: + Множ. плотности: + Множ. дистанции: + Макс. высота: Гамма сцены: From 493200bca5c851150d4b8d37466d6fb640569efc Mon Sep 17 00:00:00 2001 From: Nicky Date: Tue, 9 Mar 2021 10:40:01 +0100 Subject: [PATCH 122/125] Undo Linux updates. --- autobuild.xml | 106 +- indra/cmake/00-Common.cmake | 16 +- indra/cmake/APR.cmake | 2 +- indra/cmake/Boost.cmake | 22 +- indra/cmake/BuildPackagesInfo.cmake | 4 - indra/cmake/CEFPlugin.cmake | 19 +- indra/cmake/ColladaDom.cmake | 19 +- indra/cmake/Copy3rdPartyLibs.cmake | 8 +- indra/cmake/DBusGlib.cmake | 13 +- indra/cmake/FindAPR.cmake | 96 +- indra/cmake/FindFMODSTUDIO.cmake | 17 +- indra/cmake/FindGLH.cmake | 4 +- indra/cmake/FindGLOD.cmake | 48 +- indra/cmake/FindGoogleBreakpad.cmake | 9 +- indra/cmake/FindHUNSPELL.cmake | 40 +- indra/cmake/FindJsonCpp.cmake | 59 +- indra/cmake/FindKDU.cmake | 45 +- indra/cmake/FindNGHTTP2.cmake | 34 +- indra/cmake/FindOpenJPEG.cmake | 51 +- indra/cmake/FindURIPARSER.cmake | 45 +- indra/cmake/FindZLIB.cmake | 47 +- indra/cmake/GLIB.cmake | 11 - indra/cmake/GLOD.cmake | 9 +- indra/cmake/GStreamer010Plugin.cmake | 9 +- indra/cmake/GStreamer10Plugin.cmake | 32 - indra/cmake/GoogleBreakpad.cmake | 10 +- indra/cmake/GoogleMock.cmake | 57 +- indra/cmake/LLPhysicsExtensions.cmake | 3 +- indra/cmake/LLPrimitive.cmake | 107 +- indra/cmake/LLWindow.cmake | 5 +- indra/cmake/UI.cmake | 37 +- indra/cmake/jemalloc.cmake | 4 +- indra/linux_crash_logger/CMakeLists.txt | 4 - indra/llcommon/CMakeLists.txt | 2 - indra/llfilesystem/lldir_linux.cpp | 10 +- indra/llplugin/llpluginsharedmemory.cpp | 11 +- indra/llplugin/slplugin/CMakeLists.txt | 4 - indra/llrender/llglslshader.cpp | 1 - indra/llwindow/CMakeLists.txt | 9 +- indra/llwindow/llkeyboard.cpp | 11 +- indra/llwindow/llkeyboard.h | 13 +- indra/llwindow/llkeyboardheadless.cpp | 15 +- indra/llwindow/llkeyboardheadless.h | 5 - indra/llwindow/llkeyboardsdl.cpp | 355 +----- indra/llwindow/llkeyboardsdl.h | 15 +- indra/llwindow/llkeyboardwin32.cpp | 10 +- indra/llwindow/llwindowsdl.cpp | 1020 ++++++++++------- indra/llwindow/llwindowsdl.h | 24 +- indra/media_plugins/CMakeLists.txt | 13 +- indra/media_plugins/cef/media_plugin_cef.cpp | 31 +- .../media_plugins/gstreamer010/CMakeLists.txt | 4 +- .../media_plugin_gstreamer010.cpp | 9 + .../media_plugins/gstreamer10/CMakeLists.txt | 78 -- .../gstreamer10/llmediaimplgstreamer.h | 53 - .../gstreamer10/llmediaimplgstreamer_syms.cpp | 197 ---- .../gstreamer10/llmediaimplgstreamer_syms.h | 68 -- .../llmediaimplgstreamer_syms_raw.inc | 68 -- .../gstreamer10/media_plugin_gstreamer10.cpp | 980 ---------------- indra/newview/CMakeLists.txt | 23 +- indra/newview/desktopnotifierlinux.cpp | 24 +- indra/newview/exoflickr.cpp | 5 + indra/newview/linux_tools/wrapper.sh | 11 - indra/newview/llappviewer.cpp | 27 +- indra/newview/llappviewerlinux.cpp | 19 +- indra/newview/llappviewerlinux.h | 14 +- indra/newview/lldirpicker.cpp | 41 +- indra/newview/lldirpicker.h | 2 +- indra/newview/llfilepicker.cpp | 103 +- indra/newview/llfilepicker.h | 17 +- indra/newview/llgroupmgr.cpp | 9 - .../skins/default/xui/en/mime_types_linux.xml | 30 +- indra/newview/viewer_manifest.py | 131 +-- 72 files changed, 1458 insertions(+), 2996 deletions(-) delete mode 100644 indra/cmake/GLIB.cmake delete mode 100644 indra/cmake/GStreamer10Plugin.cmake delete mode 100644 indra/media_plugins/gstreamer10/CMakeLists.txt delete mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer.h delete mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp delete mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h delete mode 100644 indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc delete mode 100644 indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp diff --git a/autobuild.xml b/autobuild.xml index 2f98545a84..bcb424112c 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3,91 +3,7 @@ installables - glib - - copyright - Copyright (C) glib project - license - LGPL - license_file - LICENSES/glib.txt - name - glib - platforms - - linux64 - - archive - - hash - 9c93ba8b8af97fc8379f77de77e1540a - url - http://3p.firestormviewer.org/glib-2.48.0.202301938-linux64-202301938.tar.bz2 - - name - linux - - - version - 2.48.0 - - fltk - - copyright - Copyright (C) fltk project - license - LGPL/fltk - license_file - LICENSES/fltk.txt - name - fltk - platforms - - linux64 - - archive - - hash - 81fe1e927e4fe3c5e5f15ce6219ca883 - url - http://3p.firestormviewer.org/fltk-1.3.5.202282121-linux64-202282121.tar.bz2 - - name - linux - - - version - 1.3.5 - - gstreamer10 - - copyright - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - license - LGPL - license_file - LICENSES/gstreamer.txt - name - gstreamer10 - platforms - - linux - - archive - - hash - 01f39ecf80dae64e30402ac384035b3e - url - http://downloads.phoenixviewer.com/gstreamer10-1.6.3.201605191852-linux-201605191852.tar.bz2 - - name - linux - - - version - 0.10.6.294903 - - jemalloc + jemalloc copyright Copyright (C) 2002-present Jason Evans jasone@canonware.com. @@ -334,9 +250,9 @@ archive hash - 1d4819f2944d572667b6a009bfadd8e0 + 64c1dff0e19792acec7fd32556bf4d7b url - http://3p.firestormviewer.org/SDL-2.0.10-linux64-192791159.tar.bz2 + http://3p.firestormviewer.org/SDL-1.2.15-linux64-181411635.tar.bz2 name linux64 @@ -2893,14 +2809,26 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors open-libndofdev platforms + linux + + archive + + hash + 246ed298944fd5200e4cac3703d6e075 + url + http://3p.firestormviewer.org/open_libndofdev-0.9.183150124-linux-183150124.tar.bz2 + + name + linux + linux64 archive hash - e52e1223fe1603485178ac65d6562a61 + db8a7126178e7230fd6917a28cd67bd7 url - http://3p.firestormviewer.org/open_libndofdev-0.12.193591719-linux64-193591719.tar.bz2 + http://3p.firestormviewer.org/open_libndofdev-0.9.183150121-linux64-183150121.tar.bz2 name linux64 diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 8cf08950a7..ce8b396cc1 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -189,10 +189,6 @@ if (LINUX) -pthread ) - if( FS_KEEP_FRAMEPOINTER ) - add_compile_options(-fno-omit-frame-pointer) - endif() - # force this platform to accept TOS via external browser No, do not. # add_definitions(-DEXTERNAL_TOS) @@ -215,7 +211,6 @@ if (LINUX) set(CMAKE_EXE_LINKER_FLAGS "-Wl,--no-keep-memory -Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../lib' -Wl,--exclude-libs,ALL") endif (NOT USESYSTEMLIBS) - set(CMAKE_CXX_LINK_FLAGS "-Wl,--no-keep-memory -Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../lib' -Wl,--exclude-libs,ALL") set(CMAKE_CXX_FLAGS_DEBUG "-fno-inline ${CMAKE_CXX_FLAGS_DEBUG}") if( NOT (CMAKE_CXX_COMPILER MATCHES ".*clang") ) @@ -288,6 +283,17 @@ if (USESYSTEMLIBS) if (LINUX AND ADDRESS_SIZE EQUAL 32) add_definitions(-march=pentiumpro) endif (LINUX AND ADDRESS_SIZE EQUAL 32) + +else (USESYSTEMLIBS) + set(${ARCH}_linux_INCLUDES + atk-1.0 + cairo + freetype + glib-2.0 + gstreamer-0.10 + gtk-2.0 + pango-1.0 + ) endif (USESYSTEMLIBS) endif(NOT DEFINED ${CMAKE_CURRENT_LIST_FILE}_INCLUDED) diff --git a/indra/cmake/APR.cmake b/indra/cmake/APR.cmake index 47567ee009..1a01671002 100644 --- a/indra/cmake/APR.cmake +++ b/indra/cmake/APR.cmake @@ -1,4 +1,4 @@ -#include(BerkeleyDB) +include(BerkeleyDB) include(Linking) include(Prebuilt) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index c2d5df6c0d..21f9faa410 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -5,21 +5,17 @@ set(Boost_FIND_QUIETLY ON) set(Boost_FIND_REQUIRED ON) if (USESYSTEMLIBS) - SET(BOOST_INCLUDEDIR ${CMAKE_INCLUDE_PATH} ) - SET(BOOST_LIBRARYDIR ${CMAKE_LIBRARY_PATH} ) include(FindBoost) - include_directories( ${BOOST_INCLUDEDIR} ) - - FIND_LIBRARY(BOOST_CONTEXT_LIBRARY boost_context-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_COROUTINE_LIBRARY boost_coroutine-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_REGEX_LIBRARY boost_regex-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_SIGNALS_LIBRARY boost_signals-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_SYSTEM_LIBRARY boost_system-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_THREAD_LIBRARY boost_thread-mt PATHS ${BOOST_LIBRARYDIR} ) - FIND_LIBRARY(BOOST_WAVE_LIBRARY boost_wave-mt PATHS ${BOOST_LIBRARYDIR} ) + set(BOOST_CONTEXT_LIBRARY boost_context-mt) + set(BOOST_FIBER_LIBRARY boost_fiber-mt) + set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt) + set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt) + set(BOOST_REGEX_LIBRARY boost_regex-mt) + set(BOOST_SIGNALS_LIBRARY boost_signals-mt) + set(BOOST_SYSTEM_LIBRARY boost_system-mt) + set(BOOST_THREAD_LIBRARY boost_thread-mt) + set(BOOST_WAVE_LIBRARY boost_wave-mt) else (USESYSTEMLIBS) use_prebuilt_binary(boost) set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include) diff --git a/indra/cmake/BuildPackagesInfo.cmake b/indra/cmake/BuildPackagesInfo.cmake index 313a904471..8f8b6b2330 100644 --- a/indra/cmake/BuildPackagesInfo.cmake +++ b/indra/cmake/BuildPackagesInfo.cmake @@ -3,8 +3,6 @@ include(Python) include(FindAutobuild) -if( NOT USESYSTEMLIBS ) - # packages-formatter.py runs autobuild install --versions, which needs to know # the build_directory, which (on Windows) depends on AUTOBUILD_ADDRSIZE. # Within an autobuild build, AUTOBUILD_ADDRSIZE is already set. But when @@ -20,5 +18,3 @@ add_custom_command(OUTPUT packages-info.txt ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" > packages-info.txt ) - -endif() \ No newline at end of file diff --git a/indra/cmake/CEFPlugin.cmake b/indra/cmake/CEFPlugin.cmake index 21a8f441f2..9e7ab912f8 100644 --- a/indra/cmake/CEFPlugin.cmake +++ b/indra/cmake/CEFPlugin.cmake @@ -3,7 +3,7 @@ include(Linking) include(Prebuilt) if (USESYSTEMLIBS) - set(CEFPLUGIN ON CACHE BOOL + set(CEFPLUGIN OFF CACHE BOOL "CEFPLUGIN support for the llplugin/llmedia test apps.") else (USESYSTEMLIBS) use_prebuilt_binary(dullahan) @@ -37,18 +37,9 @@ elseif (DARWIN) ) elseif (LINUX) - - if (USESYSTEMLIBS) - find_library( LIB_DULLAHAN "dullahan" ) - find_library( LIB_CEF "cef" ) - find_library( LIB_CEF_WRAPPER "cef_dll_wrapper" ) - set(CEF_PLUGIN_LIBRARIES ${LIB_DULLAHAN} ${LIB_CEF} ${LIB_CEF_WRAPPER} ) - else() set(CEF_PLUGIN_LIBRARIES - dullahan - cef - cef_dll_wrapper.a - ) -endif() - + dullahan + cef + cef_dll_wrapper.a + ) endif (WINDOWS) diff --git a/indra/cmake/ColladaDom.cmake b/indra/cmake/ColladaDom.cmake index 6409f16557..3999e61045 100644 --- a/indra/cmake/ColladaDom.cmake +++ b/indra/cmake/ColladaDom.cmake @@ -2,20 +2,11 @@ include (Prebuilt) if (USESYSTEMLIBS) - include(FindPkgConfig) - pkg_check_modules( MINIZIP REQUIRED minizip ) - pkg_check_modules( LIBXML2 REQUIRED libxml-2.0 ) - pkg_check_modules( LIBPCRECPP REQUIRED libpcrecpp ) - - find_library( COLLADADOM_LIBRARY collada14dom ) - find_path( COLLADADOM_INCLUDE_DIR colladadom/dae.h ) - - if( COLLADADOM_INCLUDE_DIR STREQUAL "COLLADADOM_INCLUDE_DIR-NOTFOUND" ) - message( FATAL_ERROR "Cannot find colladadom include dir" ) - endif() - - set( COLLADA_INCLUDE_DIRS ${COLLADADOM_INCLUDE_DIR}/colladadom ${COLLADADOM_INCLUDE_DIR}/colladadom/1.4 ) - + find_path(COLLADA_INCLUDE_DIRS 1.4/dom/domConstants.h + PATH_SUFFIXES collada collada-dom2.4) + set(COLLADA_INCLUDE_DIRS + ${COLLADA_INCLUDE_DIRS} ${COLLADA_INCLUDE_DIRS}/1.4 + ) else (USESYSTEMLIBS) set(COLLADA_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/collada diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 63f4bc336b..c0652e556c 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -237,11 +237,14 @@ elseif(LINUX) set(release_files #libapr-1.so.0 #libaprutil-1.so.0 + libatk-1.0.so #libdb-5.1.so ${EXPAT_COPY} #libfreetype.so.6.6.2 #libfreetype.so.6 #libGLOD.so + libgmodule-2.0.so + libgobject-2.0.so libhunspell-1.3.so.0.0.0 libopenal.so #libopenjpeg.so @@ -309,9 +312,10 @@ to_staging_dirs( ${release_files} ) -if(NOT USESYSTEMLIBS) +# We need to do this regardless +#if(NOT USESYSTEMLIBS) add_custom_target( stage_third_party_libs ALL DEPENDS ${third_party_targets} ) -endif(NOT USESYSTEMLIBS) +#endif(NOT USESYSTEMLIBS) diff --git a/indra/cmake/DBusGlib.cmake b/indra/cmake/DBusGlib.cmake index d0f6c221cc..5e46b6711a 100644 --- a/indra/cmake/DBusGlib.cmake +++ b/indra/cmake/DBusGlib.cmake @@ -1,8 +1,6 @@ # -*- cmake -*- include(Prebuilt) -include(GLIB) -if( GLIB_FOUND ) if (USESYSTEMLIBS) include(FindPkgConfig) @@ -12,11 +10,14 @@ elseif (LINUX) use_prebuilt_binary(dbus_glib) set(DBUSGLIB_FOUND ON FORCE BOOL) set(DBUSGLIB_INCLUDE_DIRS - ${GLIB_INCLUDE_DIRS} - ${LIBS_PREBUILT_DIR}/include/dbus - ) + ${LIBS_PREBUILT_DIR}/include/dbus + ) # We don't need to explicitly link against dbus-glib itself, because # the viewer probes for the system's copy at runtime. + set(DBUSGLIB_LIBRARIES + gobject-2.0 + glib-2.0 + ) endif (USESYSTEMLIBS) if (DBUSGLIB_FOUND) @@ -26,5 +27,3 @@ endif (DBUSGLIB_FOUND) if (DBUSGLIB) add_definitions(-DLL_DBUS_ENABLED=1) endif (DBUSGLIB) - -endif() diff --git a/indra/cmake/FindAPR.cmake b/indra/cmake/FindAPR.cmake index 993748597a..906b6c9452 100644 --- a/indra/cmake/FindAPR.cmake +++ b/indra/cmake/FindAPR.cmake @@ -1,8 +1,94 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules( APR REQUIRED apr-1 ) -set( APR_INCLUDE_DIR ${APR_INCLUDE_DIRS} ) +# - Find Apache Portable Runtime +# Find the APR includes and libraries +# This module defines +# APR_INCLUDE_DIR and APRUTIL_INCLUDE_DIR, where to find apr.h, etc. +# APR_LIBRARIES and APRUTIL_LIBRARIES, the libraries needed to use APR. +# APR_FOUND and APRUTIL_FOUND, If false, do not try to use APR. +# also defined, but not for general use are +# APR_LIBRARY and APRUTIL_LIBRARY, where to find the APR library. -pkg_check_modules( APRUTIL REQUIRED apr-util-1 ) -set( APRUTIL_INCLUDE_DIR ${APRUTIL_INCLUDE_DIRS} ) +# APR first. + +FIND_PATH(APR_INCLUDE_DIR apr.h +/usr/local/include/apr-1 +/usr/local/include/apr-1.0 +/usr/include/apr-1 +/usr/include/apr-1.0 +) + +SET(APR_NAMES ${APR_NAMES} apr-1) +FIND_LIBRARY(APR_LIBRARY + NAMES ${APR_NAMES} + PATHS /usr/lib /usr/local/lib + ) + +IF (APR_LIBRARY AND APR_INCLUDE_DIR) + SET(APR_LIBRARIES ${APR_LIBRARY}) + SET(APR_FOUND "YES") +ELSE (APR_LIBRARY AND APR_INCLUDE_DIR) + SET(APR_FOUND "NO") +ENDIF (APR_LIBRARY AND APR_INCLUDE_DIR) + + +IF (APR_FOUND) + IF (NOT APR_FIND_QUIETLY) + MESSAGE(STATUS "Found APR: ${APR_LIBRARIES}") + ENDIF (NOT APR_FIND_QUIETLY) +ELSE (APR_FOUND) + IF (APR_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find APR library") + ENDIF (APR_FIND_REQUIRED) +ENDIF (APR_FOUND) + +# Deprecated declarations. +SET (NATIVE_APR_INCLUDE_PATH ${APR_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_APR_LIB_PATH ${APR_LIBRARY} PATH) + +MARK_AS_ADVANCED( + APR_LIBRARY + APR_INCLUDE_DIR + ) + +# Next, APRUTIL. + +FIND_PATH(APRUTIL_INCLUDE_DIR apu.h +/usr/local/include/apr-1 +/usr/local/include/apr-1.0 +/usr/include/apr-1 +/usr/include/apr-1.0 +) + +SET(APRUTIL_NAMES ${APRUTIL_NAMES} aprutil-1) +FIND_LIBRARY(APRUTIL_LIBRARY + NAMES ${APRUTIL_NAMES} + PATHS /usr/lib /usr/local/lib + ) + +IF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) + SET(APRUTIL_LIBRARIES ${APRUTIL_LIBRARY}) + SET(APRUTIL_FOUND "YES") +ELSE (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) + SET(APRUTIL_FOUND "NO") +ENDIF (APRUTIL_LIBRARY AND APRUTIL_INCLUDE_DIR) + + +IF (APRUTIL_FOUND) + IF (NOT APRUTIL_FIND_QUIETLY) + MESSAGE(STATUS "Found APRUTIL: ${APRUTIL_LIBRARIES}") + ENDIF (NOT APRUTIL_FIND_QUIETLY) +ELSE (APRUTIL_FOUND) + IF (APRUTIL_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find APRUTIL library") + ENDIF (APRUTIL_FIND_REQUIRED) +ENDIF (APRUTIL_FOUND) + +# Deprecated declarations. +SET (NATIVE_APRUTIL_INCLUDE_PATH ${APRUTIL_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_APRUTIL_LIB_PATH ${APRUTIL_LIBRARY} PATH) + +MARK_AS_ADVANCED( + APRUTIL_LIBRARY + APRUTIL_INCLUDE_DIR + ) diff --git a/indra/cmake/FindFMODSTUDIO.cmake b/indra/cmake/FindFMODSTUDIO.cmake index 902d9a4835..4562b0ae45 100644 --- a/indra/cmake/FindFMODSTUDIO.cmake +++ b/indra/cmake/FindFMODSTUDIO.cmake @@ -9,10 +9,13 @@ # also defined, but not for general use are # FMODSTUDIO_LIBRARY, where to find the FMODSTUDIO library. -FIND_PATH(FMODSTUDIO_INCLUDE_DIR fmod.h PATH_SUFFIXES fmodstudio) +FIND_PATH(FMODSTUDIO_INCLUDE_DIR fmod.h PATH_SUFFIXES fmod) SET(FMODSTUDIO_NAMES ${FMODSTUDIO_NAMES} fmod fmod_vc) -FIND_LIBRARY(FMODSTUDIO_LIBRARY NAMES ${FMODSTUDIO_NAMES} PATH_SUFFIXES fmod ) +FIND_LIBRARY(FMODSTUDIO_LIBRARY + NAMES ${FMODSTUDIO_NAMES} + PATH_SUFFIXES fmod + ) IF (FMODSTUDIO_SDK_DIR OR WINDOWS) if(WINDOWS) @@ -36,14 +39,12 @@ IF (FMODSTUDIO_SDK_DIR OR WINDOWS) ${FMODSTUDIO_SDK_DIR}/api ${FMODSTUDIO_SDK_DIR} ) + IF (FMODSTUDIO_LIBRARY AND FMODSTUDIO_INCLUDE_DIR) + SET(FMODSTUDIO_LIBRARIES ${FMODSTUDIO_LIBRARY}) + SET(FMODSTUDIO_FOUND "YES") + endif (FMODSTUDIO_LIBRARY AND FMODSTUDIO_INCLUDE_DIR) ENDIF (FMODSTUDIO_SDK_DIR OR WINDOWS) -IF (FMODSTUDIO_LIBRARY AND FMODSTUDIO_INCLUDE_DIR) - SET(FMODSTUDIO_LIBRARIES ${FMODSTUDIO_LIBRARY}) - SET(FMODSTUDIO_FOUND "YES") -endif (FMODSTUDIO_LIBRARY AND FMODSTUDIO_INCLUDE_DIR) - - IF (FMODSTUDIO_FOUND) IF (NOT FMODSTUDIO_FIND_QUIETLY) MESSAGE(STATUS "Found FMODSTUDIO: ${FMODSTUDIO_LIBRARIES}") diff --git a/indra/cmake/FindGLH.cmake b/indra/cmake/FindGLH.cmake index 7288eb1d12..3d16adaf03 100644 --- a/indra/cmake/FindGLH.cmake +++ b/indra/cmake/FindGLH.cmake @@ -6,7 +6,9 @@ # GLH_INCLUDE_DIR, where to find glh/glh_linear.h. # GLH_FOUND, If false, do not try to use GLH. -find_path(GLH_INCLUDE_DIR glh/glh_linear.h ) +find_path(GLH_INCLUDE_DIR glh/glh_linear.h + NO_SYSTEM_ENVIRONMENT_PATH + ) if (GLH_INCLUDE_DIR) set(GLH_FOUND "YES") diff --git a/indra/cmake/FindGLOD.cmake b/indra/cmake/FindGLOD.cmake index b6ee8ba792..010a0c5aa4 100644 --- a/indra/cmake/FindGLOD.cmake +++ b/indra/cmake/FindGLOD.cmake @@ -1,16 +1,36 @@ +# -*- cmake -*- -find_library( GLOD_LIBRARY GLOD ) -find_library( VDS_LIBRARY vds ) -find_path( GLOD_INCLUDE_DIR glod/glod.h ) +# - Find GLOD +# This module defines +# GLOD_INCLUDE_DIR, where to find glod.h, etc. +# GLOD_LIBRARIES, the library needed to use GLOD. +# GLOD_FOUND, If false, do not try to use GLOD. -if( GLOD_INCLUDE_DIR STREQUAL "GLOD_INCLUDE_DIR-NOTFOUND" ) - message( FATAL_ERROR "Cannot find glod include dir" ) -endif() -if( GLOD_LIBRARY STREQUAL "GLOD_LIBRARY-NOTFOUND" ) - message( FATAL_ERROR "Cannot find library GLOD.a" ) -endif() -if( VDS_LIBRARY STREQUAL "VDS_LIBRARY-NOTFOUND" ) - message( FATAL_ERROR "Cannot find library vds.a" ) -endif() -set(GLOD_LIBRARIES ${GLOD_LIBRARY} ${VDS_LIBRARY} ) - +# LL coded the #include with a glod path in llfloatermodelpreview.cpp +find_path(GLOD_INCLUDE_DIR glod/glod.h) + +set(GLOD_NAMES ${GLOD_NAMES} glod) +find_library(GLOD_LIBRARIES + NAMES ${GLOD_NAMES} + ) + +if (GLOD_LIBRARIES AND GLOD_INCLUDE_DIR) + set(GLOD_FOUND "YES") +else (GLOD_LIBRARIES AND GLOD_INCLUDE_DIR) + set(GLOD_FOUND "NO") +endif (GLOD_LIBRARIES AND GLOD_INCLUDE_DIR) + +if (GLOD_FOUND) + if (NOT GLOD_FIND_QUIETLY) + message(STATUS "Found GLOD: Library in '${GLOD_LIBRARY}' and header in '${GLOD_INCLUDE_DIR}' ") + endif (NOT GLOD_FIND_QUIETLY) +else (GLOD_FOUND) + if (GLOD_FIND_REQUIRED) + message(FATAL_ERROR " * * * * *\nCould not find GLOD library!\n* * * * *") + endif (GLOD_FIND_REQUIRED) +endif (GLOD_FOUND) + +mark_as_advanced( + GLOD_LIBRARIES + GLOD_INCLUDE_DIR + ) diff --git a/indra/cmake/FindGoogleBreakpad.cmake b/indra/cmake/FindGoogleBreakpad.cmake index 78890ca5c2..e9f67b98c7 100644 --- a/indra/cmake/FindGoogleBreakpad.cmake +++ b/indra/cmake/FindGoogleBreakpad.cmake @@ -9,15 +9,15 @@ # also defined, but not for general use are # BREAKPAD_EXCEPTION_HANDLER_LIBRARY, where to find the Google BreakPad library. -FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR breakpad/client/linux/handler/exception_handler.h) +# LL code uses "google_breakpad" path prefix, while google breakpad headers don't use a prefix. Find and add both paths to the correct variable. +FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR google_breakpad/exception_handler.h) +FIND_PATH(BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIRS exception_handler.h PATH_SUFFIXES google_breakpad) +SET(BREAKPAD_INCLUDE_DIRECTORIES ${BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR} ${BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIRS}) SET(BREAKPAD_EXCEPTION_HANDLER_NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} breakpad_client) FIND_LIBRARY(BREAKPAD_EXCEPTION_HANDLER_LIBRARY NAMES ${BREAKPAD_EXCEPTION_HANDLER_NAMES} ) -message ( ${BREAKPAD_EXCEPTION_HANDLER_LIBRARY} ) - -include_directories( ${BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR}/google_breakpad/ ) IF (BREAKPAD_EXCEPTION_HANDLER_LIBRARY AND BREAKPAD_EXCEPTION_HANDLER_INCLUDE_DIR) SET(BREAKPAD_EXCEPTION_HANDLER_LIBRARIES ${BREAKPAD_EXCEPTION_HANDLER_LIBRARY}) @@ -30,6 +30,7 @@ ENDIF (BREAKPAD_EXCEPTION_HANDLER_LIBRARY AND BREAKPAD_EXCEPTION_HANDLER_INCLUDE IF (BREAKPAD_EXCEPTION_HANDLER_FOUND) IF (NOT BREAKPAD_EXCEPTION_HANDLER_FIND_QUIETLY) MESSAGE(STATUS "Found Google BreakPad: ${BREAKPAD_EXCEPTION_HANDLER_LIBRARIES}") + MESSAGE(STATUS "Found Google BreakPad headers in: ${BREAKPAD_INCLUDE_DIRECTORIES}") ENDIF (NOT BREAKPAD_EXCEPTION_HANDLER_FIND_QUIETLY) ELSE (BREAKPAD_EXCEPTION_HANDLER_FOUND) IF (BREAKPAD_EXCEPTION_HANDLER_FIND_REQUIRED) diff --git a/indra/cmake/FindHUNSPELL.cmake b/indra/cmake/FindHUNSPELL.cmake index c2b7c589f6..ab621570d9 100644 --- a/indra/cmake/FindHUNSPELL.cmake +++ b/indra/cmake/FindHUNSPELL.cmake @@ -1,6 +1,38 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules( HUNSPELL REQUIRED hunspell ) -set( HUNSPELL_INCLUDE_DIR ${HUNSPELL_INCLUDE_DIRS} ) -set( HUNSPELL_LIBRARY ${HUNSPELL_LIBRARIES} ) \ No newline at end of file +# - Find HUNSPELL +# This module defines +# HUNSPELL_INCLUDE_DIR, where to find libhunspell.h, etc. +# HUNSPELL_LIBRARY, the library needed to use HUNSPELL. +# HUNSPELL_FOUND, If false, do not try to use HUNSPELL. + +find_path(HUNSPELL_INCLUDE_DIR hunspell.h + PATH_SUFFIXES hunspell + ) + +set(HUNSPELL_NAMES ${HUNSPELL_NAMES} hunspell-1.4 libhunspell-1.3 libhunspell) +find_library(HUNSPELL_LIBRARY + NAMES ${HUNSPELL_NAMES} + ) + +if (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) + set(HUNSPELL_FOUND "YES") +else (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) + set(HUNSPELL_FOUND "NO") +endif (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR) + + +if (HUNSPELL_FOUND) + if (NOT HUNSPELL_FIND_QUIETLY) + message(STATUS "Found Hunspell: Library in '${HUNSPELL_LIBRARY}' and header in '${HUNSPELL_INCLUDE_DIR}' ") + endif (NOT HUNSPELL_FIND_QUIETLY) +else (HUNSPELL_FOUND) + if (HUNSPELL_FIND_REQUIRED) + message(FATAL_ERROR " * * *\nCould not find HUNSPELL library! * * *") + endif (HUNSPELL_FIND_REQUIRED) +endif (HUNSPELL_FOUND) + +mark_as_advanced( + HUNSPELL_LIBRARY + HUNSPELL_INCLUDE_DIR + ) diff --git a/indra/cmake/FindJsonCpp.cmake b/indra/cmake/FindJsonCpp.cmake index c02794ed67..a2c74a4d27 100644 --- a/indra/cmake/FindJsonCpp.cmake +++ b/indra/cmake/FindJsonCpp.cmake @@ -1,6 +1,59 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules( JSONCPP REQUIRED jsoncpp ) -set( JSONCPP_INCLUDE_DIR ${JSONCPP_INCLUDE_DIRS} ) +# - Find JSONCpp +# Find the JSONCpp includes and library +# This module defines +# JSONCPP_INCLUDE_DIR, where to find json.h, etc. +# JSONCPP_LIBRARIES, the libraries needed to use jsoncpp. +# JSONCPP_FOUND, If false, do not try to use jsoncpp. +# also defined, but not for general use are +# JSONCPP_LIBRARY, where to find the jsoncpp library. +FIND_PATH(JSONCPP_INCLUDE_DIR NAMES jsoncpp/json.h jsoncpp/json/json.h +/usr/local/include +/usr/include +) + +# Get the GCC compiler version +EXEC_PROGRAM(${CMAKE_CXX_COMPILER} + ARGS ${CMAKE_CXX_COMPILER_ARG1} -dumpversion + OUTPUT_VARIABLE _gcc_COMPILER_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +# Try to find a library that was compiled with the same compiler version as we currently use. +SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson_linux-gcc-${_gcc_COMPILER_VERSION}_libmt.so) + +# On standalone, assume that the system installed library was compiled with the used compiler. +SET(JSONCPP_NAMES ${JSONCPP_NAMES} libjson.so libjsoncpp.so) + +FIND_LIBRARY(JSONCPP_LIBRARY + NAMES ${JSONCPP_NAMES} + ) + +IF (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) + SET(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY}) + SET(JSONCPP_FOUND "YES") +ELSE (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) + SET(JSONCPP_FOUND "NO") +ENDIF (JSONCPP_LIBRARY AND JSONCPP_INCLUDE_DIR) + + +IF (JSONCPP_FOUND) + IF (NOT JSONCPP_FIND_QUIETLY) + MESSAGE(STATUS "Found JSONCpp: ${JSONCPP_LIBRARIES}") + ENDIF (NOT JSONCPP_FIND_QUIETLY) +ELSE (JSONCPP_FOUND) + IF (JSONCPP_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find JSONCpp library") + ENDIF (JSONCPP_FIND_REQUIRED) +ENDIF (JSONCPP_FOUND) + +# Deprecated declarations. +SET (NATIVE_JSONCPP_INCLUDE_PATH ${JSONCPP_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_JSONCPP_LIB_PATH ${JSONCPP_LIBRARY} PATH) + +MARK_AS_ADVANCED( + JSONCPP_LIBRARY + JSONCPP_INCLUDE_DIR + ) diff --git a/indra/cmake/FindKDU.cmake b/indra/cmake/FindKDU.cmake index 6f86b34812..3944ad8308 100644 --- a/indra/cmake/FindKDU.cmake +++ b/indra/cmake/FindKDU.cmake @@ -1,9 +1,40 @@ -find_path( KDU_INCLUDE_DIR kdu/kdu_image.h ) -find_library( KDU_LIBRARY kdu_x64 ) +# -*- cmake -*- -if ( NOT KDU_LIBRARY OR NOT KDU_INCLUDE_DIR ) - message(FATAL_ERROR "Cannot find KDU: Library '${KDU_LIBRARY}'; header '${KDU_INCLUDE_DIR}' ") -endif() +# - Find KDU +# This module defines +# KDU_INCLUDE_DIR, where to find kdu.h, etc. +# KDU_LIBRARIES, the library needed to use KDU. +# KDU_FOUND, If false, do not try to use KDU. -set( KDU_INCLUDE_DIR ${KDU_INCLUDE_DIR}/kdu ) -set( LLKDU_LIBRARIES llkdu ) +# LL coded the #include with a glod path in llfloatermodelpreview.cpp +find_path(KDU_INCLUDE_DIR kdu_image.h + PATH_SUFFIXES kdu + ) + +set(KDU_NAMES ${KDU_NAMES} kdu) +find_library(KDU_LIBRARIES + NAMES ${KDU_NAMES} + ) + +if (KDU_LIBRARIES AND KDU_INCLUDE_DIR) + set(KDU_FOUND "YES") + set(KDU_INCLUDE_DIRS ${KDU_INCLUDE_DIR}) + set(KDU_LIBRARY ${KDU_LIBRARIES}) +else (KDU_LIBRARIES AND KDU_INCLUDE_DIR) + set(KDU_FOUND "NO") +endif (KDU_LIBRARIES AND KDU_INCLUDE_DIR) + +if (KDU_FOUND) + if (NOT KDU_FIND_QUIETLY) + message(STATUS "Found KDU: Library in '${KDU_LIBRARY}' and header in '${KDU_INCLUDE_DIR}' ") + endif (NOT KDU_FIND_QUIETLY) +else (KDU_FOUND) + if (KDU_FIND_REQUIRED) + message(FATAL_ERROR " * * * * *\nCould not find KDU library!\n* * * * *") + endif (KDU_FIND_REQUIRED) +endif (KDU_FOUND) + +mark_as_advanced( + KDU_LIBRARIES + KDU_INCLUDE_DIR + ) diff --git a/indra/cmake/FindNGHTTP2.cmake b/indra/cmake/FindNGHTTP2.cmake index 0ac3c812f9..c11d88814f 100644 --- a/indra/cmake/FindNGHTTP2.cmake +++ b/indra/cmake/FindNGHTTP2.cmake @@ -1,5 +1,35 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) +# - Find NGHTTP2 +# This module defines +# NGHTTP2_INCLUDE_DIR, where to find glod.h, etc. +# NGHTTP2_LIBRARIES, the library needed to use NGHTTP2. +# NGHTTP2_FOUND, If false, do not try to use NGHTTP2. +find_path(NGHTTP2_INCLUDE_DIR nghttp2/nghttp2.h) + +set(NGHTTP2_NAMES ${NGHTTP2_NAMES} nghttp2) +find_library(NGHTTP2_LIBRARIES + NAMES ${NGHTTP2_NAMES} + ) + +if (NGHTTP2_LIBRARIES AND NGHTTP2_INCLUDE_DIR) + set(NGHTTP2_FOUND "YES") +else (NGHTTP2_LIBRARIES AND NGHTTP2_INCLUDE_DIR) + set(NGHTTP2_FOUND "NO") +endif (NGHTTP2_LIBRARIES AND NGHTTP2_INCLUDE_DIR) + +if (NGHTTP2_FOUND) + if (NOT NGHTTP2_FIND_QUIETLY) + message(STATUS "Found NGHTTP2: Library in '${NGHTTP2_LIBRARY}' and header in '${NGHTTP2_INCLUDE_DIR}' ") + endif (NOT NGHTTP2_FIND_QUIETLY) +else (NGHTTP2_FOUND) + if (NGHTTP2_FIND_REQUIRED) + message(FATAL_ERROR " * * * * *\nCould not find NGHTTP2 library!\n* * * * *") + endif (NGHTTP2_FIND_REQUIRED) +endif (NGHTTP2_FOUND) + +mark_as_advanced( + NGHTTP2_LIBRARIES + NGHTTP2_INCLUDE_DIR + ) diff --git a/indra/cmake/FindOpenJPEG.cmake b/indra/cmake/FindOpenJPEG.cmake index 17ccf1e13d..cdbc6c5841 100644 --- a/indra/cmake/FindOpenJPEG.cmake +++ b/indra/cmake/FindOpenJPEG.cmake @@ -1,7 +1,52 @@ # -*- cmake -*- -include(FindPkgConfig) +# - Find OpenJPEG +# Find the OpenJPEG includes and library +# This module defines +# OPENJPEG_INCLUDE_DIR, where to find openjpeg.h, etc. +# OPENJPEG_LIBRARIES, the libraries needed to use OpenJPEG. +# OPENJPEG_FOUND, If false, do not try to use OpenJPEG. +# also defined, but not for general use are +# OPENJPEG_LIBRARY, where to find the OpenJPEG library. -pkg_check_modules( OPENJPEG REQUIRED libopenjpeg1 ) +FIND_PATH(OPENJPEG_INCLUDE_DIR openjpeg.h +/usr/local/include/openjpeg-2.1 +/usr/local/include/openjpeg +/usr/local/include +/usr/include/openjpeg-2.1 +/usr/include/openjpeg +/usr/include +) -set(OPENJPEG_INCLUDE_DIR ${OPENJPEG_INCLUDE_DIRS}) +SET(OPENJPEG_NAMES ${OPENJPEG_NAMES} openjpeg openjp2) +FIND_LIBRARY(OPENJPEG_LIBRARY + NAMES ${OPENJPEG_NAMES} + PATHS /usr/lib /usr/local/lib + ) + +IF (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) + SET(OPENJPEG_LIBRARIES ${OPENJPEG_LIBRARY}) + SET(OPENJPEG_FOUND "YES") +ELSE (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) + SET(OPENJPEG_FOUND "NO") +ENDIF (OPENJPEG_LIBRARY AND OPENJPEG_INCLUDE_DIR) + + +IF (OPENJPEG_FOUND) + IF (NOT OPENJPEG_FIND_QUIETLY) + MESSAGE(STATUS "Found OpenJPEG: ${OPENJPEG_LIBRARIES}") + ENDIF (NOT OPENJPEG_FIND_QUIETLY) +ELSE (OPENJPEG_FOUND) + IF (OPENJPEG_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find OpenJPEG library") + ENDIF (OPENJPEG_FIND_REQUIRED) +ENDIF (OPENJPEG_FOUND) + +# Deprecated declarations. +SET (NATIVE_OPENJPEG_INCLUDE_PATH ${OPENJPEG_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_OPENJPEG_LIB_PATH ${OPENJPEG_LIBRARY} PATH) + +MARK_AS_ADVANCED( + OPENJPEG_LIBRARY + OPENJPEG_INCLUDE_DIR + ) diff --git a/indra/cmake/FindURIPARSER.cmake b/indra/cmake/FindURIPARSER.cmake index 213e71e2bf..b3b4efbbdb 100644 --- a/indra/cmake/FindURIPARSER.cmake +++ b/indra/cmake/FindURIPARSER.cmake @@ -1,5 +1,44 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules( URIPARSER REQUIRED liburiparser ) -set( URIPARSER_INCLUDE_DIR ${URIPARSER_INCLUDE_DIRS} ) \ No newline at end of file +# - Find uriparser +# Find the URIPARSER includes and library +# This module defines +# URIPARSER_INCLUDE_DIRS, where to find uriparser.h, etc. +# URIPARSER_LIBRARIES, the libraries needed to use uriparser. +# URIPARSER_FOUND, If false, do not try to use uriparser. +# +# This FindURIPARSER is about 43 times as fast the one provided with cmake (2.8.x), +# because it doesn't look up the version of uriparser, resulting in a dramatic +# speed up for configure (from 4 minutes 22 seconds to 6 seconds). +# +# Note: Since this file is only used for standalone, the windows +# specific parts were left out. + +FIND_PATH(URIPARSER_INCLUDE_DIR uriparser/Uri.h) + +FIND_LIBRARY(URIPARSER_LIBRARY uriparser) + +if (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + SET(URIPARSER_INCLUDE_DIRS ${URIPARSER_INCLUDE_DIR}) + SET(URIPARSER_LIBRARIES ${URIPARSER_LIBRARY}) + SET(URIPARSER_FOUND "YES") +else (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + SET(URIPARSER_FOUND "NO") +endif (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + +if (URIPARSER_FOUND) + if (NOT URIPARSER_FIND_QUIETLY) + message(STATUS "Found URIPARSER: ${URIPARSER_LIBRARIES}") + SET(URIPARSER_FIND_QUIETLY TRUE) + endif (NOT URIPARSER_FIND_QUIETLY) +else (URIPARSER_FOUND) + if (URIPARSER_FIND_REQUIRED) + message(FATAL_ERROR "Could not find URIPARSER library") + endif (URIPARSER_FIND_REQUIRED) +endif (URIPARSER_FOUND) + +mark_as_advanced( + URIPARSER_LIBRARY + URIPARSER_INCLUDE_DIR + ) + diff --git a/indra/cmake/FindZLIB.cmake b/indra/cmake/FindZLIB.cmake index 7701aca285..03a7db9d6f 100644 --- a/indra/cmake/FindZLIB.cmake +++ b/indra/cmake/FindZLIB.cmake @@ -1,5 +1,46 @@ # -*- cmake -*- -include(FindPkgConfig) -pkg_check_modules( ZLIB REQUIRED zlib ) -set( ZLIB_INCLUDE_DIR ${ZLIB_INCLUDE_DIRS} ) +# - Find zlib +# Find the ZLIB includes and library +# This module defines +# ZLIB_INCLUDE_DIRS, where to find zlib.h, etc. +# ZLIB_LIBRARIES, the libraries needed to use zlib. +# ZLIB_FOUND, If false, do not try to use zlib. +# +# This FindZLIB is about 43 times as fast the one provided with cmake (2.8.x), +# because it doesn't look up the version of zlib, resulting in a dramatic +# speed up for configure (from 4 minutes 22 seconds to 6 seconds). +# +# Note: Since this file is only used for standalone, the windows +# specific parts were left out. + +FIND_PATH(ZLIB_INCLUDE_DIR zlib.h + NO_SYSTEM_ENVIRONMENT_PATH + ) + +FIND_LIBRARY(ZLIB_LIBRARY z) + +if (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) + SET(ZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR}) + SET(ZLIB_LIBRARIES ${ZLIB_LIBRARY}) + SET(ZLIB_FOUND "YES") +else (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) + SET(ZLIB_FOUND "NO") +endif (ZLIB_LIBRARY AND ZLIB_INCLUDE_DIR) + +if (ZLIB_FOUND) + if (NOT ZLIB_FIND_QUIETLY) + message(STATUS "Found ZLIB: ${ZLIB_LIBRARIES}") + SET(ZLIB_FIND_QUIETLY TRUE) + endif (NOT ZLIB_FIND_QUIETLY) +else (ZLIB_FOUND) + if (ZLIB_FIND_REQUIRED) + message(FATAL_ERROR "Could not find ZLIB library") + endif (ZLIB_FIND_REQUIRED) +endif (ZLIB_FOUND) + +mark_as_advanced( + ZLIB_LIBRARY + ZLIB_INCLUDE_DIR + ) + diff --git a/indra/cmake/GLIB.cmake b/indra/cmake/GLIB.cmake deleted file mode 100644 index 0024cb26ac..0000000000 --- a/indra/cmake/GLIB.cmake +++ /dev/null @@ -1,11 +0,0 @@ - -include(Prebuilt) - -if( LINUX ) - use_prebuilt_binary(glib) - set(GLIB_FOUND ON CACHE BOOL "Build against glib 2") - set(GLIB_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/glib-2.0 ${LIBS_PREBUILT_DIR}/lib/release/glib-2.0/include ) - set(GLIB_LIBRARIES libgobject-2.0.a libglib-2.0.a libffi.a libpcre.a) - - add_definitions(-DLL_GLIB=1) -endif() diff --git a/indra/cmake/GLOD.cmake b/indra/cmake/GLOD.cmake index e7fc1b39e9..2580ead67b 100644 --- a/indra/cmake/GLOD.cmake +++ b/indra/cmake/GLOD.cmake @@ -1,8 +1,9 @@ # -*- cmake -*- -if (USESYSTEMLIBS) - include(FindGLOD) -else (USESYSTEMLIBS) +#if (USESYSTEMLIBS) +# set(GLOD_FIND_REQUIRED true) +# include(FindGLOD) +#else (USESYSTEMLIBS) include(Prebuilt) use_prebuilt_binary(glod) set(GLOD_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include) @@ -11,4 +12,4 @@ if(LINUX) else() set(GLOD_LIBRARIES GLOD) endif() -endif (USESYSTEMLIBS) +#endif (USESYSTEMLIBS) diff --git a/indra/cmake/GStreamer010Plugin.cmake b/indra/cmake/GStreamer010Plugin.cmake index 2a41cc5314..3fbc40ef8f 100644 --- a/indra/cmake/GStreamer010Plugin.cmake +++ b/indra/cmake/GStreamer010Plugin.cmake @@ -1,6 +1,5 @@ # -*- cmake -*- include(Prebuilt) -include(GLIB) if (USESYSTEMLIBS) include(FindPkgConfig) @@ -14,10 +13,10 @@ elseif (LINUX) set(GSTREAMER010_FOUND ON FORCE BOOL) set(GSTREAMER010_PLUGINS_BASE_FOUND ON FORCE BOOL) set(GSTREAMER010_INCLUDE_DIRS - ${GLIB_INCLUDE_DIRS} - ${LIBS_PREBUILT_DIR}/include/gstreamer-0.10 - ${LIBS_PREBUILT_DIR}/include/libxml2 - ) + ${LIBS_PREBUILT_DIR}/include/gstreamer-0.10 + ${LIBS_PREBUILT_DIR}/include/glib-2.0 + ${LIBS_PREBUILT_DIR}/include/libxml2 + ) # We don't need to explicitly link against gstreamer itself, because # LLMediaImplGStreamer probes for the system's copy at runtime. set(GSTREAMER010_LIBRARIES diff --git a/indra/cmake/GStreamer10Plugin.cmake b/indra/cmake/GStreamer10Plugin.cmake deleted file mode 100644 index ded45da610..0000000000 --- a/indra/cmake/GStreamer10Plugin.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# -*- cmake -*- -include(Prebuilt) -include(GLIB) - -if (USESYSTEMLIBS) - include(FindPkgConfig) - - pkg_check_modules(GSTREAMER10 REQUIRED gstreamer-1.0) - pkg_check_modules(GSTREAMER10_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0) -elseif (LINUX OR WINDOWS) - use_prebuilt_binary(gstreamer10) - use_prebuilt_binary(libxml2) - set(GSTREAMER10_FOUND ON FORCE BOOL) - set(GSTREAMER10_PLUGINS_BASE_FOUND ON FORCE BOOL) - set(GSTREAMER10_INCLUDE_DIRS - ${GLIB_INCLUDE_DIRS} - ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0 - ${LIBS_PREBUILT_DIR}/include/libxml2 - ) - # We don't need to explicitly link against gstreamer itself, because - # LLMediaImplGStreamer probes for the system's copy at runtime. - set(GSTREAMER10_LIBRARIES) -endif (USESYSTEMLIBS) - -if (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) - set(GSTREAMER10 ON CACHE BOOL "Build with GStreamer-1.0 streaming media support.") -endif (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) - -if (GSTREAMER10) - add_definitions(-DLL_GSTREAMER10_ENABLED=1) -endif (GSTREAMER10) - diff --git a/indra/cmake/GoogleBreakpad.cmake b/indra/cmake/GoogleBreakpad.cmake index 829e1ac08a..83cf9cb1e7 100644 --- a/indra/cmake/GoogleBreakpad.cmake +++ b/indra/cmake/GoogleBreakpad.cmake @@ -1,10 +1,10 @@ # -*- cmake -*- include(Prebuilt) -if (USESYSTEMLIBS) - set(BREAKPAD_EXCEPTION_HANDLER_FIND_REQUIRED ON) - include(FindGoogleBreakpad) -else (USESYSTEMLIBS) +#if (USESYSTEMLIBS) +# set(BREAKPAD_EXCEPTION_HANDLER_FIND_REQUIRED ON) +# include(FindGoogleBreakpad) +#else (USESYSTEMLIBS) use_prebuilt_binary(google_breakpad) if (DARWIN) set(BREAKPAD_EXCEPTION_HANDLER_LIBRARIES exception_handler) @@ -18,5 +18,5 @@ else (USESYSTEMLIBS) # yes, this does look dumb, no, it's not incorrect # set(BREAKPAD_INCLUDE_DIRECTORIES "${LIBS_PREBUILT_DIR}/include/google_breakpad" "${LIBS_PREBUILT_DIR}/include/google_breakpad/google_breakpad") -endif (USESYSTEMLIBS) +#endif (USESYSTEMLIBS) diff --git a/indra/cmake/GoogleMock.cmake b/indra/cmake/GoogleMock.cmake index 6ea2878d2e..5a00546927 100644 --- a/indra/cmake/GoogleMock.cmake +++ b/indra/cmake/GoogleMock.cmake @@ -2,44 +2,27 @@ include(Prebuilt) include(Linking) -if( NOT USESYSTEMLIBS ) - - use_prebuilt_binary(googlemock) +use_prebuilt_binary(googlemock) - set(GOOGLEMOCK_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include) +set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include) - if (LINUX) - # VWR-24366: gmock is underlinked, it needs gtest. - set(GOOGLEMOCK_LIBRARIES - gmock -Wl,--no-as-needed - gtest -Wl,--as-needed) - elseif(WINDOWS) - set(GOOGLEMOCK_LIBRARIES - gmock) - set(GOOGLEMOCK_INCLUDE_DIRS - ${LIBS_PREBUILT_DIR}/include - ${LIBS_PREBUILT_DIR}/include/gmock - ${LIBS_PREBUILT_DIR}/include/gmock/boost/tr1/tr1) - elseif(DARWIN) - set(GOOGLEMOCK_LIBRARIES - gmock - gtest) - endif(LINUX) -else() - - find_library( GOOGLETEST_LIBRARY gtest ) - find_library( GOOGLEMOCK_LIBRARY gmock ) +if (LINUX) + # VWR-24366: gmock is underlinked, it needs gtest. + set(GOOGLEMOCK_LIBRARIES + gmock -Wl,--no-as-needed + gtest -Wl,--as-needed) +elseif(WINDOWS) + set(GOOGLEMOCK_LIBRARIES + gmock) + set(GOOGLEMOCK_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/include + ${LIBS_PREBUILT_DIR}/include/gmock + ${LIBS_PREBUILT_DIR}/include/gmock/boost/tr1/tr1) +elseif(DARWIN) + set(GOOGLEMOCK_LIBRARIES + gmock + gtest) +endif(LINUX) - if( GOOGLETEST_LIBRARY STREQUAL "GOOGLETEST_LIBRARY-NOTFOUND" ) - message( FATAL_ERROR "Cannot find gtest library" ) - endif() - - if( GOOGLEMOCK_LIBRARY STREQUAL "GOOGLEMOCK_LIBRARY-NOTFOUND" ) - message( FATAL_ERROR "Cannot find gmock library" ) - endif() - - set(GOOGLEMOCK_LIBRARIES ${GOOGLEMOCK_LIBRARY} ${GOOGLETEST_LIBRARY} ) - -endif() diff --git a/indra/cmake/LLPhysicsExtensions.cmake b/indra/cmake/LLPhysicsExtensions.cmake index 0093a05c58..3a41305e0c 100644 --- a/indra/cmake/LLPhysicsExtensions.cmake +++ b/indra/cmake/LLPhysicsExtensions.cmake @@ -23,8 +23,7 @@ if (HAVOK) elseif (HAVOK_TPV) use_prebuilt_binary(llphysicsextensions_tpv) - #set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensions_tpv) - set(LLPHYSICSEXTENSIONS_LIBRARIES libhacd.a libnd_hacdConvexDecomposition.a libnd_Pathing.a ) + set(LLPHYSICSEXTENSIONS_LIBRARIES llphysicsextensions_tpv) # include paths for LLs version and ours are different. set(LLPHYSICSEXTENSIONS_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/llphysicsextensions) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index 1fe2cc6a9c..93626f689f 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -4,75 +4,46 @@ include(Prebuilt) include(Boost) +use_prebuilt_binary(colladadom) +use_prebuilt_binary(pcre) +use_prebuilt_binary(libxml2) + set(LLPRIMITIVE_INCLUDE_DIRS ${LIBS_OPEN_DIR}/llprimitive ) +if (WINDOWS) + set(LLPRIMITIVE_LIBRARIES + debug llprimitive + optimized llprimitive + debug libcollada14dom23-sd + optimized libcollada14dom23-s + libxml2_a + debug pcrecppd + optimized pcrecpp + debug pcred + optimized pcre + ${BOOST_SYSTEM_LIBRARIES} + ) +elseif (DARWIN) + set(LLPRIMITIVE_LIBRARIES + llprimitive + debug collada14dom-d + optimized collada14dom + minizip + xml2 + pcrecpp + pcre + iconv # Required by libxml2 + ) +elseif (LINUX) + set(LLPRIMITIVE_LIBRARIES + llprimitive + debug collada14dom-d + optimized collada14dom + minizip + xml2 + pcrecpp + pcre + ) +endif (WINDOWS) -if( NOT USESYSTEMLIBS ) - use_prebuilt_binary(colladadom) - use_prebuilt_binary(pcre) - use_prebuilt_binary(libxml2) - - set( COLLADADOM_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/collada ${LIBS_PREBUILT_DIR}/include/collada/1.4 ) - - if (WINDOWS) - set(LLPRIMITIVE_LIBRARIES - debug llprimitive - optimized llprimitive - debug libcollada14dom23-sd - optimized libcollada14dom23-s - libxml2_a - debug pcrecppd - optimized pcrecpp - debug pcred - optimized pcre - ${BOOST_SYSTEM_LIBRARIES} - ) - elseif (DARWIN) - set(LLPRIMITIVE_LIBRARIES - llprimitive - debug collada14dom-d - optimized collada14dom - minizip - xml2 - pcrecpp - pcre - iconv # Required by libxml2 - ) - elseif (LINUX) - set(LLPRIMITIVE_LIBRARIES - llprimitive - debug collada14dom-d - optimized collada14dom - minizip - xml2 - pcrecpp - pcre - ) - endif (WINDOWS) - -else() - - include(FindPkgConfig) - pkg_check_modules( MINIZIP REQUIRED minizip ) - pkg_check_modules( LIBXML2 REQUIRED libxml-2.0 ) - pkg_check_modules( LIBPCRECPP REQUIRED libpcrecpp ) - - find_library( COLLADADOM_LIBRARY collada14dom ) - find_path( COLLADADOM_INCLUDE_DIR colladadom/dae.h ) - - if( COLLADADOM_INCLUDE_DIR STREQUAL "COLLADADOM_INCLUDE_DIR-NOTFOUND" ) - message( FATAL_ERROR "Cannot find colladadom include dir" ) - endif() - - set( COLLADADOM_INCLUDE_DIRS ${COLLADADOM_INCLUDE_DIR}/colladadom ${COLLADADOM_INCLUDE_DIR}/colladadom/1.4 ) - - set(LLPRIMITIVE_LIBRARIES - llprimitive - ${COLLADADOM_LIBRARY} - ${MINIZIP_LIBRARIES} - ${LIBXML2_LIBRARIES} - ${LIBPRCECPP_LIBRARIES} - ) - -endif() diff --git a/indra/cmake/LLWindow.cmake b/indra/cmake/LLWindow.cmake index 5156f09697..398647e58f 100644 --- a/indra/cmake/LLWindow.cmake +++ b/indra/cmake/LLWindow.cmake @@ -3,10 +3,9 @@ include(Variables) include(GLEXT) include(Prebuilt) -include(FindPkgConfig) if (USESYSTEMLIBS) - pkg_check_modules(SDL2 REQUIRED sdl2) + include(FindSDL) # This should be done by FindSDL. Sigh. mark_as_advanced( @@ -18,7 +17,7 @@ else (USESYSTEMLIBS) if (LINUX) use_prebuilt_binary(SDL) set (SDL_FOUND TRUE) - set (SDL_LIBRARY SDL2 X11) + set (SDL_LIBRARY SDL directfb fusion direct X11) endif (LINUX) endif (USESYSTEMLIBS) diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 8c5d266cf5..f1a8b68900 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -5,29 +5,23 @@ include(FreeType) if (USESYSTEMLIBS) include(FindPkgConfig) - if( NOT GTK_VERSION ) - set( GTK_VERSION 2.0 ) - endif() if (LINUX) set(PKGCONFIG_PACKAGES atk cairo - gdk-${GTK_VERSION} + gdk-2.0 gdk-pixbuf-2.0 glib-2.0 gmodule-2.0 - gtk+-${GTK_VERSION} + gtk+-2.0 gthread-2.0 libpng pango pangoft2 - sdl2 + pangox + pangoxft + sdl ) - if( GTK_VERSION LESS "3.0" ) - LIST( APPEND PKGCONFIG_PACKAGES pangoxft ) - else() - add_definitions( -DGTK_DISABLE_DEPRECATED) - endif() endif (LINUX) foreach(pkg ${PKGCONFIG_PACKAGES}) @@ -37,16 +31,29 @@ if (USESYSTEMLIBS) list(APPEND UI_LIBRARIES ${${pkg}_LIBRARIES}) add_definitions(${${pkg}_CFLAGS_OTHERS}) endforeach(pkg) - list(APPEND UI_LIBRARIES X11) else (USESYSTEMLIBS) if (LINUX) - use_prebuilt_binary(fltk) + use_prebuilt_binary(gtk-atk-pango-glib) endif (LINUX) if (LINUX) set(UI_LIB_NAMES - libfltk.a freetype + atk-1.0 + gdk-x11-2.0 + gdk_pixbuf-2.0 + glib-2.0 + gmodule-2.0 + gobject-2.0 + gthread-2.0 + gtk-x11-2.0 + pango-1.0 + pangoft2-1.0 + pangox-1.0 + #pangoxft-1.0 + gio-2.0 + pangocairo-1.0 + ffi ) foreach(libname ${UI_LIB_NAMES}) @@ -73,5 +80,5 @@ else (USESYSTEMLIBS) endif (USESYSTEMLIBS) if (LINUX) - add_definitions(-DLL_X11=1 -DLL_FLTK=1) + add_definitions(-DLL_GTK=1 -DLL_X11=1) endif (LINUX) diff --git a/indra/cmake/jemalloc.cmake b/indra/cmake/jemalloc.cmake index d5df9fe2e5..8980134b0a 100644 --- a/indra/cmake/jemalloc.cmake +++ b/indra/cmake/jemalloc.cmake @@ -2,12 +2,12 @@ include(Prebuilt) if (LINUX AND NOT SYSTEMLIBS ) - set( USE_JEMALLOC ON CACHE BOOL "Ship prebuild jemalloc library with packaged viewer" ) + set(USE_JEMALLOC ON) endif () if( USE_JEMALLOC ) if (USESYSTEMLIBS) - message( WARNING "Search for jemalloc not implemented for standalone builds" ) + message( WARNING "Not implemented" ) else (USESYSTEMLIBS) use_prebuilt_binary(jemalloc) endif (USESYSTEMLIBS) diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index b166a33056..aa82ed12cc 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -59,9 +59,6 @@ add_executable(linux-crash-logger ${linux_crash_logger_SOURCE_FILES}) # llcommon uses `clock_gettime' which is provided by librt on linux. set(LIBRT_LIBRARY rt) -set(CMAKE_THREAD_PREFER_PTHREAD TRUE) -set(THREADS_PREFER_PTHREAD_FLAG TRUE) -find_package(Threads REQUIRED) target_link_libraries(linux-crash-logger ${LLCRASHLOGGER_LIBRARIES} @@ -77,7 +74,6 @@ target_link_libraries(linux-crash-logger ${DB_LIBRARIES} ${FREETYPE_LIBRARIES} ${LIBRT_LIBRARY} - Threads::Threads ) add_custom_target(linux-crash-logger-target ALL diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index b75e4ae704..cd59780a0f 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -343,9 +343,7 @@ if (DARWIN) target_link_libraries(llcommon ${CARBON_LIBRARY}) endif (DARWIN) -if( NOT USESYSTEMLIBS ) add_dependencies(llcommon stage_third_party_libs) -endif() if (LL_TESTS) include(LLAddBuildTest) diff --git a/indra/llfilesystem/lldir_linux.cpp b/indra/llfilesystem/lldir_linux.cpp index 512bfdb683..2e92e13bfd 100644 --- a/indra/llfilesystem/lldir_linux.cpp +++ b/indra/llfilesystem/lldir_linux.cpp @@ -39,11 +39,6 @@ static std::string getCurrentUserHome(char* fallback) { - // snap-package: - // could use SNAP_USER_DATA (/home/nicky/snap/viewer-release/x1, whereas x1 is the release and thus will change, data is backed up/restored across snap refresh / snap revert) - // could use SNAP_USER_COMMON (/home/nicky/snap/viewer-release/common, data is NOT backed up/restored across snap refresh / snap revert) - // see https://docs.snapcraft.io/environment-variables/7983 - const uid_t uid = getuid(); struct passwd *pw; @@ -176,10 +171,7 @@ void LLDir_Linux::initAppDirs(const std::string &app_name, // traditionally on unixoids, MyApp gets ~/.myapp dir for data mOSUserAppDir = mOSUserDir; mOSUserAppDir += "/"; - // When running as a snap package we canot use /home//.secondlife as strict-mode snaps do not get access to .dot files - // In that case we use /home//secondlife - if( nullptr == getenv( "SNAP_USER_DATA" ) ) - mOSUserAppDir += "."; + mOSUserAppDir += "."; std::string lower_app_name(app_name); LLStringUtil::toLower(lower_app_name); mOSUserAppDir += lower_app_name; diff --git a/indra/llplugin/llpluginsharedmemory.cpp b/indra/llplugin/llpluginsharedmemory.cpp index dcdfe122f5..63ff5085c6 100644 --- a/indra/llplugin/llpluginsharedmemory.cpp +++ b/indra/llplugin/llpluginsharedmemory.cpp @@ -318,16 +318,7 @@ bool LLPluginSharedMemory::unlink(void) bool LLPluginSharedMemory::create(size_t size) { - char const *pSnapName = getenv( "SNAP_NAME" ); - if( pSnapName ) - { - mName = "snap."; - mName += pSnapName; - mName += ".LL"; - } - else - mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING; - + mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING; mName += createName(); mSize = size; diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt index 3158cb7e12..3e45820403 100644 --- a/indra/llplugin/slplugin/CMakeLists.txt +++ b/indra/llplugin/slplugin/CMakeLists.txt @@ -91,10 +91,6 @@ if (DARWIN) ) endif (DARWIN) -if( LINUX ) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") -endif() - if (LL_TESTS) ll_deploy_sharedlibs_command(SLPlugin) endif (LL_TESTS) diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 81a942cd6d..d436173886 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -273,7 +273,6 @@ void LLGLSLShader::readProfileQuery(U32 count, U32 mode) glEndQueryARB(GL_TIME_ELAPSED); glEndQueryARB(GL_SAMPLES_PASSED); - // U64 and GLuint64 somehow turn out different on x86_64 //U64 time_elapsed = 0; GLuint64 time_elapsed = 0; diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index d242477c7e..bb003a77e3 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -190,12 +190,11 @@ endif (llwindow_HEADER_FILES) ${viewer_SOURCE_FILES} ) -if (SDL2_FOUND OR SDL_FOUND ) +if (SDL_FOUND) set_property(TARGET llwindow PROPERTY COMPILE_DEFINITIONS LL_SDL=1 ) -endif () +endif (SDL_FOUND) + + target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) -if (NOT USESYSTEMLIBS) -target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) -endif() diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index 10cbcf3115..f6f6c3931c 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -179,10 +179,12 @@ void LLKeyboard::resetKeys() } -BOOL LLKeyboard::translateKey(const U32 os_key, KEY *out_key) +BOOL LLKeyboard::translateKey(const U16 os_key, KEY *out_key) { + std::map::iterator iter; + // Only translate keys in the map, ignore all other keys for now - auto iter = mTranslateKeyMap.find(os_key); + iter = mTranslateKeyMap.find(os_key); if (iter == mTranslateKeyMap.end()) { //LL_WARNS() << "Unknown virtual key " << os_key << LL_ENDL; @@ -197,9 +199,10 @@ BOOL LLKeyboard::translateKey(const U32 os_key, KEY *out_key) } -U32 LLKeyboard::inverseTranslateKey(const KEY translated_key) +U16 LLKeyboard::inverseTranslateKey(const KEY translated_key) { - auto iter = mInvTranslateKeyMap.find(translated_key); + std::map::iterator iter; + iter = mInvTranslateKeyMap.find(translated_key); if (iter == mInvTranslateKeyMap.end()) { return 0; diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index 631ccd3a3a..6f2dc87317 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -75,19 +75,14 @@ public: BOOL getKeyDown(const KEY key) { return mKeyLevel[key]; } BOOL getKeyRepeated(const KEY key) { return mKeyRepeated[key]; } - BOOL translateKey(const U32 os_key, KEY *translated_key); - U32 inverseTranslateKey(const KEY translated_key); + BOOL translateKey(const U16 os_key, KEY *translated_key); + U16 inverseTranslateKey(const KEY translated_key); BOOL handleTranslatedKeyUp(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes BOOL handleTranslatedKeyDown(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes -#if LL_LINUX - virtual BOOL handleKeyUp(const U32 key, MASK mask) = 0; - virtual BOOL handleKeyDown(const U32 key, MASK mask) = 0; -#else virtual BOOL handleKeyUp(const U16 key, MASK mask) = 0; virtual BOOL handleKeyDown(const U16 key, MASK mask) = 0; -#endif #ifdef LL_DARWIN // We only actually use this for OS X. @@ -121,8 +116,8 @@ protected: void addKeyName(KEY key, const std::string& name); protected: - std::map mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs - std::map mInvTranslateKeyMap; // Map of translations from Linden KEYs to OS keys + std::map mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs + std::map mInvTranslateKeyMap; // Map of translations from Linden KEYs to OS keys LLWindowCallbacks *mCallbacks; LLTimer mKeyLevelTimer[KEY_COUNT]; // Time since level was set diff --git a/indra/llwindow/llkeyboardheadless.cpp b/indra/llwindow/llkeyboardheadless.cpp index 67a84d9478..a1b6b294e0 100644 --- a/indra/llwindow/llkeyboardheadless.cpp +++ b/indra/llwindow/llkeyboardheadless.cpp @@ -35,21 +35,12 @@ void LLKeyboardHeadless::resetMaskKeys() { } -#ifdef LL_LINUX - -BOOL LLKeyboardHeadless::handleKeyDown(const U32 key, const U32 mask) -{ return FALSE; } -BOOL LLKeyboardHeadless::handleKeyUp(const U32 key, const U32 mask) -{ return FALSE; } - -#else - -BOOL LLKeyboardHeadless::handleKeyUp(const U16 key, const U32 mask) -{ return FALSE; } BOOL LLKeyboardHeadless::handleKeyDown(const U16 key, const U32 mask) { return FALSE; } -#endif + +BOOL LLKeyboardHeadless::handleKeyUp(const U16 key, const U32 mask) +{ return FALSE; } MASK LLKeyboardHeadless::currentMask(BOOL for_mouse_event) { return MASK_NONE; } diff --git a/indra/llwindow/llkeyboardheadless.h b/indra/llwindow/llkeyboardheadless.h index 3131897544..8ed28ace90 100644 --- a/indra/llwindow/llkeyboardheadless.h +++ b/indra/llwindow/llkeyboardheadless.h @@ -35,13 +35,8 @@ public: LLKeyboardHeadless(); /*virtual*/ ~LLKeyboardHeadless() {}; -#ifdef LL_LINUX - /*virtual*/ BOOL handleKeyUp(const U32 key, MASK mask); - /*virtual*/ BOOL handleKeyDown(const U32 key, MASK mask); -#else /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); -#endif /*virtual*/ void resetMaskKeys(); /*virtual*/ MASK currentMask(BOOL for_mouse_event); /*virtual*/ void scanKeyboard(); diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 7416b4f7b2..7c9aa1d340 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -29,8 +29,7 @@ #include "linden_common.h" #include "llkeyboardsdl.h" #include "llwindowcallbacks.h" -#include "SDL2/SDL.h" -#include "SDL2/SDL_keycode.h" +#include "SDL/SDL.h" LLKeyboardSDL::LLKeyboardSDL() { @@ -41,10 +40,6 @@ LLKeyboardSDL::LLKeyboardSDL() // Virtual key mappings from SDL_keysym.h ... // SDL maps the letter keys to the ASCII you'd expect, but it's lowercase... - - // Looks like we need to map those despite of SDL_TEXTINPUT handling most of this, but without - // the translation lower->upper here accelerators will not work. - U16 cur_char; for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) { @@ -54,7 +49,7 @@ LLKeyboardSDL::LLKeyboardSDL() { mTranslateKeyMap[cur_char] = (cur_char - 'a') + 'A'; } - + for (cur_char = '0'; cur_char <= '9'; cur_char++) { mTranslateKeyMap[cur_char] = cur_char; @@ -73,7 +68,7 @@ LLKeyboardSDL::LLKeyboardSDL() //mTranslateKeyMap[SDLK_KP3] = KEY_PAGE_DOWN; //mTranslateKeyMap[SDLK_KP0] = KEY_INSERT; - // mTranslateKeyMap[SDLK_SPACE] = ' '; // Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_SPACE] = ' '; mTranslateKeyMap[SDLK_RETURN] = KEY_RETURN; mTranslateKeyMap[SDLK_LEFT] = KEY_LEFT; mTranslateKeyMap[SDLK_RIGHT] = KEY_RIGHT; @@ -116,39 +111,40 @@ LLKeyboardSDL::LLKeyboardSDL() mTranslateKeyMap[SDLK_F10] = KEY_F10; mTranslateKeyMap[SDLK_F11] = KEY_F11; mTranslateKeyMap[SDLK_F12] = KEY_F12; - // mTranslateKeyMap[SDLK_PLUS] = '='; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_COMMA] = ','; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_MINUS] = '-'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_PERIOD] = '.'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_SLASH] = KEY_DIVIDE; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_SEMICOLON] = ';'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; // Those are handled by SDL2 via text input, do not map them - // mTranslateKeyMap[SDLK_QUOTE] = '\''; // Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_PLUS] = '='; + mTranslateKeyMap[SDLK_COMMA] = ','; + mTranslateKeyMap[SDLK_MINUS] = '-'; + mTranslateKeyMap[SDLK_PERIOD] = '.'; + mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; + mTranslateKeyMap[SDLK_SLASH] = KEY_DIVIDE; + mTranslateKeyMap[SDLK_SEMICOLON] = ';'; + mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; + mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; + mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; + mTranslateKeyMap[SDLK_QUOTE] = '\''; // Build inverse map - for (auto iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } // numpad map - mTranslateNumpadMap[SDLK_KP_0] = KEY_PAD_INS; - mTranslateNumpadMap[SDLK_KP_1] = KEY_PAD_END; - mTranslateNumpadMap[SDLK_KP_2] = KEY_PAD_DOWN; - mTranslateNumpadMap[SDLK_KP_3] = KEY_PAD_PGDN; - mTranslateNumpadMap[SDLK_KP_4] = KEY_PAD_LEFT; - mTranslateNumpadMap[SDLK_KP_5] = KEY_PAD_CENTER; - mTranslateNumpadMap[SDLK_KP_6] = KEY_PAD_RIGHT; - mTranslateNumpadMap[SDLK_KP_7] = KEY_PAD_HOME; - mTranslateNumpadMap[SDLK_KP_8] = KEY_PAD_UP; - mTranslateNumpadMap[SDLK_KP_9] = KEY_PAD_PGUP; + mTranslateNumpadMap[SDLK_KP0] = KEY_PAD_INS; + mTranslateNumpadMap[SDLK_KP1] = KEY_PAD_END; + mTranslateNumpadMap[SDLK_KP2] = KEY_PAD_DOWN; + mTranslateNumpadMap[SDLK_KP3] = KEY_PAD_PGDN; + mTranslateNumpadMap[SDLK_KP4] = KEY_PAD_LEFT; + mTranslateNumpadMap[SDLK_KP5] = KEY_PAD_CENTER; + mTranslateNumpadMap[SDLK_KP6] = KEY_PAD_RIGHT; + mTranslateNumpadMap[SDLK_KP7] = KEY_PAD_HOME; + mTranslateNumpadMap[SDLK_KP8] = KEY_PAD_UP; + mTranslateNumpadMap[SDLK_KP9] = KEY_PAD_PGUP; mTranslateNumpadMap[SDLK_KP_PERIOD] = KEY_PAD_DEL; // build inverse numpad map - for (auto iter = mTranslateNumpadMap.begin(); + for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) { @@ -158,7 +154,7 @@ LLKeyboardSDL::LLKeyboardSDL() void LLKeyboardSDL::resetMaskKeys() { - SDL_Keymod mask = SDL_GetModState(); + SDLMod mask = SDL_GetModState(); // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). // It looks a bit suspicious, as it won't correct for keys that have been released. @@ -205,37 +201,37 @@ MASK LLKeyboardSDL::updateModifiers(const U32 mask) } -static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) +static U16 adjustNativekeyFromUnhandledMask(const U16 key, const U32 mask) { // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. - U32 rtn = key; + U16 rtn = key; if (!(mask & KMOD_NUM)) { switch (key) { - case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; - case SDLK_KP_0: rtn = SDLK_INSERT; break; - case SDLK_KP_1: rtn = SDLK_END; break; - case SDLK_KP_2: rtn = SDLK_DOWN; break; - case SDLK_KP_3: rtn = SDLK_PAGEDOWN; break; - case SDLK_KP_4: rtn = SDLK_LEFT; break; - case SDLK_KP_6: rtn = SDLK_RIGHT; break; - case SDLK_KP_7: rtn = SDLK_HOME; break; - case SDLK_KP_8: rtn = SDLK_UP; break; - case SDLK_KP_9: rtn = SDLK_PAGEUP; break; + case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; + case SDLK_KP0: rtn = SDLK_INSERT; break; + case SDLK_KP1: rtn = SDLK_END; break; + case SDLK_KP2: rtn = SDLK_DOWN; break; + case SDLK_KP3: rtn = SDLK_PAGEDOWN; break; + case SDLK_KP4: rtn = SDLK_LEFT; break; + case SDLK_KP6: rtn = SDLK_RIGHT; break; + case SDLK_KP7: rtn = SDLK_HOME; break; + case SDLK_KP8: rtn = SDLK_UP; break; + case SDLK_KP9: rtn = SDLK_PAGEUP; break; } } return rtn; } -BOOL LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) +BOOL LLKeyboardSDL::handleKeyDown(const U16 key, const U32 mask) { - U32 adjusted_nativekey; + U16 adjusted_nativekey; KEY translated_key = 0; U32 translated_mask = MASK_NONE; - BOOL handled = FALSE; + BOOL handled = FALSE; adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); @@ -250,12 +246,12 @@ BOOL LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) } -BOOL LLKeyboardSDL::handleKeyUp(const U32 key, const U32 mask) +BOOL LLKeyboardSDL::handleKeyUp(const U16 key, const U32 mask) { - U32 adjusted_nativekey; + U16 adjusted_nativekey; KEY translated_key = 0; U32 translated_mask = MASK_NONE; - BOOL handled = FALSE; + BOOL handled = FALSE; adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); @@ -272,7 +268,7 @@ BOOL LLKeyboardSDL::handleKeyUp(const U32 key, const U32 mask) MASK LLKeyboardSDL::currentMask(BOOL for_mouse_event) { MASK result = MASK_NONE; - SDL_Keymod mask = SDL_GetModState(); + SDLMod mask = SDL_GetModState(); if (mask & KMOD_SHIFT) result |= MASK_SHIFT; if (mask & KMOD_CTRL) result |= MASK_CONTROL; @@ -281,7 +277,7 @@ MASK LLKeyboardSDL::currentMask(BOOL for_mouse_event) // For keyboard events, consider Meta keys equivalent to Control if (!for_mouse_event) { - if (mask & KMOD_GUI) result |= MASK_CONTROL; + if (mask & KMOD_META) result |= MASK_CONTROL; } return result; @@ -314,7 +310,7 @@ void LLKeyboardSDL::scanKeyboard() } -BOOL LLKeyboardSDL::translateNumpadKey( const U32 os_key, KEY *translated_key) +BOOL LLKeyboardSDL::translateNumpadKey( const U16 os_key, KEY *translated_key) { return translateKey(os_key, translated_key); } @@ -324,258 +320,5 @@ U16 LLKeyboardSDL::inverseTranslateNumpadKey(const KEY translated_key) return inverseTranslateKey(translated_key); } -enum class WindowsVK : U32 -{ - VK_UNKNOWN = 0, - VK_BACK = 0x08, - VK_TAB = 0x09, - VK_CLEAR = 0x0C, - VK_RETURN = 0x0D, - VK_SHIFT = 0x10, - VK_CONTROL = 0x11, - VK_MENU = 0x12, - VK_PAUSE = 0x13, - VK_CAPITAL = 0x14, - VK_KANA = 0x15, - VK_HANGUL = 0x15, - VK_JUNJA = 0x17, - VK_FINAL = 0x18, - VK_HANJA = 0x19, - VK_KANJI = 0x19, - VK_ESCAPE = 0x1B, - VK_CONVERT = 0x1C, - VK_NONCONVERT = 0x1D, - VK_ACCEPT = 0x1E, - VK_MODECHANGE = 0x1F, - VK_SPACE = 0x20, - VK_PRIOR = 0x21, - VK_NEXT = 0x22, - VK_END = 0x23, - VK_HOME = 0x24, - VK_LEFT = 0x25, - VK_UP = 0x26, - VK_RIGHT = 0x27, - VK_DOWN = 0x28, - VK_SELECT = 0x29, - VK_PRINT = 0x2A, - VK_EXECUTE = 0x2B, - VK_SNAPSHOT = 0x2C, - VK_INSERT = 0x2D, - VK_DELETE = 0x2E, - VK_HELP = 0x2F, - VK_0 = 0x30, - VK_1 = 0x31, - VK_2 = 0x32, - VK_3 = 0x33, - VK_4 = 0x34, - VK_5 = 0x35, - VK_6 = 0x36, - VK_7 = 0x37, - VK_8 = 0x38, - VK_9 = 0x39, - VK_A = 0x41, - VK_B = 0x42, - VK_C = 0x43, - VK_D = 0x44, - VK_E = 0x45, - VK_F = 0x46, - VK_G = 0x47, - VK_H = 0x48, - VK_I = 0x49, - VK_J = 0x4A, - VK_K = 0x4B, - VK_L = 0x4C, - VK_M = 0x4D, - VK_N = 0x4E, - VK_O = 0x4F, - VK_P = 0x50, - VK_Q = 0x51, - VK_R = 0x52, - VK_S = 0x53, - VK_T = 0x54, - VK_U = 0x55, - VK_V = 0x56, - VK_W = 0x57, - VK_X = 0x58, - VK_Y = 0x59, - VK_Z = 0x5A, - VK_LWIN = 0x5B, - VK_RWIN = 0x5C, - VK_APPS = 0x5D, - VK_SLEEP = 0x5F, - VK_NUMPAD0 = 0x60, - VK_NUMPAD1 = 0x61, - VK_NUMPAD2 = 0x62, - VK_NUMPAD3 = 0x63, - VK_NUMPAD4 = 0x64, - VK_NUMPAD5 = 0x65, - VK_NUMPAD6 = 0x66, - VK_NUMPAD7 = 0x67, - VK_NUMPAD8 = 0x68, - VK_NUMPAD9 = 0x69, - VK_MULTIPLY = 0x6A, - VK_ADD = 0x6B, - VK_SEPARATOR = 0x6C, - VK_SUBTRACT = 0x6D, - VK_DECIMAL = 0x6E, - VK_DIVIDE = 0x6F, - VK_F1 = 0x70, - VK_F2 = 0x71, - VK_F3 = 0x72, - VK_F4 = 0x73, - VK_F5 = 0x74, - VK_F6 = 0x75, - VK_F7 = 0x76, - VK_F8 = 0x77, - VK_F9 = 0x78, - VK_F10 = 0x79, - VK_F11 = 0x7A, - VK_F12 = 0x7B, - VK_F13 = 0x7C, - VK_F14 = 0x7D, - VK_F15 = 0x7E, - VK_F16 = 0x7F, - VK_F17 = 0x80, - VK_F18 = 0x81, - VK_F19 = 0x82, - VK_F20 = 0x83, - VK_F21 = 0x84, - VK_F22 = 0x85, - VK_F23 = 0x86, - VK_F24 = 0x87, - VK_NUMLOCK = 0x90, - VK_SCROLL = 0x91, - VK_LSHIFT = 0xA0, - VK_RSHIFT = 0xA1, - VK_LCONTROL = 0xA2, - VK_RCONTROL = 0xA3, - VK_LMENU = 0xA4, - VK_RMENU = 0xA5, - VK_BROWSER_BACK = 0xA6, - VK_BROWSER_FORWARD = 0xA7, - VK_BROWSER_REFRESH = 0xA8, - VK_BROWSER_STOP = 0xA9, - VK_BROWSER_SEARCH = 0xAA, - VK_BROWSER_FAVORITES = 0xAB, - VK_BROWSER_HOME = 0xAC, - VK_VOLUME_MUTE = 0xAD, - VK_VOLUME_DOWN = 0xAE, - VK_VOLUME_UP = 0xAF, - VK_MEDIA_NEXT_TRACK = 0xB0, - VK_MEDIA_PREV_TRACK = 0xB1, - VK_MEDIA_STOP = 0xB2, - VK_MEDIA_PLAY_PAUSE = 0xB3, - VK_MEDIA_LAUNCH_MAIL = 0xB4, - VK_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5, - VK_MEDIA_LAUNCH_APP1 = 0xB6, - VK_MEDIA_LAUNCH_APP2 = 0xB7, - VK_OEM_1 = 0xBA, - VK_OEM_PLUS = 0xBB, - VK_OEM_COMMA = 0xBC, - VK_OEM_MINUS = 0xBD, - VK_OEM_PERIOD = 0xBE, - VK_OEM_2 = 0xBF, - VK_OEM_3 = 0xC0, - VK_OEM_4 = 0xDB, - VK_OEM_5 = 0xDC, - VK_OEM_6 = 0xDD, - VK_OEM_7 = 0xDE, - VK_OEM_8 = 0xDF, - VK_OEM_102 = 0xE2, - VK_PROCESSKEY = 0xE5, - VK_PACKET = 0xE7, - VK_ATTN = 0xF6, - VK_CRSEL = 0xF7, - VK_EXSEL = 0xF8, - VK_EREOF = 0xF9, - VK_PLAY = 0xFA, - VK_ZOOM = 0xFB, - VK_NONAME = 0xFC, - VK_PA1 = 0xFD, - VK_OEM_CLEAR = 0xFE, -}; - -std::map< U32, U32 > mSDL2_to_Win; -std::set< U32 > mIgnoreSDL2Keys; - -U32 LLKeyboardSDL::mapSDL2toWin( U32 aSymbol ) -{ - // Map SDLK_ virtual keys to Windows VK_ virtual keys. - // Text is handled via unicode input (SDL_TEXTINPUT event) and does not need to be translated into VK_ values as those match already. - if( mSDL2_to_Win.empty() ) - { - mSDL2_to_Win[ SDLK_UNKNOWN ] = (U32)WindowsVK::VK_UNKNOWN; - mSDL2_to_Win[ SDLK_BACKSPACE ] = (U32)WindowsVK::VK_BACK; - mSDL2_to_Win[ SDLK_TAB ] = (U32)WindowsVK::VK_TAB; - mSDL2_to_Win[ SDLK_CLEAR ] = (U32)WindowsVK::VK_CLEAR; - mSDL2_to_Win[ SDLK_RETURN ] = (U32)WindowsVK::VK_RETURN; - mSDL2_to_Win[ SDLK_PAUSE ] = (U32)WindowsVK::VK_PAUSE; - mSDL2_to_Win[ SDLK_ESCAPE ] = (U32)WindowsVK::VK_ESCAPE; - mSDL2_to_Win[ SDLK_DELETE ] = (U32)WindowsVK::VK_DELETE; - mSDL2_to_Win[ SDLK_KP_PERIOD ] = (U32)WindowsVK::VK_OEM_PERIOD; - mSDL2_to_Win[ SDLK_KP_DIVIDE ] = (U32)WindowsVK::VK_DIVIDE; - mSDL2_to_Win[ SDLK_KP_MULTIPLY] = (U32)WindowsVK::VK_MULTIPLY; - mSDL2_to_Win[ SDLK_KP_MINUS ] = (U32)WindowsVK::VK_OEM_MINUS; - mSDL2_to_Win[ SDLK_KP_PLUS ] = (U32)WindowsVK::VK_OEM_PLUS; - mSDL2_to_Win[ SDLK_KP_ENTER ] = (U32)WindowsVK::VK_RETURN; - - // ? - //mSDL2_to_Win[ SDLK_KP_EQUALS ] = (U32)WindowsVK::VK_EQUALS; - - mSDL2_to_Win[ SDLK_UP ] = (U32)WindowsVK::VK_UP; - mSDL2_to_Win[ SDLK_DOWN ] = (U32)WindowsVK::VK_DOWN; - mSDL2_to_Win[ SDLK_RIGHT ] = (U32)WindowsVK::VK_RIGHT; - mSDL2_to_Win[ SDLK_LEFT ] = (U32)WindowsVK::VK_LEFT; - mSDL2_to_Win[ SDLK_INSERT ] = (U32)WindowsVK::VK_INSERT; - mSDL2_to_Win[ SDLK_HOME ] = (U32)WindowsVK::VK_HOME; - mSDL2_to_Win[ SDLK_END ] = (U32)WindowsVK::VK_END; - mSDL2_to_Win[ SDLK_PAGEUP ] = (U32)WindowsVK::VK_PRIOR; - mSDL2_to_Win[ SDLK_PAGEDOWN ] = (U32)WindowsVK::VK_NEXT; - mSDL2_to_Win[ SDLK_F1 ] = (U32)WindowsVK::VK_F1; - mSDL2_to_Win[ SDLK_F2 ] = (U32)WindowsVK::VK_F2; - mSDL2_to_Win[ SDLK_F3 ] = (U32)WindowsVK::VK_F3; - mSDL2_to_Win[ SDLK_F4 ] = (U32)WindowsVK::VK_F4; - mSDL2_to_Win[ SDLK_F5 ] = (U32)WindowsVK::VK_F5; - mSDL2_to_Win[ SDLK_F6 ] = (U32)WindowsVK::VK_F6; - mSDL2_to_Win[ SDLK_F7 ] = (U32)WindowsVK::VK_F7; - mSDL2_to_Win[ SDLK_F8 ] = (U32)WindowsVK::VK_F8; - mSDL2_to_Win[ SDLK_F9 ] = (U32)WindowsVK::VK_F9; - mSDL2_to_Win[ SDLK_F10 ] = (U32)WindowsVK::VK_F10; - mSDL2_to_Win[ SDLK_F11 ] = (U32)WindowsVK::VK_F11; - mSDL2_to_Win[ SDLK_F12 ] = (U32)WindowsVK::VK_F12; - mSDL2_to_Win[ SDLK_F13 ] = (U32)WindowsVK::VK_F13; - mSDL2_to_Win[ SDLK_F14 ] = (U32)WindowsVK::VK_F14; - mSDL2_to_Win[ SDLK_F15 ] = (U32)WindowsVK::VK_F15; - mSDL2_to_Win[ SDLK_CAPSLOCK ] = (U32)WindowsVK::VK_CAPITAL; - mSDL2_to_Win[ SDLK_RSHIFT ] = (U32)WindowsVK::VK_SHIFT; - mSDL2_to_Win[ SDLK_LSHIFT ] = (U32)WindowsVK::VK_SHIFT; - mSDL2_to_Win[ SDLK_RCTRL ] = (U32)WindowsVK::VK_CONTROL; - mSDL2_to_Win[ SDLK_LCTRL ] = (U32)WindowsVK::VK_CONTROL; - mSDL2_to_Win[ SDLK_RALT ] = (U32)WindowsVK::VK_MENU; - mSDL2_to_Win[ SDLK_LALT ] = (U32)WindowsVK::VK_MENU; - - // VK_MODECHANGE ? - // mSDL2_to_Win[ SDLK_MODE ] = (U32)WindowsVK::VK_MODE; - - mSDL2_to_Win[ SDLK_HELP ] = (U32)WindowsVK::VK_HELP; - - // ? - // mSDL2_to_Win[ SDLK_SYSREQ ] = (U32)WindowsVK::VK_SYSREQ; - mSDL2_to_Win[ SDLK_MENU ] = (U32)WindowsVK::VK_MENU; - - // ? - // mSDL2_to_Win[ SDLK_POWER ] = (U32)WindowsVK::VK_POWER; - - // ? - //mSDL2_to_Win[ SDLK_UNDO ] = (U32)WindowsVK::VK_UNDO; - } - - auto itr = mSDL2_to_Win.find( aSymbol ); - if( itr != mSDL2_to_Win.end() ) - return itr->second; - - return aSymbol; -} - #endif diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index e31229eb6e..02a71425f1 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -28,7 +28,7 @@ #define LL_LLKEYBOARDSDL_H #include "llkeyboard.h" -#include "SDL2/SDL.h" +#include "SDL/SDL.h" class LLKeyboardSDL : public LLKeyboard { @@ -36,8 +36,8 @@ public: LLKeyboardSDL(); /*virtual*/ ~LLKeyboardSDL() {}; - /*virtual*/ BOOL handleKeyUp(const U32 key, MASK mask); - /*virtual*/ BOOL handleKeyDown(const U32 key, MASK mask); + /*virtual*/ BOOL handleKeyUp(const U16 key, MASK mask); + /*virtual*/ BOOL handleKeyDown(const U16 key, MASK mask); /*virtual*/ void resetMaskKeys(); /*virtual*/ MASK currentMask(BOOL for_mouse_event); /*virtual*/ void scanKeyboard(); @@ -45,14 +45,11 @@ public: protected: MASK updateModifiers(const U32 mask); void setModifierKeyLevel( KEY key, BOOL new_state ); - BOOL translateNumpadKey( const U32 os_key, KEY *translated_key ); + BOOL translateNumpadKey( const U16 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); private: - std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys - std::map mInvTranslateNumpadMap; // inverse of the above - -public: - static U32 mapSDL2toWin( U32 ); + std::map mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map mInvTranslateNumpadMap; // inverse of the above }; #endif diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 5ce422a294..04b99187eb 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -119,10 +119,8 @@ LLKeyboardWin32::LLKeyboardWin32() mTranslateKeyMap[VK_APPS] = KEY_CONTEXT_MENU; // FIRE-19933: Open context menu on context menu key press // Build inverse map - // Change to U32 for SDL2 - //std::map::iterator iter; - // for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) - for (auto iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + std::map::iterator iter; + for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } @@ -144,9 +142,7 @@ LLKeyboardWin32::LLKeyboardWin32() mTranslateNumpadMap[0x6E] = KEY_PAD_DEL; // keypad . mTranslateNumpadMap[0x6F] = KEY_PAD_DIVIDE; // keypad / - // Change to U32 for SDL2 - // for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) - for (auto iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) + for (iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) { mInvTranslateNumpadMap[iter->second] = iter->first; } diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 1d842755b0..52b48017ee 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -43,7 +43,6 @@ #if LL_GTK extern "C" { # include "gtk/gtk.h" -#include } #include #endif // LL_GTK @@ -121,6 +120,11 @@ bool LLWindowSDL::ll_try_gtk_init(void) if (!tried_gtk_init) { tried_gtk_init = TRUE; + +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32 ) + if (!g_thread_supported ()) g_thread_init (NULL); +#endif + maybe_lock_display(); gtk_is_good = gtk_init_check(NULL, NULL); maybe_unlock_display(); @@ -204,7 +208,6 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, // Ignore use_gl for now, only used for drones on PC mWindow = NULL; - mContext = {}; mNeedsResize = FALSE; mOverrideAspectRatio = 0.f; mGrabbyKeyFlags = 0; @@ -252,6 +255,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, mFlashing = FALSE; #endif // LL_X11 + mKeyScanCode = 0; mKeyVirtualKey = 0; mKeyModifiers = KMOD_NONE; } @@ -328,6 +332,12 @@ static int x11_detect_VRAM_kb_fp(FILE *fp, const char *prefix_str) static int x11_detect_VRAM_kb() { +#if LL_SOLARIS && defined(__sparc) + // NOTE: there's no Xorg server on SPARC so just return 0 + // and allow SDL to attempt to get the amount of VRAM + return(0); +#else + std::string x_log_location("/var/log/"); std::string fname; int rtn = 0; // 'could not detect' @@ -408,65 +418,13 @@ static int x11_detect_VRAM_kb() } } return rtn; +#endif // LL_SOLARIS } #endif // LL_X11 void LLWindowSDL::setTitle(const std::string &title) { - SDL_SetWindowTitle( mWindow, title.c_str() ); -} - -void LLWindowSDL::tryFindFullscreenSize( int &width, int &height ) -{ - LL_INFOS() << "createContext: setting up fullscreen " << width << "x" << height << LL_ENDL; - - // If the requested width or height is 0, find the best default for the monitor. - if((width == 0) || (height == 0)) - { - // Scan through the list of modes, looking for one which has: - // height between 700 and 800 - // aspect ratio closest to the user's original mode - S32 resolutionCount = 0; - LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); - - if(resolutionList != NULL) - { - F32 closestAspect = 0; - U32 closestHeight = 0; - U32 closestWidth = 0; - int i; - - LL_INFOS() << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << LL_ENDL; - - for(i=0; i < resolutionCount; i++) - { - F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; - - LL_INFOS() << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << LL_ENDL; - - if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && - (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) - { - LL_INFOS() << " (new closest mode) " << LL_ENDL; - - // This is the closest mode we've seen yet. - closestWidth = resolutionList[i].mWidth; - closestHeight = resolutionList[i].mHeight; - closestAspect = aspect; - } - } - - width = closestWidth; - height = closestHeight; - } - } - - if((width == 0) || (height == 0)) - { - // Mode search failed for some reason. Use the old-school default. - width = 1024; - height = 768; - } + SDL_WM_SetCaption(title.c_str(), title.c_str()); } BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) @@ -493,48 +451,90 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B << int(c_sdl_version.major) << "." << int(c_sdl_version.minor) << "." << int(c_sdl_version.patch) << LL_ENDL; - SDL_version r_sdl_version; - SDL_GetVersion(&r_sdl_version); + const SDL_version *r_sdl_version; + r_sdl_version = SDL_Linked_Version(); LL_INFOS() << " Running against SDL " - << int(r_sdl_version.major) << "." - << int(r_sdl_version.minor) << "." - << int(r_sdl_version.patch) << LL_ENDL; + << int(r_sdl_version->major) << "." + << int(r_sdl_version->minor) << "." + << int(r_sdl_version->patch) << LL_ENDL; - if (width == 0) - width = 1024; - if (height == 0) - width = 768; + const SDL_VideoInfo *video_info = SDL_GetVideoInfo( ); + if (!video_info) + { + LL_INFOS() << "SDL_GetVideoInfo() failed! " << SDL_GetError() << LL_ENDL; + setupFailure("SDL_GetVideoInfo() failed, Window creation error", "Error", OSMB_OK); + return FALSE; + } + + if (video_info->current_h > 0) + { + mOriginalAspectRatio = (float)video_info->current_w / (float)video_info->current_h; + + // FIRE-8825 - Try to work around an issue with Fullscreen and dual monitors + // assume two equal width monitors if aspect ratio exceeds 2.0 + // if someone can find out how to read a monitor's native resolution in OpenGL, please change this + // SDL1.2 doesn't offer any way to do it, apparently SDL 1.3 does + if(mOriginalAspectRatio>2.0f) + mOriginalAspectRatio=mOriginalAspectRatio/2.0f; + // + + LL_INFOS() << "Original aspect ratio was " << video_info->current_w << ":" << video_info->current_h << "=" << mOriginalAspectRatio << LL_ENDL; + } + + SDL_EnableUNICODE(1); + SDL_WM_SetCaption(mWindowTitle.c_str(), mWindowTitle.c_str()); + + // Set the application icon. + SDL_Surface *bmpsurface; + bmpsurface = Load_BMP_Resource("firestorm_icon.BMP"); + if (bmpsurface) + { + // This attempts to give a black-keyed mask to the icon. + SDL_SetColorKey(bmpsurface, + SDL_SRCCOLORKEY, + SDL_MapRGB(bmpsurface->format, 0,0,0) ); + SDL_WM_SetIcon(bmpsurface, NULL); + // The SDL examples cheerfully avoid freeing the icon + // surface, but I'm betting that's leaky. + SDL_FreeSurface(bmpsurface); + bmpsurface = NULL; + } + + // note: these SetAttributes make Tom's 9600-on-AMD64 fail to + // get a visual, but it's broken anyway when it does, and without + // these SetAttributes we might easily get an avoidable substandard + // visual to work with on most other machines. + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); +#if !LL_SOLARIS + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); + // We need stencil support for a few (minor) things. + if (!getenv("LL_GL_NO_STENCIL")) + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); +#else + // NOTE- use smaller Z-buffer to enable more graphics cards + // - This should not affect better GPUs and has been proven + // to provide 24-bit z-buffers when available. + // + // As the API states: + // + // GLX_DEPTH_SIZE Must be followed by a nonnegative + // minimum size specification. If this + // value is zero, visuals with no depth + // buffer are preferred. Otherwise, the + // largest available depth buffer of at + // least the minimum size is preferred. + + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); +#endif + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, (bits <= 16) ? 1 : 8); + + // *FIX: try to toggle vsync here? mFullscreen = fullscreen; - int sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; - - if( mFullscreen ) - { - sdlflags |= SDL_WINDOW_FULLSCREEN; - tryFindFullscreenSize( width, height ); - } - - mSDLFlags = sdlflags; - - GLint redBits{8}, greenBits{8}, blueBits{8}, alphaBits{8}; - - GLint depthBits{(bits <= 16) ? 16 : 24}, stencilBits{8}; - - if (getenv("LL_GL_NO_STENCIL")) - stencilBits = 0; - - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, alphaBits); - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, redBits); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, greenBits); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, blueBits); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthBits ); - - // We need stencil support for a few (minor) things. - if (stencilBits) - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilBits); - - // *FIX: try to toggle vsync here? + int sdlflags = SDL_OPENGL | SDL_RESIZABLE | SDL_ANYFORMAT; SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -544,31 +544,73 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); } - mWindow = SDL_CreateWindow( mWindowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, mSDLFlags ); + mSDLFlags = sdlflags; - if( mWindow ) + if (mFullscreen) { - mContext = SDL_GL_CreateContext( mWindow ); + LL_INFOS() << "createContext: setting up fullscreen " << width << "x" << height << LL_ENDL; - if( mContext == 0 ) + // If the requested width or height is 0, find the best default for the monitor. + if((width == 0) || (height == 0)) { - LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; - setupFailure("GL Context creation error creation error", "Error", OSMB_OK); - return FALSE; + // Scan through the list of modes, looking for one which has: + // height between 700 and 800 + // aspect ratio closest to the user's original mode + S32 resolutionCount = 0; + LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); + + if(resolutionList != NULL) + { + F32 closestAspect = 0; + U32 closestHeight = 0; + U32 closestWidth = 0; + int i; + + LL_INFOS() << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << LL_ENDL; + + for(i=0; i < resolutionCount; i++) + { + F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; + + LL_INFOS() << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << LL_ENDL; + + if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && + (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) + { + LL_INFOS() << " (new closest mode) " << LL_ENDL; + + // This is the closest mode we've seen yet. + closestWidth = resolutionList[i].mWidth; + closestHeight = resolutionList[i].mHeight; + closestAspect = aspect; + } + } + + width = closestWidth; + height = closestHeight; + } } - // SDL_GL_SetSwapInterval(1); - mSurface = SDL_GetWindowSurface( mWindow ); - } + if((width == 0) || (height == 0)) + { + // Mode search failed for some reason. Use the old-school default. + width = 1024; + height = 768; + } - if( mFullscreen ) - { - if (mSurface) + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); + if (!mWindow && bits > 16) + { + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); + } + + if (mWindow) { mFullscreen = TRUE; - mFullscreenWidth = mSurface->w; - mFullscreenHeight = mSurface->h; - mFullscreenBits = mSurface->format->BitsPerPixel; + mFullscreenWidth = mWindow->w; + mFullscreenHeight = mWindow->h; + mFullscreenBits = mWindow->format->BitsPerPixel; mFullscreenRefresh = -1; LL_INFOS() << "Running at " << mFullscreenWidth @@ -589,29 +631,35 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B std::string error = llformat("Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); OSMessageBox(error, "Error", OSMB_OK); - return FALSE; } } - else + + if(!mFullscreen && (mWindow == NULL)) { + if (width == 0) + width = 1024; + if (height == 0) + width = 768; + + LL_INFOS() << "createContext: creating window " << width << "x" << height << "x" << bits << LL_ENDL; + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); + if (!mWindow && bits > 16) + { + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); + } + if (!mWindow) { LL_WARNS() << "createContext: window creation failure. SDL: " << SDL_GetError() << LL_ENDL; setupFailure("Window creation error", "Error", OSMB_OK); return FALSE; } + } else if (!mFullscreen && (mWindow != NULL)) + { + LL_INFOS() << "createContext: SKIPPING - !fullscreen, but +mWindow " << width << "x" << height << "x" << bits << LL_ENDL; } - // Set the application icon. - SDL_Surface *bmpsurface; - bmpsurface = Load_BMP_Resource("firestorm_icon.BMP"); - if (bmpsurface) - { - SDL_SetWindowIcon(mWindow, bmpsurface); - SDL_FreeSurface(bmpsurface); - bmpsurface = NULL; - } - // Detect video memory size. # if LL_X11 gGLManager.mVRAM = x11_detect_VRAM_kb() / 1024; @@ -624,7 +672,7 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B // fallback to letting SDL detect VRAM. // note: I've not seen SDL's detection ever actually find // VRAM != 0, but if SDL *does* detect it then that's a bonus. - gGLManager.mVRAM = 0; + gGLManager.mVRAM = video_info->video_mem / 1024; if (gGLManager.mVRAM != 0) { LL_INFOS() << "SDL detected " << gGLManager.mVRAM << "MB VRAM." << LL_ENDL; @@ -636,32 +684,46 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B // explicitly unsupported cards. //const char* RENDERER = (const char*) glGetString(GL_RENDERER); - SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redBits); - SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenBits); - SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueBits); - SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &alphaBits); - SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthBits); - SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilBits); + GLint depthBits, stencilBits, redBits, greenBits, blueBits, alphaBits; + + glGetIntegerv(GL_RED_BITS, &redBits); + glGetIntegerv(GL_GREEN_BITS, &greenBits); + glGetIntegerv(GL_BLUE_BITS, &blueBits); + glGetIntegerv(GL_ALPHA_BITS, &alphaBits); + glGetIntegerv(GL_DEPTH_BITS, &depthBits); + glGetIntegerv(GL_STENCIL_BITS, &stencilBits); LL_INFOS() << "GL buffer:" << LL_ENDL; - LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; - LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; - LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; - LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; - LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; - LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; + LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; + LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; + LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; + LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; + LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; + LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; GLint colorBits = redBits + greenBits + blueBits + alphaBits; // fixme: actually, it's REALLY important for picking that we get at // least 8 bits each of red,green,blue. Alpha we can be a bit more // relaxed about if we have to. +#if LL_SOLARIS && defined(__sparc) +// again the __sparc required because Xsun support, 32bit are very pricey on SPARC + if(colorBits < 24) //HACK: on SPARC allow 24-bit color +#else if (colorBits < 32) +#endif { close(); setupFailure( +#if LL_SOLARIS && defined(__sparc) + "Second Life requires at least 24-bit color on SPARC to run in a window.\n" + "Please use fbconfig to set your default color depth to 24 bits.\n" + "You may also need to adjust the X11 setting in SMF. To do so use\n" + " 'svccfg -s svc:/application/x11/x11-server setprop options/default_depth=24'\n" +#else "Second Life requires True Color (32-bit) to run in a window.\n" "Please go to Control Panels -> Display -> Settings and\n" "set the screen to 32-bit color.\n" +#endif "Alternately, if you choose to run fullscreen, Second Life\n" "will automatically adjust the screen each time it runs.", "Error", @@ -669,17 +731,36 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B return FALSE; } +#if 0 // *FIX: we're going to brave it for now... + if (alphaBits < 8) + { + close(); + setupFailure( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } +#endif + #if LL_X11 /* Grab the window manager specific information */ SDL_SysWMinfo info; SDL_VERSION(&info.version); - if ( SDL_GetWindowWMInfo(mWindow, &info) ) + if ( SDL_GetWMInfo(&info) ) { /* Save the information for later use */ if ( info.subsystem == SDL_SYSWM_X11 ) { mSDL_Display = info.info.x11.display; - mSDL_XWindowID = info.info.x11.window; + mSDL_XWindowID = info.info.x11.wmwindow; + Lock_Display = info.info.x11.lock_func; + Unlock_Display = info.info.x11.unlock_func; } else { @@ -695,10 +776,14 @@ BOOL LLWindowSDL::createContext(int x, int y, int width, int height, int bits, B #endif // LL_X11 - SDL_StartTextInput(); //make sure multisampling is disabled by default glDisable(GL_MULTISAMPLE_ARB); + // We need to do this here, once video is init'd + if (-1 == SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL)) + LL_WARNS() << "Couldn't enable key-repeat: " << SDL_GetError() <mX = mSurface->w; - size->mY = mSurface->h; - return (TRUE); + size->mX = mWindow->w; + size->mY = mWindow->h; + return (TRUE); } return (FALSE); @@ -881,11 +965,11 @@ BOOL LLWindowSDL::getSize(LLCoordScreen *size) BOOL LLWindowSDL::getSize(LLCoordWindow *size) { - if (mSurface) + if (mWindow) { - size->mX = mSurface->w; - size->mY = mSurface->h; - return (TRUE); + size->mX = mWindow->w; + size->mY = mWindow->h; + return (TRUE); } return (FALSE); @@ -902,45 +986,48 @@ BOOL LLWindowSDL::setPosition(const LLCoordScreen position) return TRUE; } -template< typename T > bool setSizeImpl( const T& newSize, SDL_Window *pWin ) -{ - if( !pWin ) - return false; - - auto nFlags = SDL_GetWindowFlags( pWin ); - - if( nFlags & SDL_WINDOW_MAXIMIZED ) - SDL_RestoreWindow( pWin ); - - - SDL_SetWindowSize( pWin, newSize.mX, newSize.mY ); - SDL_Event event; - event.type = SDL_WINDOWEVENT; - event.window.event = SDL_WINDOWEVENT_RESIZED; - event.window.windowID = SDL_GetWindowID( pWin ); - event.window.data1 = newSize.mX; - event.window.data2 = newSize.mY; - SDL_PushEvent( &event ); - - return true; -} - BOOL LLWindowSDL::setSizeImpl(const LLCoordScreen size) { - return ::setSizeImpl( size, mWindow ); + if(mWindow) + { + // Push a resize event onto SDL's queue - we'll handle it + // when it comes out again. + SDL_Event event; + event.type = SDL_VIDEORESIZE; + event.resize.w = size.mX; + event.resize.h = size.mY; + SDL_PushEvent(&event); // copied into queue + + return TRUE; + } + + return FALSE; } BOOL LLWindowSDL::setSizeImpl(const LLCoordWindow size) { - return ::setSizeImpl( size, mWindow ); + if(mWindow) + { + // Push a resize event onto SDL's queue - we'll handle it + // when it comes out again. + SDL_Event event; + event.type = SDL_VIDEORESIZE; + event.resize.w = size.mX; + event.resize.h = size.mY; + SDL_PushEvent(&event); // copied into queue + + return TRUE; + } + + return FALSE; } void LLWindowSDL::swapBuffers() { if (mWindow) - { - SDL_GL_SwapWindow( mWindow ); + { + SDL_GL_SwapBuffers(); } } @@ -962,7 +1049,7 @@ F32 LLWindowSDL::getGamma() BOOL LLWindowSDL::restoreGamma() { //CGDisplayRestoreColorSyncSettings(); - // SDL_SetGamma(1.0f, 1.0f, 1.0f); + SDL_SetGamma(1.0f, 1.0f, 1.0f); return true; } @@ -971,7 +1058,7 @@ BOOL LLWindowSDL::setGamma(const F32 gamma) mGamma = gamma; if (mGamma == 0) mGamma = 0.1f; mGamma = 1/mGamma; - // SDL_SetGamma(mGamma, mGamma, mGamma); + SDL_SetGamma(mGamma, mGamma, mGamma); return true; } @@ -1020,7 +1107,7 @@ BOOL LLWindowSDL::setCursorPosition(const LLCoordWindow position) //LL_INFOS() << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << LL_ENDL; // do the actual forced cursor move. - SDL_WarpMouseInWindow(mWindow, screen_pos.mX, screen_pos.mY); + SDL_WarpMouse(screen_pos.mX, screen_pos.mY); //LL_INFOS() << llformat("llcw %d,%d -> scr %d,%d", position.mX, position.mY, screen_pos.mX, screen_pos.mY) << LL_ENDL; @@ -1118,7 +1205,7 @@ void LLWindowSDL::beforeDialog() // it only works in X11 if (running_x11 && mWindow) { - SDL_SetWindowFullscreen( mWindow, 0 ); + SDL_WM_ToggleFullScreen(mWindow); } } } @@ -1160,7 +1247,7 @@ void LLWindowSDL::afterDialog() // in X11 if (running_x11 && mWindow) { - SDL_SetWindowFullscreen( mWindow, 0 ); + SDL_WM_ToggleFullScreen(mWindow); } } } @@ -1216,33 +1303,107 @@ void LLWindowSDL::flashIcon(F32 seconds) } +#if LL_GTK BOOL LLWindowSDL::isClipboardTextAvailable() { - return SDL_HasClipboardText()==SDL_TRUE ? TRUE: FALSE; + if (ll_try_gtk_init()) + { + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_NONE); + return gtk_clipboard_wait_is_text_available(clipboard) ? + TRUE : FALSE; + } + return FALSE; // failure } BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &text) { - char *pText = SDL_GetClipboardText(); - if( !pText ) - return FALSE; - - text = LLWString(utf8str_to_wstring(pText)); - SDL_free( pText ); - return TRUE; + if (ll_try_gtk_init()) + { + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_NONE); + gchar * const data = gtk_clipboard_wait_for_text(clipboard); + if (data) + { + text = LLWString(utf8str_to_wstring(data)); + g_free(data); + return TRUE; + } + } + return FALSE; // failure } BOOL LLWindowSDL::copyTextToClipboard(const LLWString &text) { - std::string utf8 = wstring_to_utf8str(text); - int res = SDL_SetClipboardText( utf8.c_str() ); - - if( res != 0 ) + if (ll_try_gtk_init()) { - LL_WARNS() << "SDL_SetClipboardText failed: " << SDL_GetError() << LL_ENDL; + const std::string utf8 = wstring_to_utf8str(text); + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_NONE); + gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); + return TRUE; } - - return res == 0 ? TRUE : FALSE; + return FALSE; // failure +} + + +BOOL LLWindowSDL::isPrimaryTextAvailable() +{ + if (ll_try_gtk_init()) + { + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_SELECTION_PRIMARY); + return gtk_clipboard_wait_is_text_available(clipboard) ? + TRUE : FALSE; + } + return FALSE; // failure +} + +BOOL LLWindowSDL::pasteTextFromPrimary(LLWString &text) +{ + if (ll_try_gtk_init()) + { + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gchar * const data = gtk_clipboard_wait_for_text(clipboard); + if (data) + { + text = LLWString(utf8str_to_wstring(data)); + g_free(data); + return TRUE; + } + } + return FALSE; // failure +} + +BOOL LLWindowSDL::copyTextToPrimary(const LLWString &text) +{ + if (ll_try_gtk_init()) + { + const std::string utf8 = wstring_to_utf8str(text); + GtkClipboard * const clipboard = + gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); + return TRUE; + } + return FALSE; // failure +} + +#else + +BOOL LLWindowSDL::isClipboardTextAvailable() +{ + return FALSE; // unsupported +} + +BOOL LLWindowSDL::pasteTextFromClipboard(LLWString &dst) +{ + return FALSE; // unsupported +} + +BOOL LLWindowSDL::copyTextToClipboard(const LLWString &s) +{ + return FALSE; // unsupported } BOOL LLWindowSDL::isPrimaryTextAvailable() @@ -1250,16 +1411,18 @@ BOOL LLWindowSDL::isPrimaryTextAvailable() return FALSE; // unsupported } -BOOL LLWindowSDL::pasteTextFromPrimary(LLWString &text) +BOOL LLWindowSDL::pasteTextFromPrimary(LLWString &dst) { return FALSE; // unsupported } -BOOL LLWindowSDL::copyTextToPrimary(const LLWString &text) +BOOL LLWindowSDL::copyTextToPrimary(const LLWString &s) { - return FALSE; // unsupported + return FALSE; // unsupported } +#endif // LL_GTK + LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_resolutions) { if (!mSupportedResolutions) @@ -1267,33 +1430,36 @@ LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_reso mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS]; mNumSupportedResolutions = 0; - // Use display no from mWindow/mSurface here? - int max = SDL_GetNumDisplayModes(0); - max = llclamp( max, 0, MAX_NUM_RESOLUTIONS ); + SDL_Rect **modes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); + if ( (modes != NULL) && (modes != ((SDL_Rect **) -1)) ) + { + int count = 0; + while (*modes && count= 800) && (h >= 600)) - { - // make sure we don't add the same resolution multiple times! - if ( (mNumSupportedResolutions == 0) || - ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && - (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) - { - mSupportedResolutions[mNumSupportedResolutions].mWidth = w; - mSupportedResolutions[mNumSupportedResolutions].mHeight = h; - mNumSupportedResolutions++; - } - } - } + while (count--) + { + modes--; + SDL_Rect *r = *modes; + int w = r->w; + int h = r->h; + if ((w >= 800) && (h >= 600)) + { + // make sure we don't add the same resolution multiple times! + if ( (mNumSupportedResolutions == 0) || + ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && + (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) + { + mSupportedResolutions[mNumSupportedResolutions].mWidth = w; + mSupportedResolutions[mNumSupportedResolutions].mHeight = h; + mNumSupportedResolutions++; + } + } + } + } } num_resolutions = mNumSupportedResolutions; @@ -1306,7 +1472,7 @@ BOOL LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) return FALSE; to->mX = from.mX; - to->mY = mSurface->h - from.mY - 1; + to->mY = mWindow->h - from.mY - 1; return TRUE; } @@ -1317,7 +1483,7 @@ BOOL LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) return FALSE; to->mX = from.mX; - to->mY = mSurface->h - from.mY - 1; + to->mY = mWindow->h - from.mY - 1; return TRUE; } @@ -1378,13 +1544,13 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) else mReallyCapturedCount = 0; - bool wantGrab; + SDL_GrabMode wantmode, newmode; if (mReallyCapturedCount <= 0) // uncapture { - wantGrab = false; + wantmode = SDL_GRAB_OFF; } else // capture { - wantGrab = true; + wantmode = SDL_GRAB_ON; } if (mReallyCapturedCount < 0) // yuck, imbalance. @@ -1393,11 +1559,9 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) LL_WARNS() << "ReallyCapture count was < 0" << LL_ENDL; } - bool newGrab = wantGrab; - -#if LL_X11 if (!mFullscreen) /* only bother if we're windowed anyway */ { +#if LL_X11 if (mSDL_Display) { /* we dirtily mix raw X11 with SDL so that our pointer @@ -1410,8 +1574,10 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) *keyboard* input from the window manager, which was frustrating users. */ int result; - if (wantGrab == true) + if (wantmode == SDL_GRAB_ON) { + //LL_INFOS() << "X11 POINTER GRABBY" << LL_ENDL; + //newmode = SDL_WM_GrabInput(wantmode); maybe_lock_display(); result = XGrabPointer(mSDL_Display, mSDL_XWindowID, True, 0, GrabModeAsync, @@ -1419,28 +1585,38 @@ BOOL LLWindowSDL::SDLReallyCaptureInput(BOOL capture) None, None, CurrentTime); maybe_unlock_display(); if (GrabSuccess == result) - newGrab = true; + newmode = SDL_GRAB_ON; else - newGrab = false; - } - else + newmode = SDL_GRAB_OFF; + } else if (wantmode == SDL_GRAB_OFF) { - newGrab = false; + //LL_INFOS() << "X11 POINTER UNGRABBY" << LL_ENDL; + newmode = SDL_GRAB_OFF; + //newmode = SDL_WM_GrabInput(SDL_GRAB_OFF); maybe_lock_display(); XUngrabPointer(mSDL_Display, CurrentTime); // Make sure the ungrab happens RIGHT NOW. XSync(mSDL_Display, False); maybe_unlock_display(); + } else + { + newmode = SDL_GRAB_QUERY; // neutral } - } - } + } else // not actually running on X11, for some reason + newmode = wantmode; #endif // LL_X11 + } else { + // pretend we got what we wanted, when really we don't care. + newmode = wantmode; + } + // return boolean success for whether we ended up in the desired state - return capture == newGrab; + return (capture && SDL_GRAB_ON==newmode) || + (!capture && SDL_GRAB_OFF==newmode); } -U32 LLWindowSDL::SDLCheckGrabbyKeys(U32 keysym, BOOL gain) +U32 LLWindowSDL::SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain) { /* part of the fix for SL-13243: Some popular window managers like to totally eat alt-drag for the purposes of moving windows. We @@ -1629,11 +1805,6 @@ void LLWindowSDL::gatherInput() { switch (event.type) { - case SDL_MOUSEWHEEL: - if( event.wheel.y != 0 ) - mCallbacks->handleScrollWheel(this, -event.wheel.y); - break; - case SDL_MOUSEMOTION: { LLCoordWindow winCoord(event.button.x, event.button.y); @@ -1644,45 +1815,35 @@ void LLWindowSDL::gatherInput() break; } - case SDL_TEXTINPUT: - { - auto string = utf8str_to_utf16str( event.text.text ); - for( auto key: string ) - { - mKeyVirtualKey = string[0]; - mKeyModifiers = SDL_GetModState(); - handleUnicodeUTF16( key, mKeyModifiers ); - } - break; - } - case SDL_KEYDOWN: - mKeyVirtualKey = event.key.keysym.sym; - mKeyModifiers = event.key.keysym.mod; + mKeyScanCode = event.key.keysym.scancode; + mKeyVirtualKey = event.key.keysym.unicode; + mKeyModifiers = event.key.keysym.mod; + mSDLSym = event.key.keysym.sym; // Store the SDL Keysym too. + + gKeyboard->handleKeyDown(event.key.keysym.sym, event.key.keysym.mod); + // part of the fix for SL-13243 + if (SDLCheckGrabbyKeys(event.key.keysym.sym, TRUE) != 0) + SDLReallyCaptureInput(TRUE); - gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers ); - - // Slightly hacky :| To make the viewer honor enter (eg to accept form input) we've to not only send handleKeyDown but also send a - // invoke handleUnicodeUTF16 in case the user hits return. - // Note that we cannot blindly use handleUnicodeUTF16 for each SDL_KEYDOWN. Doing so will create bogus keyboard input (like % for cursor left). - if( mKeyVirtualKey == SDLK_RETURN ) - handleUnicodeUTF16( mKeyVirtualKey, mKeyModifiers ); - - // part of the fix for SL-13243 - if (SDLCheckGrabbyKeys(event.key.keysym.sym, TRUE) != 0) - SDLReallyCaptureInput(TRUE); - - break; + if (event.key.keysym.unicode) + { + handleUnicodeUTF16(event.key.keysym.unicode, + gKeyboard->currentMask(FALSE)); + } + break; case SDL_KEYUP: - mKeyVirtualKey = event.key.keysym.sym; - mKeyModifiers = event.key.keysym.mod; + mKeyScanCode = event.key.keysym.scancode; + mKeyVirtualKey = event.key.keysym.unicode; + mKeyModifiers = event.key.keysym.mod; + mSDLSym = event.key.keysym.sym; // Store the SDL Keysym too. - if (SDLCheckGrabbyKeys(mKeyVirtualKey, FALSE) == 0) - SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 + if (SDLCheckGrabbyKeys(event.key.keysym.sym, FALSE) == 0) + SDLReallyCaptureInput(FALSE); // part of the fix for SL-13243 - gKeyboard->handleKeyUp(mKeyVirtualKey,mKeyModifiers); - break; + gKeyboard->handleKeyUp(event.key.keysym.sym, event.key.keysym.mod); + break; case SDL_MOUSEBUTTONDOWN: { @@ -1690,7 +1851,7 @@ void LLWindowSDL::gatherInput() LLCoordWindow winCoord(event.button.x, event.button.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(TRUE); + MASK mask = gKeyboard->currentMask(TRUE); if (event.button.button == SDL_BUTTON_LEFT) // SDL doesn't manage double clicking... { @@ -1702,7 +1863,7 @@ void LLWindowSDL::gatherInput() if (++leftClick >= 2) { leftClick = 0; - isDoubleClick = true; + isDoubleClick = true; } } lastLeftDown = now; @@ -1733,7 +1894,7 @@ void LLWindowSDL::gatherInput() else if (event.button.button == SDL_BUTTON_RIGHT) // right { - mCallbacks->handleRightMouseDown(this, openGlCoord, mask); + mCallbacks->handleRightMouseDown(this, openGlCoord, mask); } else if (event.button.button == SDL_BUTTON_MIDDLE) // middle @@ -1753,67 +1914,86 @@ void LLWindowSDL::gatherInput() LLCoordWindow winCoord(event.button.x, event.button.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(TRUE); + MASK mask = gKeyboard->currentMask(TRUE); if (event.button.button == SDL_BUTTON_LEFT) // left - mCallbacks->handleMouseUp(this, openGlCoord, mask); + mCallbacks->handleMouseUp(this, openGlCoord, mask); else if (event.button.button == SDL_BUTTON_RIGHT) // right - mCallbacks->handleRightMouseUp(this, openGlCoord, mask); + mCallbacks->handleRightMouseUp(this, openGlCoord, mask); else if (event.button.button == SDL_BUTTON_MIDDLE) // middle - mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); + mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); // don't handle mousewheel here... break; } - case SDL_WINDOWEVENT: // *FIX: handle this? - { - if( event.window.event == SDL_WINDOWEVENT_RESIZED - /* || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED*/ ) // SDL_WINDOWEVENT_SIZE_CHANGED is followed by SDL_WINDOWEVENT_RESIZED, so handling one shall be enough - { - LL_INFOS() << "Handling a resize event: " << event.window.data1 << "x" << event.window.data2 << LL_ENDL; - - S32 width = llmax(event.window.data1, (S32)mMinWindowWidth); - S32 height = llmax(event.window.data2, (S32)mMinWindowHeight); - mSurface = SDL_GetWindowSurface( mWindow ); - - // *FIX: I'm not sure this is necessary! - // I think is is not - // SDL_SetWindowSize(mWindow, width, height); - // - - mCallbacks->handleResize(this, width, height); - } - else if( event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ) // What about SDL_WINDOWEVENT_ENTER (mouse focus) - { - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - mHaveInputFocus = true; - - mCallbacks->handleFocus(this); - } - else if( event.window.event == SDL_WINDOWEVENT_FOCUS_LOST ) // What about SDL_WINDOWEVENT_LEAVE (mouse focus) - { - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - mHaveInputFocus = false; - - mCallbacks->handleFocusLost(this); - } - else if( event.window.event == SDL_WINDOWEVENT_MINIMIZED || - event.window.event == SDL_WINDOWEVENT_MAXIMIZED || - event.window.event == SDL_WINDOWEVENT_RESTORED) - { - mIsMinimized = (event.window.event == SDL_WINDOWEVENT_MINIMIZED); - - mCallbacks->handleActivate(this, !mIsMinimized); - LL_INFOS() << "SDL deiconification state switched to " << mIsMinimized << LL_ENDL; - } + case SDL_VIDEOEXPOSE: // VIDEOEXPOSE doesn't specify the damage, but hey, it's OpenGL...repaint the whole thing! + mCallbacks->handlePaint(this, 0, 0, mWindow->w, mWindow->h); + break; - break; + case SDL_VIDEORESIZE: // *FIX: handle this? + { + LL_INFOS() << "Handling a resize event: " << event.resize.w << + "x" << event.resize.h << LL_ENDL; + + S32 width = llmax(event.resize.w, (S32)mMinWindowWidth); + S32 height = llmax(event.resize.h, (S32)mMinWindowHeight); + + // *FIX: I'm not sure this is necessary! + mWindow = SDL_SetVideoMode(width, height, 32, mSDLFlags); + if (!mWindow) + { + // *FIX: More informative dialog? + LL_INFOS() << "Could not recreate context after resize! Quitting..." << LL_ENDL; + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + break; + } + + mCallbacks->handleResize(this, width, height); + break; + } + case SDL_ACTIVEEVENT: + if (event.active.state & SDL_APPINPUTFOCUS) + { + // Note that for SDL (particularly on X11), keyboard + // and mouse focus are independent things. Here we are + // tracking keyboard focus state changes. + + // We have to do our own state massaging because SDL + // can send us two unfocus events in a row for example, + // which confuses the focus code [SL-24071]. + if (event.active.gain != mHaveInputFocus) + { + mHaveInputFocus = !!event.active.gain; + + if (mHaveInputFocus) + mCallbacks->handleFocus(this); + else + mCallbacks->handleFocusLost(this); } + } + if (event.active.state & SDL_APPACTIVE) + { + // Change in iconification/minimization state. + if ((!event.active.gain) != mIsMinimized) + { + mIsMinimized = (!event.active.gain); + + mCallbacks->handleActivate(this, !mIsMinimized); + LL_INFOS() << "SDL deiconification state switched to " << BOOL(event.active.gain) << LL_ENDL; + } + else + { + LL_INFOS() << "Ignored bogus redundant SDL deiconification state switch to " << BOOL(event.active.gain) << LL_ENDL; + } + } + break; + case SDL_QUIT: if(mCallbacks->handleCloseRequest(this)) { @@ -1822,9 +2002,9 @@ void LLWindowSDL::gatherInput() // The app is responsible for calling destroyWindow when done with GL } break; - default: - //LL_INFOS() << "Unhandled SDL event type " << event.type << LL_ENDL; - break; + default: + //LL_INFOS() << "Unhandled SDL event type " << event.type << LL_ENDL; + break; } } @@ -1852,21 +2032,21 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty { SDL_Surface *cursurface; LL_DEBUGS() << "Loaded cursor file " << filename << " " - << bmpsurface->w << "x" << bmpsurface->h << LL_ENDL; + << bmpsurface->w << "x" << bmpsurface->h << LL_ENDL; cursurface = SDL_CreateRGBSurface (SDL_SWSURFACE, - bmpsurface->w, - bmpsurface->h, - 32, - SDL_SwapLE32(0xFFU), - SDL_SwapLE32(0xFF00U), - SDL_SwapLE32(0xFF0000U), - SDL_SwapLE32(0xFF000000U)); + bmpsurface->w, + bmpsurface->h, + 32, + SDL_SwapLE32(0xFFU), + SDL_SwapLE32(0xFF00U), + SDL_SwapLE32(0xFF0000U), + SDL_SwapLE32(0xFF000000U)); SDL_FillRect(cursurface, NULL, SDL_SwapLE32(0x00000000U)); // Blit the cursor pixel data onto a 32-bit RGBA surface so we // only have to cope with processing one type of pixel format. if (0 == SDL_BlitSurface(bmpsurface, NULL, - cursurface, NULL)) + cursurface, NULL)) { // n.b. we already checked that width is a multiple of 8. const int bitmap_bytes = (cursurface->w * cursurface->h) / 8; @@ -1898,9 +2078,9 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty } } sdlcursor = SDL_CreateCursor((Uint8*)cursor_data, - (Uint8*)cursor_mask, - cursurface->w, cursurface->h, - hotx, hoty); + (Uint8*)cursor_mask, + cursurface->w, cursurface->h, + hotx, hoty); delete[] cursor_data; delete[] cursor_mask; } else { @@ -2187,8 +2367,9 @@ S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 typ gWindowImplementation->mSDL_XWindowID != None) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(), gWindowImplementation->mSDL_XWindowID); - gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); + GdkWindow *gdkwin = gdk_window_foreign_new(gWindowImplementation->mSDL_XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); } # endif //LL_X11 @@ -2240,53 +2421,15 @@ S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 typ return rtn; } -BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) +static void color_changed_callback(GtkWidget *widget, + gpointer user_data) { - return FALSE; -} - -#else - -S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) -{ - SDL_MessageBoxData oData = { SDL_MESSAGEBOX_INFORMATION, nullptr, caption.c_str(), text.c_str(), 0, nullptr, nullptr }; - SDL_MessageBoxButtonData btnOk[] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }}; - SDL_MessageBoxButtonData btnOkCancel [] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_CANCEL, "Cancel"} }; - SDL_MessageBoxButtonData btnYesNo[] = { {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_YES, "Yes" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_NO, "No"} }; + GtkColorSelection *colorsel = GTK_COLOR_SELECTION(widget); + GdkColor *colorp = (GdkColor*)user_data; - switch (type) - { - default: - case OSMB_OK: - oData.flags = SDL_MESSAGEBOX_WARNING; - oData.buttons = btnOk; - oData.numbuttons = 1; - break; - case OSMB_OKCANCEL: - oData.flags = SDL_MESSAGEBOX_INFORMATION; - oData.buttons = btnOkCancel; - oData.numbuttons = 2; - break; - case OSMB_YESNO: - oData.flags = SDL_MESSAGEBOX_INFORMATION; - oData.buttons = btnYesNo; - oData.numbuttons = 2; - break; - } - - int btn{0}; - if( 0 == SDL_ShowMessageBox( &oData, &btn ) ) - return btn; - return OSBTN_CANCEL; + gtk_color_selection_get_current_color(colorsel, colorp); } -BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) -{ - return (FALSE); -} -#endif // LL_GTK - - /* Make the raw keyboard data available - used to poke through to LLQtWebKit so @@ -2294,7 +2437,7 @@ BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) */ LLSD LLWindowSDL::getNativeKeyData() { - LLSD result = LLSD::emptyMap(); + LLSD result = LLSD::emptyMap(); U32 modifiers = 0; // pretend-native modifiers... oh what a tangled web we weave! @@ -2312,13 +2455,98 @@ LLSD LLWindowSDL::getNativeKeyData() // *todo: test ALTs - I don't have a case for testing these. Do you? // *todo: NUM? - I don't care enough right now (and it's not a GDK modifier). - result["virtual_key"] = (S32)mKeyVirtualKey; - result["virtual_key_win"] = (S32)LLKeyboardSDL::mapSDL2toWin( mKeyVirtualKey ); + result["scan_code"] = (S32)mKeyScanCode; + result["virtual_key"] = (S32)mKeyVirtualKey; result["modifiers"] = (S32)modifiers; - - return result; + result[ "sdl_sym" ] = (S32)mSDLSym; // Store the SDL Keysym too. + return result; } + +BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) +{ + BOOL rtn = FALSE; + + beforeDialog(); + + if (ll_try_gtk_init()) + { + GtkWidget *win = NULL; + + win = gtk_color_selection_dialog_new(NULL); + +# if LL_X11 + // Get GTK to tell the window manager to associate this + // dialog with our non-GTK SDL window, which should try + // to keep it on top etc. + if (mSDL_XWindowID != None) + { + gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin + GdkWindow *gdkwin = gdk_window_foreign_new(mSDL_XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); + } +# endif //LL_X11 + + GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); + + GdkColor color, orig_color; + orig_color.pixel = 0; + orig_color.red = guint16(65535 * *r); + orig_color.green= guint16(65535 * *g); + orig_color.blue = guint16(65535 * *b); + color = orig_color; + + gtk_color_selection_set_previous_color (colorsel, &color); + gtk_color_selection_set_current_color (colorsel, &color); + gtk_color_selection_set_has_palette (colorsel, TRUE); + gtk_color_selection_set_has_opacity_control(colorsel, FALSE); + + gint response = GTK_RESPONSE_NONE; + g_signal_connect (win, + "response", + G_CALLBACK (response_callback), + &response); + + g_signal_connect (G_OBJECT (colorsel), "color_changed", + G_CALLBACK (color_changed_callback), + &color); + + gtk_window_set_modal(GTK_WINDOW(win), TRUE); + gtk_widget_show_all(win); + // hide the help button - we don't service it. + gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); + gtk_main(); + + if (response == GTK_RESPONSE_OK && + (orig_color.red != color.red + || orig_color.green != color.green + || orig_color.blue != color.blue) ) + { + *r = color.red / 65535.0f; + *g = color.green / 65535.0f; + *b = color.blue / 65535.0f; + rtn = TRUE; + } + } + + afterDialog(); + + return rtn; +} +#else +S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) +{ + LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; + return 0; +} + +BOOL LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) +{ + return (FALSE); +} +#endif // LL_GTK + #if LL_LINUX || LL_SOLARIS // extracted from spawnWebBrowser for clarity and to eliminate // compiler confusion regarding close(int fd) vs. LLWindow::close() diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 698f678b80..de0ee173ec 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -32,12 +32,12 @@ #include "llwindow.h" #include "lltimer.h" -#include "SDL2/SDL.h" -#include "SDL2/SDL_endian.h" +#include "SDL/SDL.h" +#include "SDL/SDL_endian.h" #if LL_X11 // get X11-specific headers for use in low-level stuff like copy-and-paste support -#include "SDL2/SDL_syswm.h" +#include "SDL/SDL_syswm.h" #endif // AssertMacros.h does bad things. @@ -182,7 +182,7 @@ protected: void destroyContext(); void setupFailure(const std::string& text, const std::string& caption, U32 type); void fixWindowSize(void); - U32 SDLCheckGrabbyKeys(U32 keysym, BOOL gain); + U32 SDLCheckGrabbyKeys(SDLKey keysym, BOOL gain); BOOL SDLReallyCaptureInput(BOOL capture); // @@ -190,12 +190,7 @@ protected: // U32 mGrabbyKeyFlags; int mReallyCapturedCount; - - SDL_Window* mWindow; - SDL_Surface* mSurface; - SDL_GLContext mContext; - SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; - + SDL_Surface * mWindow; std::string mWindowTitle; double mOriginalAspectRatio; BOOL mNeedsResize; // Constructor figured out the window is too big, it needs a resize. @@ -206,6 +201,7 @@ protected: int mSDLFlags; + SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ int mIsMinimized; /* 0=no, 1=yes, else unknown */ @@ -218,12 +214,12 @@ private: LLTimer mFlashTimer; #endif //LL_X11 - U32 mKeyVirtualKey; - U32 mKeyModifiers; + U32 mKeyScanCode; + U32 mKeyVirtualKey; + SDLMod mKeyModifiers; + U32 mSDLSym; // Store the SDL Keysym too. BOOL mUseLegacyCursors; // Legacy cursor setting from main program -private: - void tryFindFullscreenSize( int &aWidth, int &aHeight ); }; diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index f1b617baa4..32e440a829 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -1,18 +1,11 @@ # -*- cmake -*- - add_subdirectory(base) if (LINUX) - include(GLIB) - #add_subdirectory(gstreamer010) - #add_subdirectory(example) - if( GLIB_FOUND ) - add_subdirectory(gstreamer10) - else() - MESSAGE( "Building without gstreamer (missing GLIB)" ) - endif() - #add_subdirectory(libvlc) +# add_subdirectory(gstreamer010) add_subdirectory(cef) + add_subdirectory(libvlc) + #add_subdirectory(example) endif (LINUX) if (DARWIN) diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 8650b8cb8c..e845e81808 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -897,20 +897,12 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat // Keyboard handling for Linux. #if LL_LINUX - uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); - uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); - - if( native_virtual_key == '\n' ) - native_virtual_key = '\r'; - - mCEFLib->nativeKeyboardEventSDL2(key_event, native_virtual_key, native_modifiers, false); - - // Slightly hacky :| To make CEF honor enter (eg to accept form input) we've to not only send KE_KEY_UP/KE_KEY_DOWN - // but also a KE_KEY_CHAR event. - // Note that we cannot blindly send a KE_CHAR for each KE_KEY_UP. Doing so will create bogus keyboard input (like % for cursor left). - // Adding this just in llwindowsdl does not seem to fire an appropriate unicodeInput event down below, thus repeat this check here again :( - if( dullahan::KE_KEY_UP == key_event && native_virtual_key == '\r' ) - mCEFLib->nativeKeyboardEventSDL2(dullahan::KE_KEY_CHAR, native_virtual_key, native_modifiers, false); + uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["cef_modifiers"].asInteger()); + if( native_scan_code == '\n' ) + native_scan_code = '\r'; + mCEFLib->nativeKeyboardEvent(key_event, native_scan_code, native_virtual_key, native_modifiers); #endif // }; @@ -943,17 +935,6 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD U64 lparam = ll_U32_from_sd(native_key_data["l_param"]); mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif -// Keyboard handling for Linux. -#if LL_LINUX - uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); - uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); - - if( native_virtual_key == '\n' ) - native_virtual_key = '\r'; - - mCEFLib->nativeKeyboardEventSDL2(dullahan::KE_KEY_CHAR, native_virtual_key, native_modifiers, false); -#endif -// }; //////////////////////////////////////////////////////////////////////////////// diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt index 67454c96c4..571eb57b24 100644 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ b/indra/media_plugins/gstreamer010/CMakeLists.txt @@ -37,7 +37,7 @@ if(NOT ADDRESS_SIZE EQUAL 32) if(WINDOWS) ##add_definitions(/FIXED:NO) else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC -Wno-literal-suffix) + add_definitions(-fPIC) endif(WINDOWS) endif(NOT ADDRESS_SIZE EQUAL 32) @@ -69,7 +69,7 @@ target_link_libraries(media_plugin_gstreamer010 add_dependencies(media_plugin_gstreamer010 ${LLPLUGIN_LIBRARIES} ${MEDIA_PLUGIN_BASE_LIBRARIES} - llcommon + ${LLCOMMON_LIBRARIES} ) diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp index 6526706a24..6e8aa49377 100644 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp @@ -853,6 +853,15 @@ MediaPluginGStreamer010::startup() if (!mDoneInit) { +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32 ) + g_thread_init(NULL); +#endif + + // Init the glib type system - we need it. +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 35 ) + g_type_init(); +#endif + // Get symbols! #if LL_DARWIN if (! grab_gst_syms("libgstreamer-0.10.dylib", diff --git a/indra/media_plugins/gstreamer10/CMakeLists.txt b/indra/media_plugins/gstreamer10/CMakeLists.txt deleted file mode 100644 index 730a2c27fb..0000000000 --- a/indra/media_plugins/gstreamer10/CMakeLists.txt +++ /dev/null @@ -1,78 +0,0 @@ -# -*- cmake -*- - -project(media_plugin_gstreamer10) - -include(00-Common) -include(LLCommon) -include(LLImage) -include(LLPlugin) -include(LLMath) -include(LLRender) -include(LLWindow) -include(Linking) -include(PluginAPI) -include(MediaPluginBase) -include(OpenGL) -include(GLIB) - -include(GStreamer10Plugin) - -include_directories( - ${LLPLUGIN_INCLUDE_DIRS} - ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS} - ${LLCOMMON_INCLUDE_DIRS} - ${LLMATH_INCLUDE_DIRS} - ${LLIMAGE_INCLUDE_DIRS} - ${LLRENDER_INCLUDE_DIRS} - ${LLWINDOW_INCLUDE_DIRS} - ${GSTREAMER10_INCLUDE_DIRS} - ${GSTREAMER10_PLUGINS_BASE_INCLUDE_DIRS} -) -include_directories(SYSTEM - ${LLCOMMON_SYSTEM_INCLUDE_DIRS} - ) - -### media_plugin_gstreamer10 - -if(NOT WORD_SIZE EQUAL 32) - if(NOT WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif() -endif(NOT WORD_SIZE EQUAL 32) - -set(media_plugin_gstreamer10_SOURCE_FILES - media_plugin_gstreamer10.cpp - llmediaimplgstreamer_syms.cpp - ) - -set(media_plugin_gstreamer10_HEADER_FILES - llmediaimplgstreamer_syms.h - llmediaimplgstreamertriviallogging.h - ) - -add_library(media_plugin_gstreamer10 - SHARED - ${media_plugin_gstreamer10_SOURCE_FILES} -) - -target_link_libraries(media_plugin_gstreamer10 - ${LLPLUGIN_LIBRARIES} - ${MEDIA_PLUGIN_BASE_LIBRARIES} - ${LLCOMMON_LIBRARIES} - ${PLUGIN_API_WINDOWS_LIBRARIES} - ${GSTREAMER10_LIBRARIES} -) - -#add_dependencies(media_plugin_gstreamer10 -# ${LLPLUGIN_LIBRARIES} -# ${MEDIA_PLUGIN_BASE_LIBRARIES} -# ${LLCOMMON_LIBRARIES} -#) - -if (WINDOWS) - set_target_properties( - media_plugin_gstreamer10 - PROPERTIES - LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /NODEFAULTLIB:LIBCMT" - ) -endif (WINDOWS) diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h deleted file mode 100644 index 6bc272c009..0000000000 --- a/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @file llmediaimplgstreamer.h - * @author Tofu Linden - * @brief implementation that supports media playback via GStreamer. - * - * @cond - * $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$ - * @endcond - */ - -// header guard -#ifndef llmediaimplgstreamer_h -#define llmediaimplgstreamer_h - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include -#include - -#include "apr_pools.h" -#include "apr_dso.h" -} - - -extern "C" { -gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, - GstMessage *message, - gpointer data); -} - -#endif // LL_GSTREAMER010_ENABLED - -#endif // llmediaimplgstreamer_h diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp deleted file mode 100644 index e5e5c1c9a3..0000000000 --- a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.cpp - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $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$ - * @endcond - */ - -#include -#include -#include - -#ifdef LL_WINDOWS -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0502 -#include -#endif - -#include "linden_common.h" - -extern "C" { -#include -#include -} - -#include "apr_pools.h" -#include "apr_dso.h" - -#ifdef LL_WINDOWS - -#ifndef _M_AMD64 -#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86" -#define GSTREAMER_DIR_SUFFIX "1.0\\x86\\bin\\" -#else -#define GSTREAMER_REG_KEY "Software\\GStreamer1.0\\x86_64" -#define GSTREAMER_DIR_SUFFIX "1.0\\x86_64\\bin\\" -#endif - -bool openRegKey( HKEY &aKey ) -{ - // Try native (32 bit view/64 bit view) of registry first. - if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE, &aKey ) ) - return true; - - // If native view fails, use 32 bit view or registry. - if( ERROR_SUCCESS == ::RegOpenKeyExA( HKEY_LOCAL_MACHINE, GSTREAMER_REG_KEY, 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &aKey ) ) - return true; - - return false; -} - -std::string getGStreamerDir() -{ - std::string ret; - HKEY hKey; - - if( openRegKey( hKey ) ) - { - DWORD dwLen(0); - ::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, nullptr, &dwLen ); - - if( dwLen > 0 ) - { - std::vector< char > vctBuffer; - vctBuffer.resize( dwLen ); - ::RegQueryValueExA( hKey, "InstallDir", nullptr, nullptr, reinterpret_cast< LPBYTE>(&vctBuffer[ 0 ]), &dwLen ); - ret = &vctBuffer[0]; - - if( ret[ dwLen-1 ] != '\\' ) - ret += "\\"; - ret += GSTREAMER_DIR_SUFFIX; - - SetDllDirectoryA( ret.c_str() ); - } - ::RegCloseKey( hKey ); - } - return ret; -} -#else -std::string getGStreamerDir() { return ""; } -#endif - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) RTN (*ll##GSTSYM)(__VA_ARGS__) = NULL; -#include "llmediaimplgstreamer_syms_raw.inc" -#undef LL_GST_SYM - -struct Symloader -{ - bool mRequired; - char const *mName; - apr_dso_handle_sym_t *mPPFunc; -}; - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) { REQ, #GSTSYM , (apr_dso_handle_sym_t*)&ll##GSTSYM}, -Symloader sSyms[] = { -#include "llmediaimplgstreamer_syms_raw.inc" -{ false, 0, 0 } }; -#undef LL_GST_SYM - -// a couple of stubs for disgusting reasons -GstDebugCategory* -ll_gst_debug_category_new(gchar *name, guint color, gchar *description) -{ - static GstDebugCategory dummy; - return &dummy; -} -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname) -{ -} - -static bool sSymsGrabbed = false; -static apr_pool_t *sSymGSTDSOMemoryPool = NULL; - -std::vector< apr_dso_handle_t* > sLoadedLibraries; - -bool grab_gst_syms( std::vector< std::string > const &aDSONames ) -{ - if (sSymsGrabbed) - return true; - - //attempt to load the shared libraries - apr_pool_create(&sSymGSTDSOMemoryPool, NULL); - - for( std::vector< std::string >::const_iterator itr = aDSONames.begin(); itr != aDSONames.end(); ++itr ) - { - apr_dso_handle_t *pDSO(NULL); - std::string strDSO = getGStreamerDir() + *itr; - if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymGSTDSOMemoryPool )) - sLoadedLibraries.push_back( pDSO ); - - for( int i = 0; sSyms[ i ].mName; ++i ) - { - if( !*sSyms[ i ].mPPFunc ) - { - apr_dso_sym( sSyms[ i ].mPPFunc, pDSO, sSyms[ i ].mName ); - } - } - } - - std::stringstream strm; - bool sym_error = false; - for( int i = 0; sSyms[ i ].mName; ++i ) - { - if( sSyms[ i ].mRequired && ! *sSyms[ i ].mPPFunc ) - { - sym_error = true; - strm << sSyms[ i ].mName << std::endl; - } - } - - sSymsGrabbed = !sym_error; - return sSymsGrabbed; -} - - -void ungrab_gst_syms() -{ - // should be safe to call regardless of whether we've - // actually grabbed syms. - - for( std::vector< apr_dso_handle_t* >::iterator itr = sLoadedLibraries.begin(); itr != sLoadedLibraries.end(); ++itr ) - apr_dso_unload( *itr ); - - sLoadedLibraries.clear(); - - if ( sSymGSTDSOMemoryPool ) - { - apr_pool_destroy(sSymGSTDSOMemoryPool); - sSymGSTDSOMemoryPool = NULL; - } - - for( int i = 0; sSyms[ i ].mName; ++i ) - *sSyms[ i ].mPPFunc = NULL; - - sSymsGrabbed = false; -} - diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h deleted file mode 100644 index 0874644ee6..0000000000 --- a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.h - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $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$ - * @endcond - */ - -#include "linden_common.h" -#include -extern "C" { -#include -} - -bool grab_gst_syms( std::vector< std::string > const&); -void ungrab_gst_syms(); - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) extern RTN (*ll##GSTSYM)(__VA_ARGS__); -#include "llmediaimplgstreamer_syms_raw.inc" -#undef LL_GST_SYM - -// regrettable hacks to give us better runtime compatibility with older systems -#define llg_return_if_fail(COND) do{if (!(COND)) return;}while(0) -#define llg_return_val_if_fail(COND,V) do{if (!(COND)) return V;}while(0) - -// regrettable hacks because GStreamer was not designed for runtime loading -#undef GST_TYPE_MESSAGE -#define GST_TYPE_MESSAGE (llgst_message_get_type()) -#undef GST_TYPE_OBJECT -#define GST_TYPE_OBJECT (llgst_object_get_type()) -#undef GST_TYPE_PIPELINE -#define GST_TYPE_PIPELINE (llgst_pipeline_get_type()) -#undef GST_TYPE_ELEMENT -#define GST_TYPE_ELEMENT (llgst_element_get_type()) -#undef GST_TYPE_VIDEO_SINK -#define GST_TYPE_VIDEO_SINK (llgst_video_sink_get_type()) -// more regrettable hacks to stub-out these .h-exposed GStreamer internals -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname); -#undef _gst_debug_register_funcptr -#define _gst_debug_register_funcptr ll_gst_debug_register_funcptr -GstDebugCategory* ll_gst_debug_category_new(gchar *name, guint color, gchar *description); -#undef _gst_debug_category_new -#define _gst_debug_category_new ll_gst_debug_category_new -#undef __gst_debug_enabled -#define __gst_debug_enabled (0) - -// more hacks -#define LLGST_MESSAGE_TYPE_NAME(M) (llgst_message_type_get_name(GST_MESSAGE_TYPE(M))) diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc deleted file mode 100644 index da1aa8ec1d..0000000000 --- a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc +++ /dev/null @@ -1,68 +0,0 @@ -LL_GST_SYM(true, gst_buffer_new, GstBuffer*, void) -LL_GST_SYM(true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*) -LL_GST_SYM(true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err) -LL_GST_SYM(true, gst_message_get_type, GType, void) -LL_GST_SYM(true, gst_message_type_get_name, const gchar*, GstMessageType type) -LL_GST_SYM(true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GST_SYM(true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GST_SYM(true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending) -LL_GST_SYM(true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state) -LL_GST_SYM(true, gst_object_unref, void, gpointer object) -LL_GST_SYM(true, gst_object_get_type, GType, void) -LL_GST_SYM(true, gst_pipeline_get_type, GType, void) -LL_GST_SYM(true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline) -LL_GST_SYM(true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data) -LL_GST_SYM(true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name) -LL_GST_SYM(true, gst_element_get_type, GType, void) -LL_GST_SYM(true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template) -LL_GST_SYM(true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp) -LL_GST_SYM(true, gst_caps_from_string, GstCaps *, const gchar *string) -LL_GST_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index) -LL_GST_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type) -LL_GST_SYM(true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value) -LL_GST_SYM(true, gst_structure_get_value, G_CONST_RETURN GValue *, const GstStructure *structure, const gchar *fieldname) -LL_GST_SYM(true, gst_value_get_fraction_numerator, gint, const GValue *value) -LL_GST_SYM(true, gst_value_get_fraction_denominator, gint, const GValue *value) -LL_GST_SYM(true, gst_structure_get_name, G_CONST_RETURN gchar *, const GstStructure *structure) -LL_GST_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64) - -LL_GST_SYM(false, gst_registry_fork_set_enabled, void, gboolean enabled) -LL_GST_SYM(false, gst_segtrap_set_enabled, void, gboolean enabled) -LL_GST_SYM(false, gst_message_parse_buffering, void, GstMessage *message, gint *percent) -LL_GST_SYM(false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug) -LL_GST_SYM(false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur) -LL_GST_SYM(false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano) - -LL_GST_SYM( true, gst_message_parse_tag, void, GstMessage *, GstTagList **) -LL_GST_SYM( true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer) -LL_GST_SYM( true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *) -LL_GST_SYM( true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint) - -LL_GST_SYM( true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) - -LL_GST_SYM( true, gst_sample_get_caps, GstCaps*, GstSample* ) -LL_GST_SYM( true, gst_sample_get_buffer, GstBuffer*, GstSample* ) -LL_GST_SYM( true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags ) -LL_GST_SYM( true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* ) - -LL_GST_SYM( true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* ) -LL_GST_SYM( true, gst_app_sink_pull_sample, GstSample*, GstAppSink* ) - -LL_GST_SYM( true, g_free, void, gpointer ) -LL_GST_SYM( true, g_error_free, void, GError* ) - -LL_GST_SYM( true, g_main_context_pending, gboolean, GMainContext* ) -LL_GST_SYM( true, g_main_loop_get_context, GMainContext*, GMainLoop* ) -LL_GST_SYM( true, g_main_context_iteration, gboolean, GMainContext*, gboolean ) -LL_GST_SYM( true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean ) -LL_GST_SYM( true, g_main_loop_quit, void, GMainLoop* ) -LL_GST_SYM( true, gst_mini_object_unref, void, GstMiniObject* ) -LL_GST_SYM( true, g_object_set, void, gpointer, gchar const*, ... ) -LL_GST_SYM( true, g_source_remove, gboolean, guint ) -LL_GST_SYM( true, g_value_get_string, gchar const*, GValue const* ) - - -LL_GST_SYM( true, gst_debug_set_active, void, gboolean ) -LL_GST_SYM( true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify ) -LL_GST_SYM( true, gst_debug_set_default_threshold, void, GstDebugLevel ) -LL_GST_SYM( true, gst_debug_message_get , gchar const*, GstDebugMessage * ) \ No newline at end of file diff --git a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp deleted file mode 100644 index 5c931ea8f0..0000000000 --- a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp +++ /dev/null @@ -1,980 +0,0 @@ -/** - * @file media_plugin_gstreamer10.cpp - * @brief GStreamer-1.0 plugin for LLMedia API plugin system - * - * @cond - * $LicenseInfo:firstyear=2016&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2016, Linden Research, Inc. / Nicky Dasmijn - * - * 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$ - * @endcond - */ - -#define FLIP_Y - -#include "linden_common.h" - -#include "llgl.h" - -#include "llplugininstance.h" -#include "llpluginmessage.h" -#include "llpluginmessageclasses.h" -#include "media_plugin_base.h" - -#define G_DISABLE_CAST_CHECKS -extern "C" { -#include -#include - -} - -#include "llmediaimplgstreamer.h" -#include "llmediaimplgstreamer_syms.h" - -static inline void llgst_caps_unref( GstCaps * caps ) -{ - llgst_mini_object_unref( GST_MINI_OBJECT_CAST( caps ) ); -} - -static inline void llgst_sample_unref( GstSample *aSample ) -{ - llgst_mini_object_unref( GST_MINI_OBJECT_CAST( aSample ) ); -} - -////////////////////////////////////////////////////////////////////////////// -// -class MediaPluginGStreamer10 : public MediaPluginBase -{ -public: - MediaPluginGStreamer10(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); - ~MediaPluginGStreamer10(); - - /* virtual */ void receiveMessage(const char *message_string); - - static bool startup(); - static bool closedown(); - - gboolean processGSTEvents(GstBus *bus, GstMessage *message); - -private: - std::string getVersion(); - bool navigateTo( const std::string urlIn ); - bool seek( double time_sec ); - bool setVolume( float volume ); - - // misc - bool pause(); - bool stop(); - bool play(double rate); - bool getTimePos(double &sec_out); - - double MIN_LOOP_SEC = 1.0F; - U32 INTERNAL_TEXTURE_SIZE = 1024; - - bool mIsLooping; - - enum ECommand { - COMMAND_NONE, - COMMAND_STOP, - COMMAND_PLAY, - COMMAND_FAST_FORWARD, - COMMAND_FAST_REWIND, - COMMAND_PAUSE, - COMMAND_SEEK, - }; - ECommand mCommand; - -private: - bool unload(); - bool load(); - - bool update(int milliseconds); - void mouseDown( int x, int y ); - void mouseUp( int x, int y ); - void mouseMove( int x, int y ); - - static bool mDoneInit; - - guint mBusWatchID; - - float mVolume; - - int mDepth; - - // padded texture size we need to write into - int mTextureWidth; - int mTextureHeight; - - bool mSeekWanted; - double mSeekDestination; - - // Very GStreamer-specific - GMainLoop *mPump; // event pump for this media - GstElement *mPlaybin; - GstAppSink *mAppSink; -}; - -//static -bool MediaPluginGStreamer10::mDoneInit = false; - -MediaPluginGStreamer10::MediaPluginGStreamer10( LLPluginInstance::sendMessageFunction host_send_func, - void *host_user_data ) - : MediaPluginBase(host_send_func, host_user_data) - , mBusWatchID ( 0 ) - , mSeekWanted(false) - , mSeekDestination(0.0) - , mPump ( NULL ) - , mPlaybin ( NULL ) - , mAppSink ( NULL ) - , mCommand ( COMMAND_NONE ) -{ -} - -gboolean MediaPluginGStreamer10::processGSTEvents(GstBus *bus, GstMessage *message) -{ - if (!message) - return TRUE; // shield against GStreamer bug - - switch (GST_MESSAGE_TYPE (message)) - { - case GST_MESSAGE_BUFFERING: - { - // NEEDS GST 0.10.11+ - if (llgst_message_parse_buffering) - { - gint percent = 0; - llgst_message_parse_buffering(message, &percent); - } - break; - } - case GST_MESSAGE_STATE_CHANGED: - { - GstState old_state; - GstState new_state; - GstState pending_state; - llgst_message_parse_state_changed(message, - &old_state, - &new_state, - &pending_state); - - switch (new_state) - { - case GST_STATE_VOID_PENDING: - break; - case GST_STATE_NULL: - break; - case GST_STATE_READY: - setStatus(STATUS_LOADED); - break; - case GST_STATE_PAUSED: - setStatus(STATUS_PAUSED); - break; - case GST_STATE_PLAYING: - setStatus(STATUS_PLAYING); - break; - } - break; - } - case GST_MESSAGE_ERROR: - { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_error (message, &err, &debug); - if (err) - llg_error_free (err); - llg_free (debug); - - mCommand = COMMAND_STOP; - - setStatus(STATUS_ERROR); - - break; - } - case GST_MESSAGE_INFO: - { - if (llgst_message_parse_info) - { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_info (message, &err, &debug); - if (err) - llg_error_free (err); - llg_free (debug); - } - break; - } - case GST_MESSAGE_WARNING: - { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_warning (message, &err, &debug); - if (err) - llg_error_free (err); - llg_free (debug); - - break; - } - case GST_MESSAGE_EOS: - /* end-of-stream */ - if (mIsLooping) - { - double eos_pos_sec = 0.0F; - bool got_eos_position = getTimePos(eos_pos_sec); - - if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) - { - // if we know that the movie is really short, don't - // loop it else it can easily become a time-hog - // because of GStreamer spin-up overhead - // inject a COMMAND_PAUSE - mCommand = COMMAND_PAUSE; - } - else - { - stop(); - play(1.0); - } - } - else // not a looping media - { - // inject a COMMAND_STOP - mCommand = COMMAND_STOP; - } - break; - default: - /* unhandled message */ - break; - } - - /* we want to be notified again the next time there is a message - * on the bus, so return true (false means we want to stop watching - * for messages on the bus and our callback should not be called again) - */ - return TRUE; -} - -extern "C" { - gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, - GstMessage *message, - gpointer data) - { - MediaPluginGStreamer10 *impl = (MediaPluginGStreamer10*)data; - return impl->processGSTEvents(bus, message); - } -} // extern "C" - - - -bool MediaPluginGStreamer10::navigateTo ( const std::string urlIn ) -{ - if (!mDoneInit) - return false; // error - - setStatus(STATUS_LOADING); - - mSeekWanted = false; - - if (NULL == mPump || NULL == mPlaybin) - { - setStatus(STATUS_ERROR); - return false; // error - } - - llg_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL); - - // navigateTo implicitly plays, too. - play(1.0); - - return true; -} - - -class GstSampleUnref -{ - GstSample *mT; -public: - GstSampleUnref( GstSample *aT ) - : mT( aT ) - { llassert_always( mT ); } - - ~GstSampleUnref( ) - { llgst_sample_unref( mT ); } -}; - -bool MediaPluginGStreamer10::update(int milliseconds) -{ - if (!mDoneInit) - return false; // error - - // DEBUGMSG("updating media..."); - - // sanity check - if (NULL == mPump || NULL == mPlaybin) - { - return false; - } - - // see if there's an outstanding seek wanted - if (mSeekWanted && - // bleh, GST has to be happy that the movie is really truly playing - // or it may quietly ignore the seek (with rtsp:// at least). - (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) - { - seek(mSeekDestination); - mSeekWanted = false; - } - - // *TODO: time-limit - but there isn't a lot we can do here, most - // time is spent in gstreamer's own opaque worker-threads. maybe - // we can do something sneaky like only unlock the video object - // for 'milliseconds' and otherwise hold the lock. - while (llg_main_context_pending(llg_main_loop_get_context(mPump))) - { - llg_main_context_iteration(llg_main_loop_get_context(mPump), FALSE); - } - - // check for availability of a new frame - - if( !mAppSink ) - return true; - - if( GST_STATE(mPlaybin) != GST_STATE_PLAYING) // Do not try to pull a sample if not in playing state - return true; - - GstSample *pSample = llgst_app_sink_pull_sample( mAppSink ); - if(!pSample) - return false; // Done playing - - GstSampleUnref oSampleUnref( pSample ); - GstCaps *pCaps = llgst_sample_get_caps ( pSample ); - if (!pCaps) - return false; - - gint width, height; - GstStructure *pStruct = llgst_caps_get_structure ( pCaps, 0); - - int res = llgst_structure_get_int ( pStruct, "width", &width); - res |= llgst_structure_get_int ( pStruct, "height", &height); - - if( !mPixels ) - return true; - - GstBuffer *pBuffer = llgst_sample_get_buffer ( pSample ); - GstMapInfo map; - llgst_buffer_map ( pBuffer, &map, GST_MAP_READ); - - // Our render buffer is always 1kx1k - - U32 rowSkip = INTERNAL_TEXTURE_SIZE / mTextureHeight; - U32 colSkip = INTERNAL_TEXTURE_SIZE / mTextureWidth; - - for (int row = 0; row < mTextureHeight; ++row) - { - U8 const *pTexelIn = map.data + (row*rowSkip * width *3); -#ifndef FLIP_Y - U8 *pTexelOut = mPixels + (row * mTextureWidth * mDepth ); -#else - U8 *pTexelOut = mPixels + ((mTextureHeight-row-1) * mTextureWidth * mDepth ); -#endif - for( int col = 0; col < mTextureWidth; ++col ) - { - pTexelOut[ 0 ] = pTexelIn[0]; - pTexelOut[ 1 ] = pTexelIn[1]; - pTexelOut[ 2 ] = pTexelIn[2]; - pTexelOut += mDepth; - pTexelIn += colSkip*3; - } - } - - llgst_buffer_unmap( pBuffer, &map ); - setDirty(0,0,mTextureWidth,mTextureHeight); - - return true; -} - -void MediaPluginGStreamer10::mouseDown( int x, int y ) -{ - // do nothing -} - -void MediaPluginGStreamer10::mouseUp( int x, int y ) -{ - // do nothing -} - -void MediaPluginGStreamer10::mouseMove( int x, int y ) -{ - // do nothing -} - - -bool MediaPluginGStreamer10::pause() -{ - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); - return true; - } - return false; -} - -bool MediaPluginGStreamer10::stop() -{ - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_READY); - return true; - } - return false; -} - -bool MediaPluginGStreamer10::play(double rate) -{ - // NOTE: we don't actually support non-natural rate. - - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); - return true; - } - return false; -} - -bool MediaPluginGStreamer10::setVolume( float volume ) -{ - // we try to only update volume as conservatively as - // possible, as many gst-plugins-base versions up to at least - // November 2008 have critical race-conditions in setting volume - sigh - if (mVolume == volume) - return true; // nothing to do, everything's fine - - mVolume = volume; - if (mDoneInit && mPlaybin) - { - llg_object_set(mPlaybin, "volume", mVolume, NULL); - return true; - } - - return false; -} - -bool MediaPluginGStreamer10::seek(double time_sec) -{ - bool success = false; - if (mDoneInit && mPlaybin) - { - success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, - GstSeekFlags(GST_SEEK_FLAG_FLUSH | - GST_SEEK_FLAG_KEY_UNIT), - GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), - GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); - } - return success; -} - -bool MediaPluginGStreamer10::getTimePos(double &sec_out) -{ - bool got_position = false; - if (mDoneInit && mPlaybin) - { - gint64 pos(0); - GstFormat timefmt = GST_FORMAT_TIME; - got_position = - llgst_element_query_position && - llgst_element_query_position(mPlaybin, - &timefmt, - &pos); - got_position = got_position - && (timefmt == GST_FORMAT_TIME); - // GStreamer may have other ideas, but we consider the current position - // undefined if not PLAYING or PAUSED - got_position = got_position && - (GST_STATE(mPlaybin) == GST_STATE_PLAYING || - GST_STATE(mPlaybin) == GST_STATE_PAUSED); - if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) - { - if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) - { - // if we're playing then we treat an invalid clock time - // as 0, for complicated reasons (insert reason here) - pos = 0; - } - else - { - got_position = false; - } - - } - // If all the preconditions succeeded... we can trust the result. - if (got_position) - { - sec_out = double(pos) / double(GST_SECOND); // gst to sec - } - } - return got_position; -} - -bool MediaPluginGStreamer10::load() -{ - if (!mDoneInit) - return false; // error - - setStatus(STATUS_LOADING); - - mIsLooping = false; - mVolume = 0.1234567f; // minor hack to force an initial volume update - - // Create a pumpable main-loop for this media - mPump = llg_main_loop_new (NULL, FALSE); - if (!mPump) - { - setStatus(STATUS_ERROR); - return false; // error - } - - // instantiate a playbin element to do the hard work - mPlaybin = llgst_element_factory_make ("playbin", ""); - if (!mPlaybin) - { - setStatus(STATUS_ERROR); - return false; // error - } - - // get playbin's bus - GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); - if (!bus) - { - setStatus(STATUS_ERROR); - return false; // error - } - mBusWatchID = llgst_bus_add_watch (bus, - llmediaimplgstreamer_bus_callback, - this); - llgst_object_unref (bus); - - mAppSink = (GstAppSink*)(llgst_element_factory_make ("appsink", "")); - - GstCaps* pCaps = llgst_caps_new_simple( "video/x-raw", - "format", G_TYPE_STRING, "RGB", - "width", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, - "height", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, - NULL ); - - llgst_app_sink_set_caps( mAppSink, pCaps ); - llgst_caps_unref( pCaps ); - - if (!mAppSink) - { - setStatus(STATUS_ERROR); - return false; - } - - llg_object_set(mPlaybin, "video-sink", mAppSink, NULL); - - return true; -} - -bool MediaPluginGStreamer10::unload () -{ - if (!mDoneInit) - return false; // error - - // stop getting callbacks for this bus - llg_source_remove(mBusWatchID); - mBusWatchID = 0; - - if (mPlaybin) - { - llgst_element_set_state (mPlaybin, GST_STATE_NULL); - llgst_object_unref (GST_OBJECT (mPlaybin)); - mPlaybin = NULL; - } - - if (mPump) - { - llg_main_loop_quit(mPump); - mPump = NULL; - } - - mAppSink = NULL; - - setStatus(STATUS_NONE); - - return true; -} - -void LogFunction(GstDebugCategory *category, GstDebugLevel level, const gchar *file, const gchar *function, gint line, GObject *object, GstDebugMessage *message, gpointer user_data ) -#ifndef LL_LINUX // Docu says we need G_GNUC_NO_INSTRUMENT, but GCC says 'error' - G_GNUC_NO_INSTRUMENT -#endif -{ -#ifdef LL_LINUX - std::cerr << file << ":" << line << "(" << function << "): " << llgst_debug_message_get( message ) << std::endl; -#endif -} - -//static -bool MediaPluginGStreamer10::startup() -{ - // first - check if GStreamer is explicitly disabled - if (NULL != getenv("LL_DISABLE_GSTREAMER")) - return false; - - // only do global GStreamer initialization once. - if (!mDoneInit) - { - ll_init_apr(); - - // Get symbols! - std::vector< std::string > vctDSONames; -#if LL_DARWIN -#elif LL_WINDOWS - vctDSONames.push_back( "libgstreamer-1.0-0.dll" ); - vctDSONames.push_back( "libgstapp-1.0-0.dll" ); - vctDSONames.push_back( "libglib-2.0-0.dll" ); - vctDSONames.push_back( "libgobject-2.0-0.dll" ); -#else // linux or other ELFy unixoid - vctDSONames.push_back( "libgstreamer-1.0.so.0" ); - vctDSONames.push_back( "libgstapp-1.0.so.0" ); - vctDSONames.push_back( "libglib-2.0.so.0" ); - vctDSONames.push_back( "libgobject-2.0.so" ); -#endif - if( !grab_gst_syms( vctDSONames ) ) - { - return false; - } - - if (llgst_segtrap_set_enabled) - { - llgst_segtrap_set_enabled(FALSE); - } -#if LL_LINUX - // Gstreamer tries a fork during init, waitpid-ing on it, - // which conflicts with any installed SIGCHLD handler... - struct sigaction tmpact, oldact; - if (llgst_registry_fork_set_enabled ) { - // if we can disable SIGCHLD-using forking behaviour, - // do it. - llgst_registry_fork_set_enabled(false); - } - else { - // else temporarily install default SIGCHLD handler - // while GStreamer initialises - tmpact.sa_handler = SIG_DFL; - sigemptyset( &tmpact.sa_mask ); - tmpact.sa_flags = SA_SIGINFO; - sigaction(SIGCHLD, &tmpact, &oldact); - } -#endif // LL_LINUX - // Protect against GStreamer resetting the locale, yuck. - static std::string saved_locale; - saved_locale = setlocale(LC_ALL, NULL); - -// _putenv_s( "GST_PLUGIN_PATH", "E:\\gstreamer\\1.0\\x86\\lib\\gstreamer-1.0" ); - - llgst_debug_set_default_threshold( GST_LEVEL_WARNING ); - llgst_debug_add_log_function( LogFunction, NULL, NULL ); - llgst_debug_set_active( false ); - - // finally, try to initialize GStreamer! - GError *err = NULL; - gboolean init_gst_success = llgst_init_check(NULL, NULL, &err); - - // restore old locale - setlocale(LC_ALL, saved_locale.c_str() ); - -#if LL_LINUX - // restore old SIGCHLD handler - if (!llgst_registry_fork_set_enabled) - sigaction(SIGCHLD, &oldact, NULL); -#endif // LL_LINUX - - if (!init_gst_success) // fail - { - if (err) - { - llg_error_free(err); - } - return false; - } - - mDoneInit = true; - } - - return true; -} - -//static -bool MediaPluginGStreamer10::closedown() -{ - if (!mDoneInit) - return false; // error - - ungrab_gst_syms(); - - mDoneInit = false; - - return true; -} - -MediaPluginGStreamer10::~MediaPluginGStreamer10() -{ - closedown(); -} - - -std::string MediaPluginGStreamer10::getVersion() -{ - std::string plugin_version = "GStreamer10 media plugin, GStreamer version "; - if (mDoneInit && - llgst_version) - { - guint major, minor, micro, nano; - llgst_version(&major, &minor, µ, &nano); - plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, - (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, - (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); - } - else - { - plugin_version += "(unknown)"; - } - return plugin_version; -} - -void MediaPluginGStreamer10::receiveMessage(const char *message_string) -{ - LLPluginMessage message_in; - - if(message_in.parse(message_string) >= 0) - { - std::string message_class = message_in.getClass(); - std::string message_name = message_in.getName(); - - if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) - { - if(message_name == "init") - { - LLPluginMessage message("base", "init_response"); - LLSD versions = LLSD::emptyMap(); - versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; - versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; - versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; - message.setValueLLSD("versions", versions); - - load(); - - message.setValue("plugin_version", getVersion()); - sendMessage(message); - } - else if(message_name == "idle") - { - // no response is necessary here. - double time = message_in.getValueReal("time"); - - // Convert time to milliseconds for update() - update((int)(time * 1000.0f)); - } - else if(message_name == "cleanup") - { - unload(); - closedown(); - } - else if(message_name == "shm_added") - { - SharedSegmentInfo info; - info.mAddress = message_in.getValuePointer("address"); - info.mSize = (size_t)message_in.getValueS32("size"); - std::string name = message_in.getValue("name"); - - mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); - } - else if(message_name == "shm_remove") - { - std::string name = message_in.getValue("name"); - - SharedSegmentMap::iterator iter = mSharedSegments.find(name); - if(iter != mSharedSegments.end()) - { - if(mPixels == iter->second.mAddress) - { - // This is the currently active pixel buffer. Make sure we stop drawing to it. - mPixels = NULL; - mTextureSegmentName.clear(); - } - mSharedSegments.erase(iter); - } - - // Send the response so it can be cleaned up. - LLPluginMessage message("base", "shm_remove_response"); - message.setValue("name", name); - sendMessage(message); - } - } - else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) - { - if(message_name == "init") - { - // Plugin gets to decide the texture parameters to use. - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); - // lame to have to decide this now, it depends on the movie. Oh well. - mDepth = 4; - - mTextureWidth = 1; - mTextureHeight = 1; - - message.setValueU32("format", GL_RGBA); - message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); - - message.setValueS32("depth", mDepth); - message.setValueS32("default_width", INTERNAL_TEXTURE_SIZE ); - message.setValueS32("default_height", INTERNAL_TEXTURE_SIZE ); - message.setValueU32("internalformat", GL_RGBA8); - message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. - message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale - sendMessage(message); - } - else if(message_name == "size_change") - { - std::string name = message_in.getValue("name"); - S32 width = message_in.getValueS32("width"); - S32 height = message_in.getValueS32("height"); - S32 texture_width = message_in.getValueS32("texture_width"); - S32 texture_height = message_in.getValueS32("texture_height"); - - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); - message.setValue("name", name); - message.setValueS32("width", width); - message.setValueS32("height", height); - message.setValueS32("texture_width", texture_width); - message.setValueS32("texture_height", texture_height); - sendMessage(message); - - if(!name.empty()) - { - // Find the shared memory region with this name - SharedSegmentMap::iterator iter = mSharedSegments.find(name); - if(iter != mSharedSegments.end()) - { - mPixels = (unsigned char*)iter->second.mAddress; - mTextureSegmentName = name; - - mTextureWidth = texture_width; - mTextureHeight = texture_height; - memset( mPixels, 0, mTextureWidth*mTextureHeight*mDepth ); - } - - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); - message.setValue("name", mTextureSegmentName); - message.setValueS32("width", INTERNAL_TEXTURE_SIZE ); - message.setValueS32("height", INTERNAL_TEXTURE_SIZE ); - sendMessage(message); - - } - } - else if(message_name == "load_uri") - { - std::string uri = message_in.getValue("uri"); - navigateTo( uri ); - sendStatus(); - } - else if(message_name == "mouse_event") - { - std::string event = message_in.getValue("event"); - S32 x = message_in.getValueS32("x"); - S32 y = message_in.getValueS32("y"); - - if(event == "down") - { - mouseDown(x, y); - } - else if(event == "up") - { - mouseUp(x, y); - } - else if(event == "move") - { - mouseMove(x, y); - }; - }; - } - else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) - { - if(message_name == "stop") - { - stop(); - } - else if(message_name == "start") - { - double rate = 0.0; - if(message_in.hasValue("rate")) - { - rate = message_in.getValueReal("rate"); - } - // NOTE: we don't actually support rate. - play(rate); - } - else if(message_name == "pause") - { - pause(); - } - else if(message_name == "seek") - { - double time = message_in.getValueReal("time"); - // defer the actual seek in case we haven't - // really truly started yet in which case there - // is nothing to seek upon - mSeekWanted = true; - mSeekDestination = time; - } - else if(message_name == "set_loop") - { - bool loop = message_in.getValueBoolean("loop"); - mIsLooping = loop; - } - else if(message_name == "set_volume") - { - double volume = message_in.getValueReal("volume"); - setVolume(volume); - } - } - } -} - -int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) -{ - if( MediaPluginGStreamer10::startup() ) - { - MediaPluginGStreamer10 *self = new MediaPluginGStreamer10(host_send_func, host_user_data); - *plugin_send_func = MediaPluginGStreamer10::staticReceiveMessage; - *plugin_user_data = (void*)self; - - return 0; // okay - } - else - { - return -1; // failed to init - } -} diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index de4fea39bd..49bbd8520d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2089,13 +2089,9 @@ set(viewer_APPSETTINGS_FILES app_settings/viewerart.xml ${CMAKE_SOURCE_DIR}/../etc/message.xml ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg - #packages-info.txt + packages-info.txt ) -if( NOT USESYSTEMLIBS ) - set( viewer_APPSETTINGS_FILES ${viewer_APPSETTINGS_FILE} packages-info.txt ) -endif() - if (WINDOWS) LIST(APPEND viewer_APPSETTINGS_FILES app_settings/growl_notifications.xml) endif (WINDOWS) @@ -2180,11 +2176,11 @@ add_executable(${VIEWER_BINARY_NAME} ${viewer_SOURCE_FILES} ) -if (SDL_FOUND OR SDL2_FOUND) +if (SDL_FOUND) set_property(TARGET ${VIEWER_BINARY_NAME} PROPERTY COMPILE_DEFINITIONS LL_SDL=1 ) -endif () +endif (SDL_FOUND) if (BUGSPLAT_DB) set_property(TARGET ${VIEWER_BINARY_NAME} @@ -2491,7 +2487,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${BOOST_WAVE_LIBRARY} #FS specific ${BOOST_THREAD_LIBRARY} #FS specific ${BOOST_CONTEXT_LIBRARY} - ${GLIB_LIBRARIES} + ${DBUSGLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${FMODWRAPPER_LIBRARY} # must come after LLAudio ${OPENAL_LIBRARIES} @@ -2542,10 +2538,10 @@ if (NOT ENABLE_MEDIA_PLUGINS) set(COPY_INPUT_DEPENDENCIES ${VIEWER_BINARY_NAME} linux-crash-logger - #SLPlugin - #media_plugin_cef - #media_plugin_gstreamer10 - #media_plugin_libvlc + SLPlugin + media_plugin_cef + #media_plugin_gstreamer010 + media_plugin_libvlc llcommon ) else (NOT ENABLE_MEDIA_PLUGINS) @@ -2554,8 +2550,7 @@ else (NOT ENABLE_MEDIA_PLUGINS) linux-crash-logger SLPlugin media_plugin_cef - media_plugin_gstreamer10 - #media_plugin_libvlc + #media_plugin_gstreamer010 llcommon ) endif (NOT ENABLE_MEDIA_PLUGINS) diff --git a/indra/newview/desktopnotifierlinux.cpp b/indra/newview/desktopnotifierlinux.cpp index be5cd722c7..f3b2508409 100644 --- a/indra/newview/desktopnotifierlinux.cpp +++ b/indra/newview/desktopnotifierlinux.cpp @@ -38,25 +38,8 @@ #include -#ifdef LL_GLIB - #include -#else - -// ND: If we cannot use glib headers just stub out what'ns needed to compile. -// In theory that is enough to have a (leaky, see g_free) implementation. But to play it safe tryLoadLibnotify won't try to load any DSO, thus effectivly -// disabling the usage of libnotify. -typedef int gint; -typedef gint gboolean; -typedef char gchar; -struct GError; - -void g_free( void* ) -{ -} - -#endif typedef enum { @@ -82,6 +65,7 @@ typedef void (*pND_notify_notification_set_urgency) ( NotifyNotification *notifi // the unused one is always pushed first and qualifies just as dead weight. typedef NotifyNotification* (*pND_notify_notification_new) (const char *summary, const char *body, const char *icon, void*); + void* tryLoadLibnotify() { char const* aNames[] = { @@ -97,7 +81,6 @@ void* tryLoadLibnotify() void *pLibHandle(0); -#ifdef LL_GLIB for( int i = 0; !pLibHandle && aNames[i]; ++i ) { pLibHandle = dlopen( aNames[i], RTLD_NOW ); @@ -106,8 +89,7 @@ void* tryLoadLibnotify() else LL_INFOS( "DesktopNotifierLinux" ) << "Loaded " << aNames[i] << LL_ENDL; } -#endif - + return pLibHandle; }; @@ -316,12 +298,10 @@ void DesktopNotifierLinux::showNotification( const std::string& notification_tit { LL_INFOS( "DesktopNotifierLinux" ) << "Linux desktop notification type " << notification_type << " sent." << LL_ENDL; } -#if LL_GLIB else { LL_WARNS( "DesktopNotifierLinux" ) << "Linux desktop notification type " << notification_type << " FAILED to send, error was " << error->message << LL_ENDL; } -#endif } bool DesktopNotifierLinux::isUsable() diff --git a/indra/newview/exoflickr.cpp b/indra/newview/exoflickr.cpp index 39a1d8477a..287a4e6a4a 100644 --- a/indra/newview/exoflickr.cpp +++ b/indra/newview/exoflickr.cpp @@ -31,7 +31,12 @@ #include "llsdutil.h" #include "llflickrconnect.h" +// third-party +#if LL_USESYSTEMLIBS +#include "jsoncpp/reader.h" // JSON +#else #include "json/reader.h" // JSON +#endif #include #include diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index 479584c009..57da1b18ae 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -158,17 +158,6 @@ for ARG in "$@"; do fi done -# Hack, otherwise eg sscanf inside flatpak fails. -# This happens if the locale is set (for example) to de_DE which uses , as a decimal separator. -# There is code in the viewer which should handle this but when run from "flatpak run" it will not work. -# Needs some more investigation; but at least this hack will allow the viewer to run. -if [ ! -z "${FLATPAK_ID}" ] -then - echo "Setting LC_NUMERIC to en_US.utf8 due to running inside flatpak" - export LC_NUMERIC=en_US.utf8 -fi -# - # Run the program. # Don't quote $LL_WRAPPER because, if empty, it should simply vanish from the # command line. But DO quote "${ARGS[@]}": preserve separate args as diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 41d077bd11..67ebdb43e2 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -143,21 +143,10 @@ #include "stringize.h" #include "llcoros.h" #include "llexception.h" - -#if !LL_LINUX - #include "cef/dullahan.h" - #include "cef/dullahan_version.h" - #include "vlc/libvlc_version.h" -#else - #if LL_USESYSTEMLIBS - #include "dullahan.h" - #include "dullahan_version.h" - #else - #include "cef/dullahan.h" - #include "cef/dullahan_version.h" - #endif - -#endif +//#if !LL_LINUX +#include "cef/dullahan_version.h" +#include "vlc/libvlc_version.h" +//#endif // LL_LINUX // Third party library includes #include @@ -3910,7 +3899,7 @@ LLSD LLAppViewer::getViewerInfo() const // info["LIBCEF_VERSION"] = "Undefined"; //#endif -#if !LL_LINUX +//#if !LL_LINUX std::ostringstream vlc_ver_codec; vlc_ver_codec << LIBVLC_VERSION_MAJOR; vlc_ver_codec << "."; @@ -3918,9 +3907,9 @@ LLSD LLAppViewer::getViewerInfo() const vlc_ver_codec << "."; vlc_ver_codec << LIBVLC_VERSION_REVISION; info["LIBVLC_VERSION"] = vlc_ver_codec.str(); -#else - info["LIBVLC_VERSION"] = "Using gstreamer 10"; -#endif +//#else +// info["LIBVLC_VERSION"] = "Undefined"; +//#endif S32 packets_in = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN); if (packets_in > 0) diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 7b5adf53e1..cba36b8356 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -2,7 +2,7 @@ * @file llappviewerlinux.cpp * @brief The LLAppViewerLinux class definitions * -1 * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * @@ -194,6 +194,15 @@ LLAppViewerLinux::~LLAppViewerLinux() bool LLAppViewerLinux::init() { + // g_thread_init() must be called before *any* use of glib, *and* + // before any mutexes are held, *and* some of our third-party + // libraries likes to use glib functions; in short, do this here + // really early in app startup! + +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32 ) + if (!g_thread_supported ()) g_thread_init (NULL); +#endif + bool success = LLAppViewer::init(); #if LL_SEND_CRASH_REPORTS @@ -341,6 +350,10 @@ bool LLAppViewerLinux::initSLURLHandler() return false; // failed } +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 35 ) + g_type_init(); +#endif + //ViewerAppAPI *api_server = (ViewerAppAPI*) g_object_new(viewerappapi_get_type(), NULL); @@ -359,6 +372,10 @@ bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url) DBusGConnection *bus; GError *error = NULL; +#if ( !defined(GLIB_MAJOR_VERSION) && !defined(GLIB_MINOR_VERSION) ) || ( GLIB_MAJOR_VERSION < 2 ) || ( GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 35 ) + g_type_init(); +#endif + bus = lldbus_g_bus_get (DBUS_BUS_SESSION, &error); if (bus) { diff --git a/indra/newview/llappviewerlinux.h b/indra/newview/llappviewerlinux.h index 1196bc58c1..ed71ff36fd 100644 --- a/indra/newview/llappviewerlinux.h +++ b/indra/newview/llappviewerlinux.h @@ -27,17 +27,15 @@ #ifndef LL_LLAPPVIEWERLINUX_H #define LL_LLAPPVIEWERLINUX_H -#ifdef LL_GLIB extern "C" { # include - -#if LL_DBUS_ENABLED -# include -# include -#endif - } +#if LL_DBUS_ENABLED +extern "C" { +# include +# include +} #endif #ifndef LL_LLAPPVIEWER_H @@ -72,7 +70,7 @@ protected: virtual bool sendURLToOtherInstance(const std::string& url); }; -#if LL_DBUS_ENABLED && LL_GLIB +#if LL_DBUS_ENABLED typedef struct { GObject parent; diff --git a/indra/newview/lldirpicker.cpp b/indra/newview/lldirpicker.cpp index 3c72a3db28..b8e6e81ee6 100644 --- a/indra/newview/lldirpicker.cpp +++ b/indra/newview/lldirpicker.cpp @@ -41,11 +41,6 @@ # include "llfilepicker.h" #endif -#ifdef LL_FLTK - #include "FL/Fl.H" - #include "FL/Fl_Native_File_Chooser.H" -#endif - // // Globals // @@ -198,41 +193,32 @@ LLDirPicker::LLDirPicker() : mFileName(NULL), mLocked(false) { -#ifndef LL_FLTK mFilePicker = new LLFilePicker(); -#endif - reset(); } LLDirPicker::~LLDirPicker() { -#ifndef LL_FLTK delete mFilePicker; -#endif } void LLDirPicker::reset() { -#ifndef LL_FLTK if (mFilePicker) - mFilePicker->reset(); -#else - mDir = ""; -#endif + mFilePicker->reset(); } BOOL LLDirPicker::getDir(std::string* filename, bool blocking) { reset(); + // if local file browsing is turned off, return without opening dialog if ( check_local_file_access_enabled() == false ) { return FALSE; } -#ifndef LL_FLTK #if !LL_MESA_HEADLESS if (mFilePicker) @@ -251,38 +237,15 @@ BOOL LLDirPicker::getDir(std::string* filename, bool blocking) #endif // !LL_MESA_HEADLESS return FALSE; -#else - Fl_Native_File_Chooser flDlg; - flDlg.title("Pick a dir"); - flDlg.type(Fl_Native_File_Chooser::BROWSE_DIRECTORY ); - - int res = flDlg.show(); - if( res == 0 ) - { - char const *pDir = flDlg.filename(0); - if( pDir ) - mDir = pDir; - } - else if( res == -1 ) - { - LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL; - } - - return !mDir.empty(); -#endif } std::string LLDirPicker::getDirName() { -#ifndef LL_FLTK if (mFilePicker) { return mFilePicker->getFirstFile(); } return ""; -#else - return mDir; -#endif } #else // not implemented diff --git a/indra/newview/lldirpicker.h b/indra/newview/lldirpicker.h index 861913c51c..c7dba12130 100644 --- a/indra/newview/lldirpicker.h +++ b/indra/newview/lldirpicker.h @@ -80,7 +80,7 @@ private: #if LL_LINUX || LL_SOLARIS || LL_DARWIN // On Linux we just implement LLDirPicker on top of LLFilePicker - // LLFilePicker *mFilePicker; + LLFilePicker *mFilePicker; #endif diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index ec048ea752..db95618bd0 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -36,18 +36,10 @@ #include "llviewercontrol.h" #include "llwindow.h" // beforeDialog() -#undef LL_GTK #if LL_SDL -// #include "llwindowsdl.h" // for some X/GTK utils to help with filepickers -// #include +#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers #endif // LL_SDL -#ifdef LL_FLTK - #include "FL/Fl.H" - #include "FL/Fl_Native_File_Chooser.H" -#endif - - #if LL_LINUX || LL_SOLARIS #include "llhttpconstants.h" // file picker uses some of thes constants on Linux #endif @@ -1143,14 +1135,17 @@ GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::stri GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : GTK_FILE_CHOOSER_ACTION_OPEN); - gchar const *acceptText = is_folder ? "_Apply" :(is_save ? "_Save" : "_Open"); win = gtk_file_chooser_dialog_new(NULL, NULL, - pickertype, - "_Cancel", - GTK_RESPONSE_CANCEL, - acceptText, - GTK_RESPONSE_ACCEPT, - (gchar *)NULL); + pickertype, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + is_folder ? + GTK_STOCK_APPLY : + (is_save ? + GTK_STOCK_SAVE : + GTK_STOCK_OPEN), + GTK_RESPONSE_ACCEPT, + (gchar *)NULL); mCurContextName = context; // get the default path for this usage context if it's been @@ -1191,8 +1186,9 @@ GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::stri if (None != XWindowID) { gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_x11_window_foreign_new_for_display (gdk_display_get_default(),XWindowID); - gdk_window_set_transient_for(gtk_widget_get_window(GTK_WIDGET(win)), gdkwin); + GdkWindow *gdkwin = gdk_window_foreign_new(XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); } else { @@ -1570,79 +1566,12 @@ BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) return rtn; } -#elif LL_FLTK -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) -{ - return openFileDialog( filter, blocking, eSaveFile ); -} - -BOOL LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) -{ - return openFileDialog( filter, blocking, eOpenFile ); -} - -BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) -{ - return openFileDialog( filter, blocking, eOpenMultiple ); -} - -void setupFilter( Fl_Native_File_Chooser &chooser, LLFilePicker::ESaveFilter filter ) -{ -} - -void setupFilter( Fl_Native_File_Chooser &chooser, LLFilePicker::ELoadFilter filter ) -{ -} - -bool LLFilePicker::openFileDialog( int32_t filter, bool blocking, EType aType ) -{ - if ( check_local_file_access_enabled() == false ) - return false; - - reset(); - Fl_Native_File_Chooser::Type flType = Fl_Native_File_Chooser::BROWSE_FILE; - - if( aType == eOpenMultiple ) - flType = Fl_Native_File_Chooser::BROWSE_MULTI_FILE; - else if( aType == eSaveFile ) - flType = Fl_Native_File_Chooser::BROWSE_SAVE_FILE; - - Fl_Native_File_Chooser flDlg; - flDlg.title("Pick a file"); - flDlg.type( flType ); - - if( aType == eSaveFile ) - setupFilter( flDlg, (ESaveFilter) filter ); - else - setupFilter( flDlg, (ELoadFilter) filter ); - - int res = flDlg.show(); - if( res == 0 ) - { - int32_t count = flDlg.count(); - if( count < 0 ) - count = 0; - for( int32_t i = 0; i < count; ++i ) - { - char const *pFile = flDlg.filename(i); - if( pFile && strlen(pFile) > 0 ) - mFiles.push_back( pFile ); - } - } - else if( res == -1 ) - { - LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL; - } - - return mFiles.empty()?FALSE:TRUE; -} - # else // LL_GTK // Hacky stubs designed to facilitate fake getSaveFile and getOpenFile with // static results, when we don't have a real filepicker. -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) { // if local file browsing is turned off, return without opening dialog // (Even though this is a stub, I think we still should not return anything at all) @@ -1705,7 +1634,7 @@ BOOL LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) #else // not implemented -BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blockin ) +BOOL LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) { reset(); return FALSE; diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index ba3383d995..53ea7fd316 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -33,12 +33,6 @@ #ifndef LL_LLFILEPICKER_H #define LL_LLFILEPICKER_H -#if LL_FLTK - #if LL_GTK - #undef LL_GTK - #endif -#endif - #include "stdtypes.h" #if LL_DARWIN @@ -69,7 +63,7 @@ extern "C" { class LLFilePicker { -#if LL_GTK +#ifdef LL_GTK friend class LLDirPicker; friend void chooser_responder(GtkWidget *, gint, gpointer); #endif // LL_GTK @@ -193,14 +187,7 @@ private: // we also remember the extension of the last added file. std::string mCurrentExtension; #endif -#if LL_FLTK - enum EType - { - eSaveFile, eOpenFile, eOpenMultiple - }; - bool openFileDialog( int32_t filter, bool blocking, EType aType ); -#endif - + std::vector mFiles; S32 mCurrentFile; bool mLocked; diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp index 3059b6fcc4..e626143224 100644 --- a/indra/newview/llgroupmgr.cpp +++ b/indra/newview/llgroupmgr.cpp @@ -79,15 +79,6 @@ #pragma warning (disable:4702) #endif -// ND: Disable some warnings on newer GCC versions. -#if LL_LINUX -#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ ) >= 40800 - #pragma GCC diagnostic ignored "-Wuninitialized" - #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - #endif -#endif -// - #include #if LL_MSVC diff --git a/indra/newview/skins/default/xui/en/mime_types_linux.xml b/indra/newview/skins/default/xui/en/mime_types_linux.xml index 22a0024874..a130d2b0e9 100644 --- a/indra/newview/skins/default/xui/en/mime_types_linux.xml +++ b/indra/newview/skins/default/xui/en/mime_types_linux.xml @@ -130,7 +130,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -163,7 +163,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -174,7 +174,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -196,7 +196,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -218,7 +218,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -295,7 +295,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -306,7 +306,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -317,7 +317,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -328,7 +328,7 @@ audio - media_plugin_gstreamer + media_plugin_libvlc @@ -438,7 +438,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -449,7 +449,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -460,7 +460,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -471,7 +471,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -482,7 +482,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc @@ -493,7 +493,7 @@ movie - media_plugin_gstreamer + media_plugin_libvlc diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 975fc05657..e9fd0e3d35 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -1861,56 +1861,35 @@ class LinuxManifest(ViewerManifest): # plugins with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'media_plugins'), dst="bin/llplugin"): #self.path("gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so") - #self.path2basename("libvlc", "libmedia_plugin_libvlc.so") + self.path2basename("libvlc", "libmedia_plugin_libvlc.so") self.path("cef/libmedia_plugin_cef.so", "libmedia_plugin_cef.so" ) - #with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): - # self.path( "plugins.dat" ) - # self.path( "*/*.so" ) + with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): + self.path( "plugins.dat" ) + self.path( "*/*.so" ) - #with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): - # self.path( "libvlc*.so*" ) + with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): + self.path( "libvlc*.so*" ) - #with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): - # self.path( "plugins.dat" ) - # self.path( "*/*.so" ) + with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): + self.path( "plugins.dat" ) + self.path( "*/*.so" ) - #with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): - # self.path( "libvlc*.so*" ) + with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): + self.path( "libvlc*.so*" ) - snapStage = os.environ.get( "SNAPCRAFT_STAGE" ) - if snapStage != None: - print( "Building snap package" ) - else: - snapStage = os.environ.get( "FLATPAK_DEST" ) - if snapStage != None: - print( "Building flatpak package" ) - - if snapStage == None: - data = open( "/proc/1/cgroup", "r" ).readlines()[0] - if "docker" in data: - snapStage = "/usr" - - pkgBase = os.path.join( pkgdir, 'lib', 'release') - if snapStage != None: - pkgBase = os.path.join( snapStage, "lib" ) - # CEF files - with self.prefix(src=pkgBase, dst="lib"): + with self.prefix(src=os.path.join(pkgdir, 'lib', 'release'), dst="lib"): self.path( "libcef.so" ) self.fs_try_path( "libminigbm.so" ) - pkgBase = os.path.join( pkgBase, "swiftshader" ) - with self.prefix(src=pkgBase, dst=os.path.join("bin", "swiftshader") ): + with self.prefix(src=os.path.join(pkgdir, 'lib', 'release', 'swiftshader'), dst=os.path.join("bin", "swiftshader") ): self.path( "*.so" ) with self.prefix(src=os.path.join(pkgdir, 'lib', 'release', 'swiftshader'), dst=os.path.join("lib", "swiftshader") ): self.path( "*.so" ) - pkgBase = os.path.join(pkgdir, 'bin', 'release') - if snapStage != None: - pkgBase = os.path.join( snapStage, "lib" ) - with self.prefix(pkgBase, dst="bin"): + with self.prefix(src=os.path.join(pkgdir, 'bin', 'release'), dst="bin"): self.path( "chrome-sandbox" ) self.path( "dullahan_host" ) self.fs_try_path( "natives_blob.bin" ) @@ -1921,11 +1900,7 @@ class LinuxManifest(ViewerManifest): self.path( "snapshot_blob.bin" ) self.path( "v8_context_snapshot.bin" ) - pkgBase = os.path.join(pkgdir, 'resources') - if snapStage != None: - pkgBase = os.path.join( snapStage, "resources" ) - - with self.prefix(src=pkgBase, dst="bin"): + with self.prefix(src=os.path.join(pkgdir, 'resources'), dst="bin"): self.path( "cef.pak" ) self.path( "cef_extensions.pak" ) self.path( "cef_100_percent.pak" ) @@ -1940,9 +1915,7 @@ class LinuxManifest(ViewerManifest): self.path( "devtools_resources.pak" ) self.path( "icudtl.dat" ) - pkgBase = os.path.join( pkgBase, "locales" ) - - with self.prefix(src=pkgBase, dst=os.path.join('bin', 'locales')): + with self.prefix(src=os.path.join(pkgdir, 'resources', 'locales'), dst=os.path.join('bin', 'locales')): self.path("am.pak") self.path("ar.pak") self.path("bg.pak") @@ -2028,10 +2001,10 @@ class LinuxManifest(ViewerManifest): #self.path("libGLOD.so") #self.fs_path("libminizip.so") self.path("libuuid.so*") - self.path("libSDL*") - #self.path("libdirectfb*.so*") - #self.path("libfusion*.so*") - #self.path("libdirect*.so*") + self.path("libSDL-1.2.so*") + self.path("libdirectfb*.so*") + self.path("libfusion*.so*") + self.path("libdirect*.so*") self.fs_try_path("libopenjpeg.so*") self.path("libhunspell-1.3.so*") self.path("libalut.so*") @@ -2041,7 +2014,7 @@ class LinuxManifest(ViewerManifest): self.path("libopenal.so", "libopenal.so.1") # Install as versioned file in case it's missing from the 3p- and won't get copied below self.path("libopenal.so*") #self.path("libnotify.so.1.1.2", "libnotify.so.1") # LO - uncomment when testing libnotify(growl) on linux - self.fs_try_path("libpangox-1.0.so*") + self.path("libpangox-1.0.so*") # 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 @@ -2084,15 +2057,9 @@ class LinuxManifest(ViewerManifest): installer_name = "_".join(installer_name_components) #installer_name = self.installer_base_name() - createTar = True - if os.environ.get( "FS_CREATE_NO_TAR" ): - createTar = False - - - if createTar: - self.fs_delete_linux_symbols() # Delete old syms - self.strip_binaries() - self.fs_save_linux_symbols() # Package symbols, add debug link + self.fs_delete_linux_symbols() # Delete old syms + self.strip_binaries() + self.fs_save_linux_symbols() # Package symbols, add debug link # Fix access permissions self.run_command(['find', self.get_dst_prefix(), @@ -2101,39 +2068,30 @@ class LinuxManifest(ViewerManifest): self.run_command(['find', self.get_dst_prefix(), '-type', 'f', '-perm', old, '-exec', 'chmod', new, '{}', ';']) - - if os.environ.get( "SNAPCRAFT_STAGE" ) or os.environ.get( "FLATPAK_DEST" ): - print( "Building snap package, not calling tar to bundle" ) - self.package_file = "" - return - self.package_file = installer_name + '.tar.xz' - if createTar: - # temporarily move directory tree so that it has the right - # name in the tarfile - realname = self.get_dst_prefix() - tempname = self.build_path_of(installer_name) - self.run_command(["mv", realname, tempname]) - try: - # only create tarball if it's a release build. - if self.args['buildtype'].lower() == 'release': - # --numeric-owner hides the username of the builder for - # security etc. - self.run_command(['tar', '-C', self.get_build_prefix(), - '--numeric-owner', self.fs_linux_tar_excludes(), '-caf', - tempname + '.tar.xz', installer_name]) - else: - print( "Skipping %s.tar.xz for non-Release build (%s)" ) % \ - (installer_name, self.args['buildtype']) - finally: - self.run_command(["mv", tempname, realname]) + # temporarily move directory tree so that it has the right + # name in the tarfile + realname = self.get_dst_prefix() + tempname = self.build_path_of(installer_name) + self.run_command(["mv", realname, tempname]) + try: + # only create tarball if it's a release build. + if self.args['buildtype'].lower() == 'release': + # --numeric-owner hides the username of the builder for + # security etc. + self.run_command(['tar', '-C', self.get_build_prefix(), + '--numeric-owner', self.fs_linux_tar_excludes(), '-caf', + tempname + '.tar.xz', installer_name]) + else: + print ("Skipping %s.tar.xz for non-Release build (%s)" % \ + (installer_name, self.args['buildtype'])) + finally: + self.run_command(["mv", tempname, realname]) def strip_binaries(self): if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): print ("* Going strip-crazy on the packaged binaries, since this is a RELEASE build") - if not os.path.isdir( os.path.join( self.get_dst_prefix(), "lib") ): - os.mkdir( os.path.join( self.get_dst_prefix(), "lib") ) # makes some small assumptions about our packaged dir structure self.run_command( ["find"] + @@ -2165,7 +2123,7 @@ class Linux_i686_Manifest(LinuxManifest): self.path("libexpat.so.*") self.path("libGLOD.so") self.path("libuuid.so*") - self.path("libSDL*") + self.path("libSDL-1.2.so.*") self.path("libdirectfb-1.*.so.*") self.path("libfusion-1.*.so.*") self.path("libdirect-1.*.so.*") @@ -2244,7 +2202,7 @@ class Linux_x86_64_Manifest(LinuxManifest): if self.is_packaging_viewer(): with self.prefix(src=os.path.join(pkgdir, 'lib', 'release'), dst="lib"): - self.fs_try_path("libffi*.so*") + self.path("libffi*.so*") # vivox 32-bit hack. # one has to extract libopenal.so from the 32-bit openal package, or official LL viewer, and rename it to libopenal32.so # and place it in the prebuilt lib/release directory @@ -2263,7 +2221,6 @@ class Linux_x86_64_Manifest(LinuxManifest): with self.prefix(dst="bin"): self.path2basename("../llplugin/slplugin", "SLPlugin") - self.path("secondlife-i686.supp") From 88d837c16e768c5262073a7df965066d4bd8842c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 9 Mar 2021 14:49:29 -0500 Subject: [PATCH 123/125] Increment viewer version to 6.4.17 following promotion of DRTVWR-532 --- indra/newview/VIEWER_VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index da32c8395e..dbe6222fdd 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.4.16 +6.4.17 From 906cfe699d3a01a9be8f0583244a8d724cb623b9 Mon Sep 17 00:00:00 2001 From: PanteraPolnocy Date: Tue, 9 Mar 2021 23:57:43 +0100 Subject: [PATCH 124/125] Fixed various XML duplicates/mistakes, in various languages --- .../xui/de/floater_avatar_textures.xml | 2 +- .../skins/default/xui/de/floater_tools.xml | 4 +- .../skins/default/xui/de/menu_avatar_icon.xml | 2 +- .../skins/default/xui/de/menu_mini_map.xml | 2 +- .../skins/default/xui/de/notifications.xml | 14 +- .../xui/de/panel_preferences_alerts.xml | 1 - .../newview/skins/default/xui/de/strings.xml | 5 +- .../xui/es/floater_inventory_view_finder.xml | 2 - .../default/xui/es/floater_texture_ctrl.xml | 1 - .../skins/default/xui/es/floater_tools.xml | 68 +- .../skins/default/xui/es/notifications.xml | 733 +----------------- .../default/xui/es/panel_region_estate.xml | 2 - .../newview/skins/default/xui/es/strings.xml | 24 - .../default/xui/fr/floater_about_land.xml | 3 +- .../newview/skins/default/xui/fr/strings.xml | 15 - .../skins/default/xui/it/notifications.xml | 3 - .../default/xui/it/panel_preferences_UI.xml | 1 - .../skins/default/xui/ja/notifications.xml | 53 -- .../skins/default/xui/ja/panel_fs_radar.xml | 2 - .../xui/ja/panel_preferences_firestorm.xml | 1 - .../newview/skins/default/xui/ja/strings.xml | 18 - .../skins/default/xui/pl/notifications.xml | 7 - .../skins/default/xui/ru/menu_mini_map.xml | 1 - .../skins/default/xui/ru/notifications.xml | 5 +- .../default/xui/ru/panel_preferences_UI.xml | 1 - .../default/xui/ru/panel_tools_texture.xml | 1 - 26 files changed, 44 insertions(+), 927 deletions(-) diff --git a/indra/newview/skins/default/xui/de/floater_avatar_textures.xml b/indra/newview/skins/default/xui/de/floater_avatar_textures.xml index cb0f874176..73d591530c 100644 --- a/indra/newview/skins/default/xui/de/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/de/floater_avatar_textures.xml @@ -27,7 +27,7 @@ Texturen - + diff --git a/indra/newview/skins/default/xui/de/floater_tools.xml b/indra/newview/skins/default/xui/de/floater_tools.xml index 87cd62e168..a920cce952 100644 --- a/indra/newview/skins/default/xui/de/floater_tools.xml +++ b/indra/newview/skins/default/xui/de/floater_tools.xml @@ -92,11 +92,9 @@ þ: [COUNT] - + - - diff --git a/indra/newview/skins/default/xui/de/menu_avatar_icon.xml b/indra/newview/skins/default/xui/de/menu_avatar_icon.xml index 977b513514..ec6ebbef69 100644 --- a/indra/newview/skins/default/xui/de/menu_avatar_icon.xml +++ b/indra/newview/skins/default/xui/de/menu_avatar_icon.xml @@ -4,7 +4,7 @@ - + diff --git a/indra/newview/skins/default/xui/de/menu_mini_map.xml b/indra/newview/skins/default/xui/de/menu_mini_map.xml index 21360dddcf..a38e8c4f87 100644 --- a/indra/newview/skins/default/xui/de/menu_mini_map.xml +++ b/indra/newview/skins/default/xui/de/menu_mini_map.xml @@ -59,7 +59,7 @@

- + diff --git a/indra/newview/skins/default/xui/de/notifications.xml b/indra/newview/skins/default/xui/de/notifications.xml index 22978f5ca0..4df4f2379b 100644 --- a/indra/newview/skins/default/xui/de/notifications.xml +++ b/indra/newview/skins/default/xui/de/notifications.xml @@ -512,9 +512,6 @@ Um Medien nur auf einer Fläche einzufügen, wählen Sie „Oberfläche auswähl Fehler beim Erstellen des Fotos! - - Du brauchst L$[COST], um diesen Artikel hochzuladen. - Es kostet L$[COST], um ein Foto in Ihrem Inventar zu speichern. Sie können entweder L$ kaufen oder das Foto auf Ihrem Computer speichern. @@ -3526,9 +3523,6 @@ Diese werden für ein paar Sekunden sicherheitshalber gesperrt. Fehler beim Speichern des Bildes unter [PATH]: Zu wenig Speicherplatz auf dem Medium. [NEED_MEMORY]KB werden benötigt, es stehen jedoch nur [FREE_MEMORY]KB zur Verfügung. - - Fehler beim Speichern des Bildes unter [PATH]: Verzeichnis nicht vorhanden. - Fehler beim Speichern des Bildes unter [PATH]: Verzeichnis existiert nicht. @@ -3730,10 +3724,6 @@ Sie haben eine [RESOLUTION]-gebackene Textur für „[BODYREGION]“ nach [TIME] ( [EXISTENCE] Sekunden am Leben) Sie haben lokal eine [RESOLUTION]-gebackene Textur für „[BODYREGION]“ nach [TIME] Sekunden aktualisiert. - - - Textur kann nicht hochgeladen werden. -[REASON] Texture konnte nicht hochgeladen werden. @@ -5261,7 +5251,7 @@ Stream-Liste erfolgreich aus XML importiert. Einstellungen wurden gesichert. - + Sicherungspfad ist nicht gesetzt. Bitte zunächst einen Ort festlegen, an dem die Einstellungen gesichert sind und von wo sie wiederhergestellt werden sollen. @@ -5314,7 +5304,7 @@ Export unerwartet fehlgeschlagen. Siehe Log-Datei für weitergehende Details. [OBJECT] erfolgreich als [FILENAME] gespeichert. - + Export von [OBJECT] nach [FILENAME] fehlgeschlagen. diff --git a/indra/newview/skins/default/xui/de/panel_preferences_alerts.xml b/indra/newview/skins/default/xui/de/panel_preferences_alerts.xml index 151e82c73a..ba5551ac3a 100644 --- a/indra/newview/skins/default/xui/de/panel_preferences_alerts.xml +++ b/indra/newview/skins/default/xui/de/panel_preferences_alerts.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index 0a4f4fe8a6..2f9df6fae8 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -104,9 +104,6 @@ Voice-Serverversion: [VOICE_VERSION] [day, datetime, slt]. [month, datetime, slt] [year, datetime, slt] [hour24, datetime, slt]:[min, datetime, slt]:[second,datetime,slt] SLT - - [month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second,datetime,slt] - Fehler beim Abrufen der URL für die Server-Versionshinweise. @@ -6620,7 +6617,7 @@ Support-Bereich der Website Secondlife.com und melden Sie das Problem. Explodiert - + Penetriert diff --git a/indra/newview/skins/default/xui/es/floater_inventory_view_finder.xml b/indra/newview/skins/default/xui/es/floater_inventory_view_finder.xml index c2ce9b3c41..c484f4efb4 100644 --- a/indra/newview/skins/default/xui/es/floater_inventory_view_finder.xml +++ b/indra/newview/skins/default/xui/es/floater_inventory_view_finder.xml @@ -5,9 +5,7 @@ - - diff --git a/indra/newview/skins/default/xui/es/floater_texture_ctrl.xml b/indra/newview/skins/default/xui/es/floater_texture_ctrl.xml index 4b9704d61c..3b94cf10e0 100644 --- a/indra/newview/skins/default/xui/es/floater_texture_ctrl.xml +++ b/indra/newview/skins/default/xui/es/floater_texture_ctrl.xml @@ -49,5 +49,4 @@ - diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml index be3eb75781..82b2f5385b 100644 --- a/indra/newview/skins/default/xui/ru/notifications.xml +++ b/indra/newview/skins/default/xui/ru/notifications.xml @@ -437,14 +437,11 @@ Ошибка при кодировке снимка. - - Чтобы загрузить этот предмет, вам нужно L$[COST]. - Требуется L$[COST] для сохранения фото в вашем инвентаре. Купите L$ или сохраните фото на компьютере. - Вам нужно L$[COST] , чтобы загрузить этот элемент. + Чтобы загрузить этот предмет, вам нужно L$[COST]. Требуется L$[COST] для сохранения текстуры в вашем инвентаре. Купите L$ или сохраните фото на компьютере. diff --git a/indra/newview/skins/default/xui/ru/panel_preferences_UI.xml b/indra/newview/skins/default/xui/ru/panel_preferences_UI.xml index 7c8d8529bf..8baf6aed4f 100644 --- a/indra/newview/skins/default/xui/ru/panel_preferences_UI.xml +++ b/indra/newview/skins/default/xui/ru/panel_preferences_UI.xml @@ -34,7 +34,6 @@ Масштаб HUD: - Диалогов скрипта на объект: diff --git a/indra/newview/skins/default/xui/ru/panel_tools_texture.xml b/indra/newview/skins/default/xui/ru/panel_tools_texture.xml index 0c4f01b2a4..16b175fb17 100644 --- a/indra/newview/skins/default/xui/ru/panel_tools_texture.xml +++ b/indra/newview/skins/default/xui/ru/panel_tools_texture.xml @@ -26,7 +26,6 @@ - Альфа-режим From e6c34f01d27581fa51189599c418f05c389ed17d Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 10 Mar 2021 15:13:12 +0100 Subject: [PATCH 125/125] Revert revert of Simplified Cache viewer release - continue with simplified cache This reverts commit 40ad606ae086d156f8d9d5cf236e096662b52e70, reversing changes made to 906cfe699d3a01a9be8f0583244a8d724cb623b9. --- indra/CMakeLists.txt | 2 +- indra/cmake/00-Common.cmake | 3 +- indra/cmake/CMakeLists.txt | 2 +- indra/cmake/LLFileSystem.cmake | 7 + indra/cmake/LLVFS.cmake | 7 - .../llimage_libtest/CMakeLists.txt | 6 +- .../llui_libtest/CMakeLists.txt | 4 +- indra/linux_crash_logger/CMakeLists.txt | 7 +- indra/llappearance/CMakeLists.txt | 20 +- indra/llappearance/lltexlayer.cpp | 2 - indra/llaudio/CMakeLists.txt | 6 +- indra/llaudio/llaudiodecodemgr.cpp | 58 +- indra/llaudio/llaudiodecodemgr.h | 1 - indra/llaudio/llaudioengine.cpp | 15 +- indra/llaudio/llaudioengine.h | 13 +- indra/llcharacter/CMakeLists.txt | 18 +- indra/llcharacter/llkeyframefallmotion.cpp | 5 + indra/llcharacter/llkeyframemotion.cpp | 21 +- indra/llcharacter/llkeyframemotion.h | 12 +- indra/llcommon/llerror.cpp | 61 +- indra/llcrashlogger/CMakeLists.txt | 4 +- indra/llcrashlogger/llcrashlock.h | 2 +- indra/{llvfs => llfilesystem}/CMakeLists.txt | 57 +- indra/{llvfs => llfilesystem}/lldir.cpp | 0 indra/{llvfs => llfilesystem}/lldir.h | 0 indra/{llvfs => llfilesystem}/lldir_linux.cpp | 0 indra/{llvfs => llfilesystem}/lldir_linux.h | 0 indra/{llvfs => llfilesystem}/lldir_mac.cpp | 2 +- indra/{llvfs => llfilesystem}/lldir_mac.h | 0 .../{llvfs => llfilesystem}/lldir_solaris.cpp | 0 indra/{llvfs => llfilesystem}/lldir_solaris.h | 0 .../lldir_utils_objc.h} | 12 +- .../lldir_utils_objc.mm} | 8 +- indra/{llvfs => llfilesystem}/lldir_win32.cpp | 0 indra/{llvfs => llfilesystem}/lldir_win32.h | 0 indra/{llvfs => llfilesystem}/lldirguard.h | 0 .../{llvfs => llfilesystem}/lldiriterator.cpp | 0 indra/{llvfs => llfilesystem}/lldiriterator.h | 0 indra/llfilesystem/lldiskcache.cpp | 327 +++ indra/llfilesystem/lldiskcache.h | 183 ++ indra/llfilesystem/llfilesystem.cpp | 319 +++ indra/llfilesystem/llfilesystem.h | 81 + indra/{llvfs => llfilesystem}/lllfsthread.cpp | 0 indra/{llvfs => llfilesystem}/lllfsthread.h | 0 .../tests/lldir_test.cpp | 0 .../tests/lldiriterator_test.cpp | 0 indra/llimage/CMakeLists.txt | 6 +- indra/llimage/llimage.cpp | 9 - indra/llinventory/CMakeLists.txt | 4 +- indra/llmessage/CMakeLists.txt | 12 +- indra/llmessage/llassetstorage.cpp | 97 +- indra/llmessage/llassetstorage.h | 26 +- indra/llmessage/llcorehttputil.cpp | 4 +- indra/llmessage/llextendedstatus.h | 14 +- indra/llmessage/lltransfersourceasset.cpp | 8 +- indra/llmessage/lltransfersourceasset.h | 4 +- indra/llmessage/lltransfertargetvfile.cpp | 9 +- indra/llmessage/lltransfertargetvfile.h | 4 +- indra/llmessage/llxfer_vfile.cpp | 57 +- indra/llmessage/llxfer_vfile.h | 12 +- indra/llmessage/llxfermanager.cpp | 35 +- indra/llmessage/llxfermanager.h | 12 +- indra/llrender/CMakeLists.txt | 12 +- indra/llui/CMakeLists.txt | 6 +- indra/llui/llviewereventrecorder.h | 1 - indra/llvfs/llpidlock.cpp | 276 -- indra/llvfs/llpidlock.h | 60 - indra/llvfs/llvfile.cpp | 437 ---- indra/llvfs/llvfile.h | 90 - indra/llvfs/llvfs.cpp | 2260 ----------------- indra/llvfs/llvfs.h | 188 -- indra/llvfs/llvfsthread.cpp | 300 --- indra/llvfs/llvfsthread.h | 140 - indra/llwindow/CMakeLists.txt | 8 +- indra/llxml/CMakeLists.txt | 6 +- indra/mac_crash_logger/CMakeLists.txt | 7 +- indra/mac_crash_logger/mac_crash_logger.cpp | 1 - indra/newview/CMakeLists.txt | 10 +- indra/newview/aoengine.cpp | 13 +- indra/newview/aoengine.h | 2 +- indra/newview/app_settings/settings.xml | 85 +- indra/newview/app_settings/static_data.db2 | Bin 576578 -> 0 bytes indra/newview/app_settings/static_index.db2 | Bin 9894 -> 0 bytes indra/newview/fsassetblacklist.cpp | 4 +- indra/newview/fsdata.cpp | 4 +- indra/newview/fsfloaterexport.cpp | 8 +- indra/newview/fsfloaterexport.h | 2 +- indra/newview/fsfloaterimport.cpp | 10 +- indra/newview/fslslpreproc.cpp | 6 +- indra/newview/fslslpreproc.h | 2 +- indra/newview/llappviewer.cpp | 312 +-- indra/newview/llappviewer.h | 9 +- indra/newview/llappviewerwin32.cpp | 2 +- indra/newview/llcompilequeue.cpp | 14 +- indra/newview/llcompilequeue.h | 2 +- indra/newview/llfilepicker.h | 2 +- indra/newview/llfloaterauction.cpp | 11 +- indra/newview/llfloaterbvhpreview.cpp | 5 +- indra/newview/llfloatermodelpreview.cpp | 1 + indra/newview/llfloaterpreference.h | 2 +- indra/newview/llfloaterregioninfo.cpp | 11 +- indra/newview/llfloaterregioninfo.h | 4 +- indra/newview/llfloaterreporter.cpp | 12 +- indra/newview/llfloatertos.cpp | 2 +- indra/newview/llfloatertos.h | 1 - indra/newview/llgesturemgr.cpp | 20 +- indra/newview/llgesturemgr.h | 11 +- indra/newview/lllandmarklist.cpp | 5 +- indra/newview/lllandmarklist.h | 1 - indra/newview/llmeshrepository.cpp | 89 +- indra/newview/llmeshrepository.h | 1 - indra/newview/lloutfitgallery.cpp | 2 +- indra/newview/lloutfitgallery.h | 1 - indra/newview/llpostcard.cpp | 3 +- indra/newview/llpreviewgesture.cpp | 16 +- indra/newview/llpreviewgesture.h | 4 +- indra/newview/llpreviewnotecard.cpp | 16 +- indra/newview/llpreviewnotecard.h | 3 +- indra/newview/llpreviewscript.cpp | 33 +- indra/newview/llpreviewscript.h | 11 +- indra/newview/llsettingsvo.cpp | 10 +- indra/newview/llsettingsvo.h | 3 +- indra/newview/llsnapshotlivepreview.cpp | 6 +- indra/newview/llstartup.cpp | 16 +- indra/newview/lltexturecache.cpp | 46 +- indra/newview/llviewerassetstorage.cpp | 56 +- indra/newview/llviewerassetstorage.h | 8 +- indra/newview/llviewerassetupload.cpp | 26 +- indra/newview/llviewerassetupload.h | 2 +- .../newview/llviewermedia_streamingaudio.cpp | 2 - indra/newview/llviewermenufile.cpp | 2 - indra/newview/llviewermessage.cpp | 12 +- indra/newview/llviewermessage.h | 4 +- indra/newview/llviewerprecompiledheaders.h | 1 - indra/newview/llviewerstats.cpp | 3 - indra/newview/llviewerstats.h | 1 - indra/newview/llviewertexlayer.cpp | 12 +- indra/newview/llviewertexture.cpp | 2 - indra/newview/llviewertexture.h | 2 +- indra/newview/llviewertexturelist.cpp | 4 +- indra/newview/llviewerwearable.cpp | 1 - indra/newview/llvoavatar.cpp | 1 - indra/newview/llvovolume.h | 7 +- .../skins/default/xui/da/floater_stats.xml | 1 - .../newview/skins/default/xui/da/strings.xml | 3 - .../xui/de/floater_scene_load_stats.xml | 1 - .../skins/default/xui/de/floater_stats.xml | 1 - .../newview/skins/default/xui/de/strings.xml | 7 +- .../xui/en/floater_scene_load_stats.xml | 6 - .../skins/default/xui/en/floater_stats.xml | 5 - .../newview/skins/default/xui/en/strings.xml | 5 +- .../xui/es/floater_scene_load_stats.xml | 1 - .../skins/default/xui/es/floater_stats.xml | 1 - .../newview/skins/default/xui/es/strings.xml | 3 - .../xui/fr/floater_scene_load_stats.xml | 1 - .../skins/default/xui/fr/floater_stats.xml | 1 - .../newview/skins/default/xui/fr/strings.xml | 3 - .../xui/it/floater_scene_load_stats.xml | 1 - .../skins/default/xui/it/floater_stats.xml | 1 - .../newview/skins/default/xui/it/strings.xml | 6 - .../xui/ja/floater_scene_load_stats.xml | 1 - .../skins/default/xui/ja/floater_stats.xml | 1 - .../newview/skins/default/xui/ja/strings.xml | 3 - .../xui/pl/floater_scene_load_stats.xml | 1 - .../skins/default/xui/pl/floater_stats.xml | 1 - .../newview/skins/default/xui/pl/strings.xml | 7 +- .../xui/pt/floater_scene_load_stats.xml | 1 - .../skins/default/xui/pt/floater_stats.xml | 1 - .../newview/skins/default/xui/pt/strings.xml | 3 - .../xui/ru/floater_scene_load_stats.xml | 1 - .../skins/default/xui/ru/floater_stats.xml | 1 - .../newview/skins/default/xui/ru/strings.xml | 7 +- .../xui/tr/floater_scene_load_stats.xml | 1 - .../skins/default/xui/tr/floater_stats.xml | 1 - .../newview/skins/default/xui/tr/strings.xml | 3 - .../xui/zh/floater_scene_load_stats.xml | 1 - .../skins/default/xui/zh/floater_stats.xml | 1 - .../newview/skins/default/xui/zh/strings.xml | 3 - indra/newview/viewer_manifest.py | 1 - indra/test/CMakeLists.txt | 6 +- indra/win_crash_logger/CMakeLists.txt | 6 +- 181 files changed, 1569 insertions(+), 4878 deletions(-) create mode 100644 indra/cmake/LLFileSystem.cmake delete mode 100644 indra/cmake/LLVFS.cmake rename indra/{llvfs => llfilesystem}/CMakeLists.txt (51%) rename indra/{llvfs => llfilesystem}/lldir.cpp (100%) rename indra/{llvfs => llfilesystem}/lldir.h (100%) rename indra/{llvfs => llfilesystem}/lldir_linux.cpp (100%) rename indra/{llvfs => llfilesystem}/lldir_linux.h (100%) rename indra/{llvfs => llfilesystem}/lldir_mac.cpp (99%) rename indra/{llvfs => llfilesystem}/lldir_mac.h (100%) rename indra/{llvfs => llfilesystem}/lldir_solaris.cpp (100%) rename indra/{llvfs => llfilesystem}/lldir_solaris.h (100%) rename indra/{llvfs/llvfs_objc.h => llfilesystem/lldir_utils_objc.h} (85%) rename indra/{llvfs/llvfs_objc.mm => llfilesystem/lldir_utils_objc.mm} (95%) rename indra/{llvfs => llfilesystem}/lldir_win32.cpp (100%) rename indra/{llvfs => llfilesystem}/lldir_win32.h (100%) rename indra/{llvfs => llfilesystem}/lldirguard.h (100%) rename indra/{llvfs => llfilesystem}/lldiriterator.cpp (100%) rename indra/{llvfs => llfilesystem}/lldiriterator.h (100%) create mode 100644 indra/llfilesystem/lldiskcache.cpp create mode 100644 indra/llfilesystem/lldiskcache.h create mode 100644 indra/llfilesystem/llfilesystem.cpp create mode 100644 indra/llfilesystem/llfilesystem.h rename indra/{llvfs => llfilesystem}/lllfsthread.cpp (100%) rename indra/{llvfs => llfilesystem}/lllfsthread.h (100%) rename indra/{llvfs => llfilesystem}/tests/lldir_test.cpp (100%) rename indra/{llvfs => llfilesystem}/tests/lldiriterator_test.cpp (100%) delete mode 100644 indra/llvfs/llpidlock.cpp delete mode 100644 indra/llvfs/llpidlock.h delete mode 100644 indra/llvfs/llvfile.cpp delete mode 100644 indra/llvfs/llvfile.h delete mode 100644 indra/llvfs/llvfs.cpp delete mode 100644 indra/llvfs/llvfs.h delete mode 100644 indra/llvfs/llvfsthread.cpp delete mode 100644 indra/llvfs/llvfsthread.h delete mode 100644 indra/newview/app_settings/static_data.db2 delete mode 100644 indra/newview/app_settings/static_index.db2 diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index 0d8ee1fb9d..1beedb4961 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -89,7 +89,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llmath) add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) -add_subdirectory(${LIBS_OPEN_PREFIX}llvfs) +add_subdirectory(${LIBS_OPEN_PREFIX}llfilesystem) add_subdirectory(${LIBS_OPEN_PREFIX}llwindow) add_subdirectory(${LIBS_OPEN_PREFIX}llxml) diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index edaf2e3350..ce8b396cc1 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -73,12 +73,11 @@ if (WINDOWS) # CP changed to only append the flag for 32bit builds - on 64bit builds, # locally at least, the build output is spammed with 1000s of 'D9002' # warnings about this switch being ignored. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") # Remove this, it's no option to cl.exe and causes a massive amount of warnings. #if( ADDRESS_SIZE EQUAL 32 ) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /p:PreferredToolArchitecture=x64") #endif() - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo" CACHE STRING "C++ compiler release-with-debug options" FORCE) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 27ddb98e86..55e24df9b5 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -72,7 +72,7 @@ set(cmake_SOURCE_FILES LLSharedLibs.cmake LLTestCommand.cmake LLUI.cmake - LLVFS.cmake + LLFileSystem.cmake LLWindow.cmake LLXML.cmake Linking.cmake diff --git a/indra/cmake/LLFileSystem.cmake b/indra/cmake/LLFileSystem.cmake new file mode 100644 index 0000000000..2e6c42c30c --- /dev/null +++ b/indra/cmake/LLFileSystem.cmake @@ -0,0 +1,7 @@ +# -*- cmake -*- + +set(LLFILESYSTEM_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/llfilesystem + ) + +set(LLFILESYSTEM_LIBRARIES llfilesystem) diff --git a/indra/cmake/LLVFS.cmake b/indra/cmake/LLVFS.cmake deleted file mode 100644 index 0fe87cdea6..0000000000 --- a/indra/cmake/LLVFS.cmake +++ /dev/null @@ -1,7 +0,0 @@ -# -*- cmake -*- - -set(LLVFS_INCLUDE_DIRS - ${LIBS_OPEN_DIR}/llvfs - ) - -set(LLVFS_LIBRARIES llvfs) diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt index 5787d4d600..bd59f57e49 100644 --- a/indra/integration_tests/llimage_libtest/CMakeLists.txt +++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt @@ -10,11 +10,11 @@ include(LLImage) include(LLMath) include(LLImageJ2COJ) include(LLKDU) -include(LLVFS) +include(LLFileSystem) include_directories( ${LLCOMMON_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ) @@ -66,7 +66,7 @@ endif (DARWIN) target_link_libraries(llimage_libtest ${LEGACY_STDIO_LIBS} ${LLCOMMON_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLKDU_LIBRARIES} diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt index 13487dd42b..3957ede77f 100644 --- a/indra/integration_tests/llui_libtest/CMakeLists.txt +++ b/indra/integration_tests/llui_libtest/CMakeLists.txt @@ -16,7 +16,7 @@ include(LLMessage) include(LLRender) include(LLWindow) include(LLUI) -include(LLVFS) # ugh, needed for LLDir +include(LLFileSystem) include(LLXML) include(Hunspell) include(Linking) @@ -29,7 +29,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLUI_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LIBS_PREBUILD_DIR}/include/hunspell diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index d789c850a0..aa82ed12cc 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -9,7 +9,7 @@ include(LLCommon) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include(Linking) include(UI) @@ -21,7 +21,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCRASHLOGGER_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS} ) @@ -62,10 +62,9 @@ set(LIBRT_LIBRARY rt) target_link_libraries(linux-crash-logger ${LLCRASHLOGGER_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} - ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index 20eb4678dd..268849ad74 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -11,7 +11,7 @@ include(LLMath) include(LLMessage) include(LLCoreHttp) include(LLRender) -include(LLVFS) +include(LLFileSystem) include(LLWindow) include(LLXML) include(Linking) @@ -23,7 +23,7 @@ include_directories( ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) @@ -83,7 +83,7 @@ target_link_libraries(llappearance ${LLINVENTORY_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLRENDER_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -100,7 +100,7 @@ if (BUILD_HEADLESS) ${LLINVENTORY_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLRENDERHEADLESS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -109,15 +109,3 @@ if (BUILD_HEADLESS) ${LLCOMMON_LIBRARIES} ) endif (BUILD_HEADLESS) - -#add unit tests -#if (LL_TESTS) -# INCLUDE(LLAddBuildTest) -# SET(llappearance_TEST_SOURCE_FILES -# # no real unit tests yet! -# ) -# LL_ADD_PROJECT_UNIT_TESTS(llappearance "${llappearance_TEST_SOURCE_FILES}") - - #set(TEST_DEBUG on) -# set(test_libs llappearance ${LLCOMMON_LIBRARIES}) -#endif (LL_TESTS) diff --git a/indra/llappearance/lltexlayer.cpp b/indra/llappearance/lltexlayer.cpp index 2db9ac9fc0..dec14f90dc 100644 --- a/indra/llappearance/lltexlayer.cpp +++ b/indra/llappearance/lltexlayer.cpp @@ -33,8 +33,6 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "lldir.h" -#include "llvfile.h" -#include "llvfs.h" #include "lltexlayerparams.h" #include "lltexturemanagerbridge.h" #include "lllocaltextureobject.h" diff --git a/indra/llaudio/CMakeLists.txt b/indra/llaudio/CMakeLists.txt index 558ede7bf6..92a5cfe22f 100644 --- a/indra/llaudio/CMakeLists.txt +++ b/indra/llaudio/CMakeLists.txt @@ -9,14 +9,14 @@ include(OPENAL) include(LLCommon) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include_directories( ${LLAUDIO_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${OGG_INCLUDE_DIRS} ${VORBISENC_INCLUDE_DIRS} ${VORBISFILE_INCLUDE_DIRS} @@ -86,7 +86,7 @@ target_link_libraries( ${LLCOMMON_LIBRARIES} ${LLMATH_LIBRARIES} ${LLMESSAGE_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${VORBISENC_LIBRARIES} ${VORBISFILE_LIBRARIES} ${VORBIS_LIBRARIES} diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index c9643b8f2c..2278cd4afd 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -29,7 +29,7 @@ #include "llaudioengine.h" #include "lllfsthread.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llstring.h" #include "lldir.h" #include "llendianswizzle.h" @@ -90,19 +90,17 @@ protected: LLUUID mUUID; std::vector mWAVBuffer; -#if !defined(USE_WAV_VFILE) std::string mOutFilename; LLLFSThread::handle_t mFileHandle; -#endif - LLVFile *mInFilep; + LLFileSystem *mInFilep; OggVorbis_File mVF; S32 mCurrentSection; }; -size_t vfs_read(void *ptr, size_t size, size_t nmemb, void *datasource) +size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource) { - LLVFile *file = (LLVFile *)datasource; + LLFileSystem *file = (LLFileSystem *)datasource; if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/ { @@ -115,11 +113,11 @@ size_t vfs_read(void *ptr, size_t size, size_t nmemb, void *datasource) } } -S32 vfs_seek(void *datasource, ogg_int64_t offset, S32 whence) +S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence) { - LLVFile *file = (LLVFile *)datasource; + LLFileSystem *file = (LLFileSystem *)datasource; - // vfs has 31-bit files + // cache has 31-bit files if (offset > S32_MAX) { return -1; @@ -137,7 +135,7 @@ S32 vfs_seek(void *datasource, ogg_int64_t offset, S32 whence) origin = -1; break; default: - LL_ERRS("AudioEngine") << "Invalid whence argument to vfs_seek" << LL_ENDL; + LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL; return -1; } @@ -151,16 +149,16 @@ S32 vfs_seek(void *datasource, ogg_int64_t offset, S32 whence) } } -S32 vfs_close (void *datasource) +S32 cache_close (void *datasource) { - LLVFile *file = (LLVFile *)datasource; + LLFileSystem *file = (LLFileSystem *)datasource; delete file; return 0; } -long vfs_tell (void *datasource) +long cache_tell (void *datasource) { - LLVFile *file = (LLVFile *)datasource; + LLFileSystem *file = (LLFileSystem *)datasource; return file->tell(); } @@ -172,11 +170,10 @@ LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string & mUUID = uuid; mInFilep = NULL; mCurrentSection = 0; -#if !defined(USE_WAV_VFILE) mOutFilename = out_filename; mFileHandle = LLLFSThread::nullHandle(); -#endif - // No default value for mVF, it's an ogg structure? + + // No default value for mVF, it's an ogg structure? // Hey, let's zero it anyway, for predictability. memset(&mVF, 0, sizeof(mVF)); } @@ -193,15 +190,15 @@ LLVorbisDecodeState::~LLVorbisDecodeState() BOOL LLVorbisDecodeState::initDecode() { - ov_callbacks vfs_callbacks; - vfs_callbacks.read_func = vfs_read; - vfs_callbacks.seek_func = vfs_seek; - vfs_callbacks.close_func = vfs_close; - vfs_callbacks.tell_func = vfs_tell; + ov_callbacks cache_callbacks; + cache_callbacks.read_func = cache_read; + cache_callbacks.seek_func = cache_seek; + cache_callbacks.close_func = cache_close; + cache_callbacks.tell_func = cache_tell; LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL; - mInFilep = new LLVFile(gVFS, mUUID, LLAssetType::AT_SOUND); + mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND); if (!mInFilep || !mInFilep->getSize()) { LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL; @@ -210,7 +207,7 @@ BOOL LLVorbisDecodeState::initDecode() return FALSE; } - S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, vfs_callbacks); + S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks); if(r < 0) { LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL; @@ -370,7 +367,7 @@ BOOL LLVorbisDecodeState::decodeSection() { if (!mInFilep) { - LL_WARNS("AudioEngine") << "No VFS file to decode in vorbis!" << LL_ENDL; + LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL; return TRUE; } if (mDone) @@ -420,9 +417,7 @@ BOOL LLVorbisDecodeState::finishDecode() return TRUE; // We've finished } -#if !defined(USE_WAV_VFILE) if (mFileHandle == LLLFSThread::nullHandle()) -#endif { ov_clear(&mVF); @@ -495,11 +490,9 @@ BOOL LLVorbisDecodeState::finishDecode() mValid = FALSE; return TRUE; // we've finished } -#if !defined(USE_WAV_VFILE) mBytesRead = -1; mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, mWAVBuffer.size(), new WriteResponder(this)); -#endif } if (mFileHandle != LLLFSThread::nullHandle()) @@ -521,11 +514,6 @@ BOOL LLVorbisDecodeState::finishDecode() mDone = TRUE; -#if defined(USE_WAV_VFILE) - // write the data. - LLVFile output(gVFS, mUUID, LLAssetType::AT_SOUND_WAV); - output.write(&mWAVBuffer[0], mWAVBuffer.size()); -#endif LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL; return TRUE; @@ -535,7 +523,7 @@ void LLVorbisDecodeState::flushBadFile() { if (mInFilep) { - LL_WARNS("AudioEngine") << "Flushing bad vorbis file from VFS for " << mUUID << LL_ENDL; + LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL; mInFilep->remove(); // FIRE-15975; Delete the current file, or we might end with stale locks during the re-transfer diff --git a/indra/llaudio/llaudiodecodemgr.h b/indra/llaudio/llaudiodecodemgr.h index 8228e20e8c..ceaff3f2d8 100644 --- a/indra/llaudio/llaudiodecodemgr.h +++ b/indra/llaudio/llaudiodecodemgr.h @@ -33,7 +33,6 @@ #include "llassettype.h" #include "llframetimer.h" -class LLVFS; class LLVorbisDecodeState; class LLAudioDecodeMgr diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index 397b7355f1..d6f2913701 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -35,7 +35,7 @@ #include "sound_ids.h" // temporary hack for min/max distances -#include "llvfs.h" +#include "llfilesystem.h" #include "lldir.h" #include "llaudiodecodemgr.h" #include "llassetstorage.h" @@ -702,13 +702,9 @@ bool LLAudioEngine::preloadSound(const LLUUID &uuid) return true; } - // At some point we need to have the audio/asset system check the static VFS - // before it goes off and fetches stuff from the server. - //LL_WARNS() << "Used internal preload for non-local sound" << LL_ENDL; return false; } - bool LLAudioEngine::isWindEnabled() { return mEnableWind; @@ -1053,13 +1049,12 @@ bool LLAudioEngine::hasDecodedFile(const LLUUID &uuid) bool LLAudioEngine::hasLocalFile(const LLUUID &uuid) { - // See if it's in the VFS. - bool have_local = gVFS->getExists(uuid, LLAssetType::AT_SOUND); - LL_DEBUGS("AudioEngine") << "sound uuid "< -// This define is intended to allow us to switch from os based wav -// file loading to vfs based wav file loading. The problem is that I -// am unconvinced that the LLWaveFile works for loading sounds from -// memory. So, until that is fixed up, changed, whatever, this remains -// undefined. -//#define USE_WAV_VFILE - -class LLVFS; - class LLAudioSource; class LLAudioData; class LLAudioChannel; @@ -72,11 +63,9 @@ class LLAudioBuffer; class LLStreamingAudioInterface; struct SoundData; - // // LLAudioEngine definition // - class LLAudioEngine { friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods. @@ -197,7 +186,7 @@ public: // Asset callback when we're retrieved a sound from the asset server. void startNextTransfer(); - static void assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status); + static void assetCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status); // Output device selection typedef std::map output_device_map_t; diff --git a/indra/llcharacter/CMakeLists.txt b/indra/llcharacter/CMakeLists.txt index a17a5b0aa6..d90ffb5543 100644 --- a/indra/llcharacter/CMakeLists.txt +++ b/indra/llcharacter/CMakeLists.txt @@ -6,14 +6,14 @@ include(00-Common) include(LLCommon) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) include_directories(SYSTEM @@ -85,18 +85,6 @@ target_link_libraries( ${LLCOMMON_LIBRARIES} ${LLMATH_LIBRARIES} ${LLMESSAGE_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLXML_LIBRARIES} ) - - -# Add tests -#if (LL_TESTS) -# include(LLAddBuildTest) -# # UNIT TESTS -# SET(llcharacter_TEST_SOURCE_FILES -# lljoint.cpp -# ) -# LL_ADD_PROJECT_UNIT_TESTS(llcharacter "${llcharacter_TEST_SOURCE_FILES}") -#endif (LL_TESTS) - diff --git a/indra/llcharacter/llkeyframefallmotion.cpp b/indra/llcharacter/llkeyframefallmotion.cpp index 60ab2e9929..7842f0e5fb 100644 --- a/indra/llcharacter/llkeyframefallmotion.cpp +++ b/indra/llcharacter/llkeyframefallmotion.cpp @@ -70,6 +70,11 @@ LLMotion::LLMotionInitStatus LLKeyframeFallMotion::onInitialize(LLCharacter *cha // load keyframe data, setup pose and joint states LLMotion::LLMotionInitStatus result = LLKeyframeMotion::onInitialize(character); + if (result != LLMotion::STATUS_SUCCESS) + { + return result; + } + for (U32 jm=0; jmgetNumJointMotions(); jm++) { if (!mJointStates[jm]->getJoint()) diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index 524e9468a2..82059921f9 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -39,16 +39,15 @@ #include "llendianswizzle.h" #include "llkeyframemotion.h" #include "llquantize.h" -#include "llvfile.h" #include "m3math.h" #include "message.h" +#include "llfilesystem.h" #include "nd/ndexceptions.h" // For nd::exceptions::xran //----------------------------------------------------------------------------- // Static Definitions //----------------------------------------------------------------------------- -LLVFS* LLKeyframeMotion::sVFS = NULL; LLKeyframeDataCache::keyframe_data_map_t LLKeyframeDataCache::sKeyframeDataMap; //----------------------------------------------------------------------------- @@ -518,7 +517,7 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact return STATUS_SUCCESS; default: // we don't know what state the asset is in yet, so keep going - // check keyframe cache first then static vfs then asset request + // check keyframe cache first then file cache then asset request break; } @@ -562,13 +561,8 @@ LLMotion::LLMotionInitStatus LLKeyframeMotion::onInitialize(LLCharacter *charact U8 *anim_data; S32 anim_file_size; - if (!sVFS) - { - LL_ERRS() << "Must call LLKeyframeMotion::setVFS() first before loading a keyframe file!" << LL_ENDL; - } - BOOL success = FALSE; - LLVFile* anim_file = new LLVFile(sVFS, mID, LLAssetType::AT_ANIMATION); + LLFileSystem* anim_file = new LLFileSystem(mID, LLAssetType::AT_ANIMATION); if (!anim_file || !anim_file->getSize()) { delete anim_file; @@ -2356,10 +2350,9 @@ void LLKeyframeMotion::setLoopOut(F32 out_point) //----------------------------------------------------------------------------- // onLoadComplete() //----------------------------------------------------------------------------- -void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLKeyframeMotion::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLUUID* id = (LLUUID*)user_data; @@ -2391,7 +2384,7 @@ void LLKeyframeMotion::onLoadComplete(LLVFS *vfs, // asset already loaded return; } - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 size = file.getSize(); U8* buffer = new U8[size]; diff --git a/indra/llcharacter/llkeyframemotion.h b/indra/llcharacter/llkeyframemotion.h index 15c5c7c6c0..d640556090 100644 --- a/indra/llcharacter/llkeyframemotion.h +++ b/indra/llcharacter/llkeyframemotion.h @@ -44,7 +44,6 @@ #include "llbvhconsts.h" class LLKeyframeDataCache; -class LLVFS; class LLDataPacker; #define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f) @@ -141,10 +140,7 @@ public: virtual void setStopTime(F32 time); - static void setVFS(LLVFS* vfs) { sVFS = vfs; } - - static void onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); @@ -416,13 +412,7 @@ public: U32 getNumJointMotions() const { return mJointMotionArray.size(); } }; - protected: - static LLVFS* sVFS; - - //------------------------------------------------------------------------- - // Member Data - //------------------------------------------------------------------------- JointMotionList* mJointMotionList; std::vector > mJointStates; LLJoint* mPelvisp; diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 59f27c20bf..9c1b650107 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -196,23 +196,64 @@ namespace { { return LLError::getEnabledLogTypesMask() & 0x04; } - + + LL_FORCE_INLINE std::string createBoldANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "1"; + ansi_code += "m"; + + return ansi_code; + } + + LL_FORCE_INLINE std::string createResetANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "0"; + ansi_code += "m"; + + return ansi_code; + } + LL_FORCE_INLINE std::string createANSI(const std::string& color) { std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += color; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "38;5;"; + ansi_code += color; ansi_code += "m"; + return ansi_code; } virtual void recordMessage(LLError::ELevel level, const std::string& message) override { - static std::string s_ansi_error = createANSI("31"); // red - static std::string s_ansi_warn = createANSI("34"); // blue - static std::string s_ansi_debug = createANSI("35"); // magenta + // The default colors for error, warn and debug are now a bit more pastel + // and easier to read on the default (black) terminal background but you + // now have the option to set the color of each via an environment variables: + // LL_ANSI_ERROR_COLOR_CODE (default is red) + // LL_ANSI_WARN_COLOR_CODE (default is blue) + // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) + // The list of color codes can be found in many places but I used this page: + // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors + // (Note: you may need to restart Visual Studio to pick environment changes) + char* val = nullptr; + std::string s_ansi_error_code = "160"; + if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); + std::string s_ansi_warn_code = "33"; + if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); + std::string s_ansi_debug_code = "177"; + if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); + + static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red + static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue + static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta if (mUseANSI) { @@ -234,11 +275,11 @@ namespace { LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) { - static std::string s_ansi_bold = createANSI("1"); // bold - static std::string s_ansi_reset = createANSI("0"); // reset + static std::string s_ansi_bold = createBoldANSI(); // bold text + static std::string s_ansi_reset = createResetANSI(); // reset // ANSI color code escape sequence, message, and reset in one fprintf call // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. - fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); + fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); } static bool checkANSI(void) diff --git a/indra/llcrashlogger/CMakeLists.txt b/indra/llcrashlogger/CMakeLists.txt index 38aa883790..b35e5a01c3 100644 --- a/indra/llcrashlogger/CMakeLists.txt +++ b/indra/llcrashlogger/CMakeLists.txt @@ -7,7 +7,7 @@ include(LLCoreHttp) include(LLCommon) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include_directories( @@ -15,7 +15,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) include_directories(SYSTEM diff --git a/indra/llcrashlogger/llcrashlock.h b/indra/llcrashlogger/llcrashlock.h index cde183272f..60b060b736 100644 --- a/indra/llcrashlogger/llcrashlock.h +++ b/indra/llcrashlogger/llcrashlock.h @@ -1,5 +1,5 @@ /** - * @file llpidlock.h + * @file llcrashlock.h * @brief Maintainence of disk locking files for crash reporting * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ diff --git a/indra/llvfs/CMakeLists.txt b/indra/llfilesystem/CMakeLists.txt similarity index 51% rename from indra/llvfs/CMakeLists.txt rename to indra/llfilesystem/CMakeLists.txt index 67dce8c073..09c4c33ebf 100644 --- a/indra/llvfs/CMakeLists.txt +++ b/indra/llfilesystem/CMakeLists.txt @@ -1,6 +1,6 @@ # -*- cmake -*- -project(llvfs) +project(llfilesystem) include(00-Common) include(LLCommon) @@ -11,39 +11,34 @@ include_directories( ${LLCOMMON_SYSTEM_INCLUDE_DIRS} ) -set(llvfs_SOURCE_FILES +set(llfilesystem_SOURCE_FILES lldir.cpp lldiriterator.cpp lllfsthread.cpp - llpidlock.cpp - llvfile.cpp - llvfs.cpp - llvfsthread.cpp + lldiskcache.cpp + llfilesystem.cpp ) -set(llvfs_HEADER_FILES +set(llfilesystem_HEADER_FILES CMakeLists.txt - lldir.h lldirguard.h lldiriterator.h lllfsthread.h - llpidlock.h - llvfile.h - llvfs.h - llvfsthread.h + lldiskcache.h + llfilesystem.h ) if (DARWIN) - LIST(APPEND llvfs_SOURCE_FILES lldir_mac.cpp) - LIST(APPEND llvfs_HEADER_FILES lldir_mac.h) - LIST(APPEND llvfs_SOURCE_FILES llvfs_objc.mm) - LIST(APPEND llvfs_HEADER_FILES llvfs_objc.h) + LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.mm) + LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.h) + LIST(APPEND llfilesystem_SOURCE_FILES lldir_mac.cpp) + LIST(APPEND llfilesystem_HEADER_FILES lldir_mac.h) endif (DARWIN) if (LINUX) - LIST(APPEND llvfs_SOURCE_FILES lldir_linux.cpp) - LIST(APPEND llvfs_HEADER_FILES lldir_linux.h) + LIST(APPEND llfilesystem_SOURCE_FILES lldir_linux.cpp) + LIST(APPEND llfilesystem_HEADER_FILES lldir_linux.h) if (INSTALL) set_source_files_properties(lldir_linux.cpp @@ -54,31 +49,31 @@ if (LINUX) endif (LINUX) if (WINDOWS) - LIST(APPEND llvfs_SOURCE_FILES lldir_win32.cpp) - LIST(APPEND llvfs_HEADER_FILES lldir_win32.h) + LIST(APPEND llfilesystem_SOURCE_FILES lldir_win32.cpp) + LIST(APPEND llfilesystem_HEADER_FILES lldir_win32.h) endif (WINDOWS) -set_source_files_properties(${llvfs_HEADER_FILES} +set_source_files_properties(${llfilesystem_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) -list(APPEND llvfs_SOURCE_FILES ${llvfs_HEADER_FILES}) +list(APPEND llfilesystem_SOURCE_FILES ${llfilesystem_HEADER_FILES}) -add_library (llvfs ${llvfs_SOURCE_FILES}) +add_library (llfilesystem ${llfilesystem_SOURCE_FILES}) -set(vfs_BOOST_LIBRARIES +set(cache_BOOST_LIBRARIES ${BOOST_FILESYSTEM_LIBRARY} ${BOOST_SYSTEM_LIBRARY} ) -target_link_libraries(llvfs +target_link_libraries(llfilesystem ${LLCOMMON_LIBRARIES} - ${vfs_BOOST_LIBRARIES} + ${cache_BOOST_LIBRARIES} ) if (DARWIN) include(CMakeFindFrameworks) find_library(COCOA_LIBRARY Cocoa) - target_link_libraries(llvfs ${COCOA_LIBRARY}) + target_link_libraries(llfilesystem ${COCOA_LIBRARY}) endif (DARWIN) @@ -86,18 +81,18 @@ endif (DARWIN) if (LL_TESTS) include(LLAddBuildTest) # UNIT TESTS - SET(llvfs_TEST_SOURCE_FILES + SET(llfilesystem_TEST_SOURCE_FILES lldiriterator.cpp ) set_source_files_properties(lldiriterator.cpp PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES "${vfs_BOOST_LIBRARIES}" + LL_TEST_ADDITIONAL_LIBRARIES "${cache_BOOST_LIBRARIES}" ) - LL_ADD_PROJECT_UNIT_TESTS(llvfs "${llvfs_TEST_SOURCE_FILES}") + LL_ADD_PROJECT_UNIT_TESTS(llfilesystem "${llfilesystem_TEST_SOURCE_FILES}") # INTEGRATION TESTS - set(test_libs llmath llcommon llvfs ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) + set(test_libs llmath llcommon llfilesystem ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) # TODO: Some of these need refactoring to be proper Unit tests rather than Integration tests. LL_ADD_INTEGRATION_TEST(lldir "" "${test_libs}") diff --git a/indra/llvfs/lldir.cpp b/indra/llfilesystem/lldir.cpp similarity index 100% rename from indra/llvfs/lldir.cpp rename to indra/llfilesystem/lldir.cpp diff --git a/indra/llvfs/lldir.h b/indra/llfilesystem/lldir.h similarity index 100% rename from indra/llvfs/lldir.h rename to indra/llfilesystem/lldir.h diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llfilesystem/lldir_linux.cpp similarity index 100% rename from indra/llvfs/lldir_linux.cpp rename to indra/llfilesystem/lldir_linux.cpp diff --git a/indra/llvfs/lldir_linux.h b/indra/llfilesystem/lldir_linux.h similarity index 100% rename from indra/llvfs/lldir_linux.h rename to indra/llfilesystem/lldir_linux.h diff --git a/indra/llvfs/lldir_mac.cpp b/indra/llfilesystem/lldir_mac.cpp similarity index 99% rename from indra/llvfs/lldir_mac.cpp rename to indra/llfilesystem/lldir_mac.cpp index 0b1fc3b6a5..b8414ff009 100644 --- a/indra/llvfs/lldir_mac.cpp +++ b/indra/llfilesystem/lldir_mac.cpp @@ -36,7 +36,7 @@ #include #include #include -#include "llvfs_objc.h" +#include "lldir_utils_objc.h" // -------------------------------------------------------------------------------- diff --git a/indra/llvfs/lldir_mac.h b/indra/llfilesystem/lldir_mac.h similarity index 100% rename from indra/llvfs/lldir_mac.h rename to indra/llfilesystem/lldir_mac.h diff --git a/indra/llvfs/lldir_solaris.cpp b/indra/llfilesystem/lldir_solaris.cpp similarity index 100% rename from indra/llvfs/lldir_solaris.cpp rename to indra/llfilesystem/lldir_solaris.cpp diff --git a/indra/llvfs/lldir_solaris.h b/indra/llfilesystem/lldir_solaris.h similarity index 100% rename from indra/llvfs/lldir_solaris.h rename to indra/llfilesystem/lldir_solaris.h diff --git a/indra/llvfs/llvfs_objc.h b/indra/llfilesystem/lldir_utils_objc.h similarity index 85% rename from indra/llvfs/llvfs_objc.h rename to indra/llfilesystem/lldir_utils_objc.h index 56cdbebfc5..12019c4284 100644 --- a/indra/llvfs/llvfs_objc.h +++ b/indra/llfilesystem/lldir_utils_objc.h @@ -1,10 +1,10 @@ /** - * @file llvfs_objc.h + * @file lldir_utils_objc.h * @brief Definition of directory utilities class for Mac OS X * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2020, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -28,8 +28,8 @@ #error This header must not be included when compiling for any target other than Mac OS. Consider including lldir.h instead. #endif // !LL_DARWIN -#ifndef LL_LLVFS_OBJC_H -#define LL_LLVFS_OBJC_H +#ifndef LL_LLDIR_UTILS_OBJC_H +#define LL_LLDIR_UTILS_OBJC_H #include @@ -40,4 +40,4 @@ std::string* getSystemResourceFolder(); std::string* getSystemExecutableFolder(); -#endif // LL_LLVFS_OBJC_H +#endif // LL_LLDIR_UTILS_OBJC_H diff --git a/indra/llvfs/llvfs_objc.mm b/indra/llfilesystem/lldir_utils_objc.mm similarity index 95% rename from indra/llvfs/llvfs_objc.mm rename to indra/llfilesystem/lldir_utils_objc.mm index 282ea41339..da55a2f897 100644 --- a/indra/llvfs/llvfs_objc.mm +++ b/indra/llfilesystem/lldir_utils_objc.mm @@ -1,10 +1,10 @@ /** - * @file llvfs_objc.cpp + * @file lldir_utils_objc.mm * @brief Cocoa implementation of directory utilities for Mac OS X * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2020, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,7 +27,7 @@ //WARNING: This file CANNOT use standard linden includes due to conflicts between definitions of BOOL -#include "llvfs_objc.h" +#include "lldir_utils_objc.h" #import std::string* getSystemTempFolder() diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llfilesystem/lldir_win32.cpp similarity index 100% rename from indra/llvfs/lldir_win32.cpp rename to indra/llfilesystem/lldir_win32.cpp diff --git a/indra/llvfs/lldir_win32.h b/indra/llfilesystem/lldir_win32.h similarity index 100% rename from indra/llvfs/lldir_win32.h rename to indra/llfilesystem/lldir_win32.h diff --git a/indra/llvfs/lldirguard.h b/indra/llfilesystem/lldirguard.h similarity index 100% rename from indra/llvfs/lldirguard.h rename to indra/llfilesystem/lldirguard.h diff --git a/indra/llvfs/lldiriterator.cpp b/indra/llfilesystem/lldiriterator.cpp similarity index 100% rename from indra/llvfs/lldiriterator.cpp rename to indra/llfilesystem/lldiriterator.cpp diff --git a/indra/llvfs/lldiriterator.h b/indra/llfilesystem/lldiriterator.h similarity index 100% rename from indra/llvfs/lldiriterator.h rename to indra/llfilesystem/lldiriterator.h diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp new file mode 100644 index 0000000000..c9f7684b5a --- /dev/null +++ b/indra/llfilesystem/lldiskcache.cpp @@ -0,0 +1,327 @@ +/** + * @file lldiskcache.cpp + * @brief The disk cache implementation. + * + * Note: Rather than keep the top level function comments up + * to date in both the source and header files, I elected to + * only have explicit comments about each function and variable + * in the header - look there for details. The same is true for + * description of how this code is supposed to work. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, 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 "llassettype.h" +#include "lldir.h" +#include +#include +#include + +#include "lldiskcache.h" + +LLDiskCache::LLDiskCache(const std::string cache_dir, + const int max_size_bytes, + const bool enable_cache_debug_info) : + mCacheDir(cache_dir), + mMaxSizeBytes(max_size_bytes), + mEnableCacheDebugInfo(enable_cache_debug_info) +{ + mCacheFilenamePrefix = "sl_cache"; + + LLFile::mkdir(cache_dir); +} + +void LLDiskCache::purge() +{ + if (mEnableCacheDebugInfo) + { + LL_INFOS() << "Total dir size before purge is " << dirFileSize(mCacheDir) << LL_ENDL; + } + + auto start_time = std::chrono::high_resolution_clock::now(); + + typedef std::pair> file_info_t; + std::vector file_info; + +#if LL_WINDOWS + std::wstring cache_path(utf8str_to_utf16str(mCacheDir)); +#else + std::string cache_path(mCacheDir); +#endif + if (boost::filesystem::is_directory(cache_path)) + { + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) + { + if (boost::filesystem::is_regular_file(entry)) + { + if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) + { + uintmax_t file_size = boost::filesystem::file_size(entry); + const std::string file_path = entry.path().string(); + const std::time_t file_time = boost::filesystem::last_write_time(entry); + + file_info.push_back(file_info_t(file_time, { file_size, file_path })); + } + } + } + } + + std::sort(file_info.begin(), file_info.end(), [](file_info_t& x, file_info_t& y) + { + return x.first > y.first; + }); + + LL_INFOS() << "Purging cache to a maximum of " << mMaxSizeBytes << " bytes" << LL_ENDL; + + uintmax_t file_size_total = 0; + for (file_info_t& entry : file_info) + { + file_size_total += entry.second.first; + + std::string action = ""; + if (file_size_total > mMaxSizeBytes) + { + action = "DELETE:"; + boost::filesystem::remove(entry.second.second); + } + else + { + action = " KEEP:"; + } + + if (mEnableCacheDebugInfo) + { + // have to do this because of LL_INFO/LL_END weirdness + std::ostringstream line; + + line << action << " "; + line << entry.first << " "; + line << entry.second.first << " "; + line << entry.second.second; + line << " (" << file_size_total << "/" << mMaxSizeBytes << ")"; + LL_INFOS() << line.str() << LL_ENDL; + } + } + + if (mEnableCacheDebugInfo) + { + auto end_time = std::chrono::high_resolution_clock::now(); + auto execute_time = std::chrono::duration_cast(end_time - start_time).count(); + LL_INFOS() << "Total dir size after purge is " << dirFileSize(mCacheDir) << LL_ENDL; + LL_INFOS() << "Cache purge took " << execute_time << " ms to execute for " << file_info.size() << " files" << LL_ENDL; + } +} + +const std::string LLDiskCache::assetTypeToString(LLAssetType::EType at) +{ + /** + * Make use of the handy C++17 feature that allows + * for inline initialization of an std::map<> + */ + typedef std::map asset_type_to_name_t; + asset_type_to_name_t asset_type_to_name = + { + { LLAssetType::AT_TEXTURE, "TEXTURE" }, + { LLAssetType::AT_SOUND, "SOUND" }, + { LLAssetType::AT_CALLINGCARD, "CALLINGCARD" }, + { LLAssetType::AT_LANDMARK, "LANDMARK" }, + { LLAssetType::AT_SCRIPT, "SCRIPT" }, + { LLAssetType::AT_CLOTHING, "CLOTHING" }, + { LLAssetType::AT_OBJECT, "OBJECT" }, + { LLAssetType::AT_NOTECARD, "NOTECARD" }, + { LLAssetType::AT_CATEGORY, "CATEGORY" }, + { LLAssetType::AT_LSL_TEXT, "LSL_TEXT" }, + { LLAssetType::AT_LSL_BYTECODE, "LSL_BYTECODE" }, + { LLAssetType::AT_TEXTURE_TGA, "TEXTURE_TGA" }, + { LLAssetType::AT_BODYPART, "BODYPART" }, + { LLAssetType::AT_SOUND_WAV, "SOUND_WAV" }, + { LLAssetType::AT_IMAGE_TGA, "IMAGE_TGA" }, + { LLAssetType::AT_IMAGE_JPEG, "IMAGE_JPEG" }, + { LLAssetType::AT_ANIMATION, "ANIMATION" }, + { LLAssetType::AT_GESTURE, "GESTURE" }, + { LLAssetType::AT_SIMSTATE, "SIMSTATE" }, + { LLAssetType::AT_LINK, "LINK" }, + { LLAssetType::AT_LINK_FOLDER, "LINK_FOLDER" }, + { LLAssetType::AT_MARKETPLACE_FOLDER, "MARKETPLACE_FOLDER" }, + { LLAssetType::AT_WIDGET, "WIDGET" }, + { LLAssetType::AT_PERSON, "PERSON" }, + { LLAssetType::AT_MESH, "MESH" }, + { LLAssetType::AT_SETTINGS, "SETTINGS" }, + { LLAssetType::AT_UNKNOWN, "UNKNOWN" } + }; + + asset_type_to_name_t::iterator iter = asset_type_to_name.find(at); + if (iter != asset_type_to_name.end()) + { + return iter->second; + } + + return std::string("UNKNOWN"); +} + +const std::string LLDiskCache::metaDataToFilepath(const std::string id, + LLAssetType::EType at, + const std::string extra_info) +{ + std::ostringstream file_path; + + file_path << mCacheDir; + file_path << gDirUtilp->getDirDelimiter(); + file_path << mCacheFilenamePrefix; + file_path << "_"; + file_path << id; + file_path << "_"; + file_path << (extra_info.empty() ? "0" : extra_info); + //file_path << "_"; + //file_path << assetTypeToString(at); // see SL-14210 Prune descriptive tag from new cache filenames + // for details of why it was removed. Note that if you put it + // back or change the format of the filename, the cache files + // files will be invalidated (and perhaps, more importantly, + // never deleted unless you delete them manually). + file_path << ".asset"; + + return file_path.str(); +} + +void LLDiskCache::updateFileAccessTime(const std::string file_path) +{ + /** + * Threshold in time_t units that is used to decide if the last access time + * time of the file is updated or not. Added as a precaution for the concern + * outlined in SL-14582 about frequent writes on older SSDs reducing their + * lifespan. I think this is the right place for the threshold value - rather + * than it being a pref - do comment on that Jira if you disagree... + * + * Let's start with 1 hour in time_t units and see how that unfolds + */ + const std::time_t time_threshold = 1 * 60 * 60; + + // current time + const std::time_t cur_time = std::time(nullptr); + +#if LL_WINDOWS + // file last write time + const std::time_t last_write_time = boost::filesystem::last_write_time(utf8str_to_utf16str(file_path)); + + // delta between cur time and last time the file was written + const std::time_t delta_time = cur_time - last_write_time; + + // we only write the new value if the time in time_threshold has elapsed + // before the last one + if (delta_time > time_threshold) + { + boost::filesystem::last_write_time(utf8str_to_utf16str(file_path), cur_time); + } +#else + // file last write time + const std::time_t last_write_time = boost::filesystem::last_write_time(file_path); + + // delta between cur time and last time the file was written + const std::time_t delta_time = cur_time - last_write_time; + + // we only write the new value if the time in time_threshold has elapsed + // before the last one + if (delta_time > time_threshold) + { + boost::filesystem::last_write_time(file_path, cur_time); + } +#endif +} + +const std::string LLDiskCache::getCacheInfo() +{ + std::ostringstream cache_info; + + F32 max_in_mb = (F32)mMaxSizeBytes / (1024.0 * 1024.0); + F32 percent_used = ((F32)dirFileSize(mCacheDir) / (F32)mMaxSizeBytes) * 100.0; + + cache_info << std::fixed; + cache_info << std::setprecision(1); + cache_info << "Max size " << max_in_mb << " MB "; + cache_info << "(" << percent_used << "% used)"; + + return cache_info.str(); +} + +void LLDiskCache::clearCache() +{ + /** + * See notes on performance in dirFileSize(..) - there may be + * a quicker way to do this by operating on the parent dir vs + * the component files but it's called infrequently so it's + * likely just fine + */ +#if LL_WINDOWS + std::wstring cache_path(utf8str_to_utf16str(mCacheDir)); +#else + std::string cache_path(mCacheDir); +#endif + if (boost::filesystem::is_directory(cache_path)) + { + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) + { + if (boost::filesystem::is_regular_file(entry)) + { + if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) + { + boost::filesystem::remove(entry); + } + } + } + } +} + +uintmax_t LLDiskCache::dirFileSize(const std::string dir) +{ + uintmax_t total_file_size = 0; + + /** + * There may be a better way that works directly on the folder (similar to + * right clicking on a folder in the OS and asking for size vs right clicking + * on all files and adding up manually) but this is very fast - less than 100ms + * for 10,000 files in my testing so, so long as it's not called frequently, + * it should be okay. Note that's it's only currently used for logging/debugging + * so if performance is ever an issue, optimizing this or removing it altogether, + * is an easy win. + */ +#if LL_WINDOWS + std::wstring dir_path(utf8str_to_utf16str(dir)); +#else + std::string dir_path(dir); +#endif + if (boost::filesystem::is_directory(dir_path)) + { + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path), {})) + { + if (boost::filesystem::is_regular_file(entry)) + { + if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) + { + total_file_size += boost::filesystem::file_size(entry); + } + } + } + } + + return total_file_size; +} diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h new file mode 100644 index 0000000000..997884da31 --- /dev/null +++ b/indra/llfilesystem/lldiskcache.h @@ -0,0 +1,183 @@ +/** + * @file lldiskcache.h + * @brief The disk cache implementation declarations. + * + * @Description: + * This code implements a disk cache using the following ideas: + * 1/ The metadata for a file can be encapsulated in the filename. + The filenames will be composed of the following fields: + Prefix: Used to identify the file as a part of the cache. + An additional reason for using a prefix is that it + might be possible, either accidentally or maliciously + to end up with the cache dir set to a non-cache + location such as your OS system dir or a work folder. + Purging files from that would obviously be a disaster + so this is an extra step to help avoid that scenario. + ID: Typically the asset ID (UUID) of the asset being + saved but can be anything valid for a filename + Extra Info: A field for use in the future that can be used + to store extra identifiers - e.g. the discard + level of a JPEG2000 file + Asset Type: A text string created from the LLAssetType enum + that identifies the type of asset being stored. + .asset A file extension of .asset is used to help + identify this as a Viewer asset file + * 2/ The time of last access for a file can be updated instantly + * for file reads and automatically as part of the file writes. + * 3/ The purge algorithm collects a list of all files in the + * directory, sorts them by date of last access (write) and then + * deletes any files based on age until the total size of all + * the files is less than the maximum size specified. + * 4/ An LLSingleton idiom is used since there will only ever be + * a single cache and we want to access it from numerous places. + * 5/ Performance on my modest system seems very acceptable. For + * example, in testing, I was able to purge a directory of + * 10,000 files, deleting about half of them in ~ 1700ms. For + * the same sized directory of files, writing the last updated + * time to each took less than 600ms indicating that this + * important part of the mechanism has almost no overhead. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, 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 _LLDISKCACHE +#define _LLDISKCACHE + +#include "llsingleton.h" + +class LLDiskCache : + public LLParamSingleton +{ + public: + /** + * Since this is using the LLSingleton pattern but we + * want to allow the constructor to be called first + * with various parameters, we also invoke the + * LLParamSingleton idiom and use it to initialize + * the class via a call in LLAppViewer. + */ + LLSINGLETON(LLDiskCache, + /** + * The full name of the cache folder - typically a + * a child of the main Viewer cache directory. Defined + * by the setting at 'DiskCacheDirName' + */ + const std::string cache_dir, + /** + * The maximum size of the cache in bytes - Based on the + * setting at 'CacheSize' and 'DiskCachePercentOfTotal' + */ + const int max_size_bytes, + /** + * A flag that enables extra cache debugging so that + * if there are bugs, we can ask uses to enable this + * setting and send us their logs + */ + const bool enable_cache_debug_info); + + virtual ~LLDiskCache() = default; + + public: + /** + * Construct a filename and path to it based on the file meta data + * (id, asset type, additional 'extra' info like discard level perhaps) + * Worth pointing out that this function used to be in LLFileSystem but + * so many things had to be pushed back there to accomodate it, that I + * decided to move it here. Still not sure that's completely right. + */ + const std::string metaDataToFilepath(const std::string id, + LLAssetType::EType at, + const std::string extra_info); + + /** + * Update the "last write time" of a file to "now". This must be called whenever a + * file in the cache is read (not written) so that the last time the file was + * accessed is up to date (This is used in the mechanism for purging the cache) + */ + void updateFileAccessTime(const std::string file_path); + + /** + * Purge the oldest items in the cache so that the combined size of all files + * is no bigger than mMaxSizeBytes. + */ + void purge(); + + /** + * Clear the cache by removing all the files in the specified cache + * directory individually. Only the files that contain a prefix defined + * by mCacheFilenamePrefix will be removed. + */ + void clearCache(); + + /** + * Return some information about the cache for use in About Box etc. + */ + const std::string getCacheInfo(); + + private: + /** + * Utility function to gather the total size the files in a given + * directory. Primarily used here to determine the directory size + * before and after the cache purge + */ + uintmax_t dirFileSize(const std::string dir); + + /** + * Utility function to convert an LLAssetType enum into a + * string that we use as part of the cache file filename + */ + const std::string assetTypeToString(LLAssetType::EType at); + + private: + /** + * The maximum size of the cache in bytes. After purge is called, the + * total size of the cache files in the cache directory will be + * less than this value + */ + uintmax_t mMaxSizeBytes; + + /** + * The folder that holds the cached files. The consumer of this + * class must avoid letting the user set this location as a malicious + * setting could potentially point it at a non-cache directory (for example, + * the Windows System dir) with disastrous results. + */ + std::string mCacheDir; + + /** + * The prefix inserted at the start of a cache file filename to + * help identify it as a cache file. It's probably not required + * (just the presence in the cache folder is enough) but I am + * paranoid about the cache folder being set to something bad + * like the users' OS system dir by mistake or maliciously and + * this will help to offset any damage if that happens. + */ + std::string mCacheFilenamePrefix; + + /** + * When enabled, displays additional debugging information in + * various parts of the code + */ + bool mEnableCacheDebugInfo; +}; + +#endif // _LLDISKCACHE diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp new file mode 100644 index 0000000000..2e2a97826c --- /dev/null +++ b/indra/llfilesystem/llfilesystem.cpp @@ -0,0 +1,319 @@ +/** + * @file filesystem.h + * @brief Simulate local file system operations. + * @Note The initial implementation does actually use standard C++ + * file operations but eventually, there will be another + * layer that caches and manages file meta data too. + * + * $LicenseInfo:firstyear=2002&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 "lldir.h" +#include "llfilesystem.h" +#include "llfasttimer.h" +#include "lldiskcache.h" + +const S32 LLFileSystem::READ = 0x00000001; +const S32 LLFileSystem::WRITE = 0x00000002; +const S32 LLFileSystem::READ_WRITE = 0x00000003; // LLFileSystem::READ & LLFileSystem::WRITE +const S32 LLFileSystem::APPEND = 0x00000006; // 0x00000004 & LLFileSystem::WRITE + +static LLTrace::BlockTimerStatHandle FTM_VFILE_WAIT("VFile Wait"); + +LLFileSystem::LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_type, S32 mode) +{ + mFileType = file_type; + mFileID = file_id; + mPosition = 0; + mBytesRead = 0; + mMode = mode; +} + +LLFileSystem::~LLFileSystem() +{ +} + +// static +bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType file_type) +{ + std::string id_str; + file_id.toString(id_str); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); + + llifstream file(filename, std::ios::binary); + if (file.is_open()) + { + file.seekg(0, std::ios::end); + return file.tellg() > 0; + } + return false; +} + +// static +// Fix log spam +//bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type) +bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_error /*= 0*/) +// +{ + std::string id_str; + file_id.toString(id_str); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); + + // Fix log spam + //LLFile::remove(filename.c_str()); + LLFile::remove(filename.c_str(), suppress_error); + // + + return true; +} + +// static +bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, + const LLUUID& new_file_id, const LLAssetType::EType new_file_type) +{ + std::string old_id_str; + old_file_id.toString(old_id_str); + const std::string extra_info = ""; + const std::string old_filename = LLDiskCache::getInstance()->metaDataToFilepath(old_id_str, old_file_type, extra_info); + + std::string new_id_str; + new_file_id.toString(new_id_str); + const std::string new_filename = LLDiskCache::getInstance()->metaDataToFilepath(new_id_str, new_file_type, extra_info); + + // Rename needs the new file to not exist. + // Fix log spam + //LLFileSystem::removeFile(new_file_id, new_file_type); + LLFileSystem::removeFile(new_file_id, new_file_type, ENOENT); + // + + if (LLFile::rename(old_filename, new_filename) != 0) + { + // We would like to return FALSE here indicating the operation + // failed but the original code does not and doing so seems to + // break a lot of things so we go with the flow... + //return FALSE; + LL_WARNS() << "Failed to rename " << old_file_id << " to " << new_id_str << " reason: " << strerror(errno) << LL_ENDL; + } + + return TRUE; +} + +// static +S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type) +{ + std::string id_str; + file_id.toString(id_str); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, file_type, extra_info); + + S32 file_size = 0; + llifstream file(filename, std::ios::binary); + if (file.is_open()) + { + file.seekg(0, std::ios::end); + file_size = file.tellg(); + } + + return file_size; +} + +BOOL LLFileSystem::read(U8* buffer, S32 bytes) +{ + BOOL success = TRUE; + + std::string id; + mFileID.toString(id); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id, mFileType, extra_info); + + llifstream file(filename, std::ios::binary); + if (file.is_open()) + { + file.seekg(mPosition, std::ios::beg); + + file.read((char*)buffer, bytes); + + if (file) + { + mBytesRead = bytes; + } + else + { + mBytesRead = file.gcount(); + } + + file.close(); + + mPosition += mBytesRead; + if (!mBytesRead) + { + success = FALSE; + } + } + + // update the last access time for the file - this is required + // even though we are reading and not writing because this is the + // way the cache works - it relies on a valid "last accessed time" for + // each file so it knows how to remove the oldest, unused files + LLDiskCache::getInstance()->updateFileAccessTime(filename); + + return success; +} + +S32 LLFileSystem::getLastBytesRead() +{ + return mBytesRead; +} + +BOOL LLFileSystem::eof() +{ + return mPosition >= getSize(); +} + +BOOL LLFileSystem::write(const U8* buffer, S32 bytes) +{ + std::string id_str; + mFileID.toString(id_str); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id_str, mFileType, extra_info); + + BOOL success = FALSE; + + if (mMode == APPEND) + { + llofstream ofs(filename, std::ios::app | std::ios::binary); + if (ofs) + { + ofs.write((const char*)buffer, bytes); + + mPosition = ofs.tellp(); // Fix asset caching + + success = TRUE; + } + } + // Fix asset caching + else if (mMode == READ_WRITE) + { + // Don't truncate if file already exists + llofstream ofs(filename, std::ios::in | std::ios::binary); + if (ofs) + { + ofs.seekp(mPosition, std::ios::beg); + ofs.write((const char*)buffer, bytes); + mPosition += bytes; + success = TRUE; + } + else + { + // File doesn't exist - open in write mode + ofs.open(filename, std::ios::binary); + if (ofs.is_open()) + { + ofs.write((const char*)buffer, bytes); + mPosition += bytes; + success = TRUE; + } + } + } + // + else + { + llofstream ofs(filename, std::ios::binary); + if (ofs) + { + ofs.write((const char*)buffer, bytes); + + mPosition += bytes; + + success = TRUE; + } + } + + return success; +} + +BOOL LLFileSystem::seek(S32 offset, S32 origin) +{ + if (-1 == origin) + { + origin = mPosition; + } + + S32 new_pos = origin + offset; + + S32 size = getSize(); + + if (new_pos > size) + { + LL_WARNS() << "Attempt to seek past end of file" << LL_ENDL; + + mPosition = size; + return FALSE; + } + else if (new_pos < 0) + { + LL_WARNS() << "Attempt to seek past beginning of file" << LL_ENDL; + + mPosition = 0; + return FALSE; + } + + mPosition = new_pos; + return TRUE; +} + +S32 LLFileSystem::tell() const +{ + return mPosition; +} + +S32 LLFileSystem::getSize() +{ + return LLFileSystem::getFileSize(mFileID, mFileType); +} + +S32 LLFileSystem::getMaxSize() +{ + // offer up a huge size since we don't care what the max is + return INT_MAX; +} + +BOOL LLFileSystem::rename(const LLUUID& new_id, const LLAssetType::EType new_type) +{ + LLFileSystem::renameFile(mFileID, mFileType, new_id, new_type); + + mFileID = new_id; + mFileType = new_type; + + return TRUE; +} + +BOOL LLFileSystem::remove() +{ + LLFileSystem::removeFile(mFileID, mFileType); + + return TRUE; +} diff --git a/indra/llfilesystem/llfilesystem.h b/indra/llfilesystem/llfilesystem.h new file mode 100644 index 0000000000..41c39e1f63 --- /dev/null +++ b/indra/llfilesystem/llfilesystem.h @@ -0,0 +1,81 @@ +/** + * @file filesystem.h + * @brief Simulate local file system operations. + * @Note The initial implementation does actually use standard C++ + * file operations but eventually, there will be another + * layer that caches and manages file meta data too. + * + * $LicenseInfo:firstyear=2002&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_FILESYSTEM_H +#define LL_FILESYSTEM_H + +#include "lluuid.h" +#include "llassettype.h" +#include "lldiskcache.h" + +class LLFileSystem +{ + public: + LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_type, S32 mode = LLFileSystem::READ); + ~LLFileSystem(); + + BOOL read(U8* buffer, S32 bytes); + S32 getLastBytesRead(); + BOOL eof(); + + BOOL write(const U8* buffer, S32 bytes); + BOOL seek(S32 offset, S32 origin = -1); + S32 tell() const; + + S32 getSize(); + S32 getMaxSize(); + BOOL rename(const LLUUID& new_id, const LLAssetType::EType new_type); + BOOL remove(); + + static bool getExists(const LLUUID& file_id, const LLAssetType::EType file_type); + // Fix log spam + //static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type); + static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_error = 0); + // + static bool renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, + const LLUUID& new_file_id, const LLAssetType::EType new_file_type); + static S32 getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type); + + public: + static const S32 READ; + static const S32 WRITE; + static const S32 READ_WRITE; + static const S32 APPEND; + + protected: + LLAssetType::EType mFileType; + LLUUID mFileID; + S32 mPosition; + S32 mMode; + S32 mBytesRead; +//private: +// static const std::string idToFilepath(const std::string id, LLAssetType::EType at); +}; + +#endif // LL_FILESYSTEM_H diff --git a/indra/llvfs/lllfsthread.cpp b/indra/llfilesystem/lllfsthread.cpp similarity index 100% rename from indra/llvfs/lllfsthread.cpp rename to indra/llfilesystem/lllfsthread.cpp diff --git a/indra/llvfs/lllfsthread.h b/indra/llfilesystem/lllfsthread.h similarity index 100% rename from indra/llvfs/lllfsthread.h rename to indra/llfilesystem/lllfsthread.h diff --git a/indra/llvfs/tests/lldir_test.cpp b/indra/llfilesystem/tests/lldir_test.cpp similarity index 100% rename from indra/llvfs/tests/lldir_test.cpp rename to indra/llfilesystem/tests/lldir_test.cpp diff --git a/indra/llvfs/tests/lldiriterator_test.cpp b/indra/llfilesystem/tests/lldiriterator_test.cpp similarity index 100% rename from indra/llvfs/tests/lldiriterator_test.cpp rename to indra/llfilesystem/tests/lldiriterator_test.cpp diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index 293ada7548..dc8e9f7c2f 100644 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -6,7 +6,7 @@ include(00-Common) include(LLCommon) include(LLImage) include(LLMath) -include(LLVFS) +include(LLFileSystem) include(LLKDU) include(LLImageJ2COJ) include(ZLIB) @@ -17,7 +17,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCOMMON_SYSTEM_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ) @@ -68,7 +68,7 @@ else (USE_KDU) endif (USE_KDU) target_link_libraries(llimage - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${JPEG_LIBRARIES} diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 9055202121..7f87551b9e 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -2269,20 +2269,11 @@ bool LLImageFormatted::save(const std::string &filename) return true; } -// bool LLImageFormatted::save(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) -// Depricated to remove VFS dependency. -// Use: -// LLVFile::writeFile(image->getData(), image->getDataSize(), vfs, uuid, type); - -//---------------------------------------------------------------------------- - S8 LLImageFormatted::getCodec() const { return mCodec; } -//============================================================================ - static void avg4_colors4(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst) { dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2); diff --git a/indra/llinventory/CMakeLists.txt b/indra/llinventory/CMakeLists.txt index 1c18c67c97..7fae18e8ec 100644 --- a/indra/llinventory/CMakeLists.txt +++ b/indra/llinventory/CMakeLists.txt @@ -7,7 +7,7 @@ include(LLCommon) include(LLCoreHttp) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include_directories( @@ -87,7 +87,7 @@ if (LL_TESTS) LL_ADD_PROJECT_UNIT_TESTS(llinventory "${llinventory_TEST_SOURCE_FILES}") #set(TEST_DEBUG on) - set(test_libs llinventory ${LLMESSAGE_LIBRARIES} ${LLVFS_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) + set(test_libs llinventory ${LLMESSAGE_LIBRARIES} ${LLFILESYSTEM_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) LL_ADD_INTEGRATION_TEST(inventorymisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llparcel "" "${test_libs}") endif (LL_TESTS) diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index cfbd43d28f..952cd085ae 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -13,7 +13,7 @@ include(LLCommon) include(LLCoreHttp) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLAddBuildTest) include(Python) include(Tut) @@ -27,7 +27,7 @@ include_directories( ${LLCOREHTTP_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIR} ) @@ -215,7 +215,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -233,7 +233,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -263,7 +263,7 @@ if (LL_TESTS) if (LINUX) set(test_libs ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${CURL_LIBRARIES} ${NGHTTP2_LIBRARIES} @@ -279,7 +279,7 @@ if (LINUX) else (LINUX) set(test_libs ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${CURL_LIBRARIES} ${NGHTTP2_LIBRARIES} diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index d7801b6ddc..f38a5e663e 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -42,8 +42,7 @@ // this library includes #include "message.h" #include "llxfermanager.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "lldbstrings.h" #include "lltransfersourceasset.h" @@ -202,7 +201,7 @@ LLBaseDownloadRequest::LLBaseDownloadRequest(const LLUUID &uuid, const LLAssetTy mIsTemp(FALSE), mIsPriority(FALSE), mDataSentInFirstPacket(FALSE), - mDataIsInVFS(FALSE) + mDataIsInCache(FALSE) { // Need to guarantee that this time is up to date, we may be creating a circuit even though we haven't been // running a message system loop. @@ -266,7 +265,8 @@ LLSD LLAssetRequest::getFullDetails() const sd["is_local"] = mIsLocal; sd["is_priority"] = mIsPriority; sd["data_send_in_first_packet"] = mDataSentInFirstPacket; - sd["data_is_in_vfs"] = mDataIsInVFS; + // Note: cannot change this (easily) since it is consumed by server + sd["data_is_in_vfs"] = mDataIsInCache; return sd; } @@ -330,28 +330,23 @@ LLBaseDownloadRequest* LLEstateAssetRequest::getCopy() // TODO: rework tempfile handling? -LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host) +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) { - _init(msg, xfer, vfs, static_vfs, upstream_host); + _init(msg, xfer, upstream_host); } -LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs) +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) { - _init(msg, xfer, vfs, static_vfs, LLHost()); + _init(msg, xfer, LLHost()); } void LLAssetStorage::_init(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, - LLVFS *static_vfs, const LLHost &upstream_host) { mShutDown = FALSE; mMessageSys = msg; mXferManager = xfer; - mVFS = vfs; - mStaticVFS = static_vfs; setUpstream(upstream_host); msg->setHandlerFuncFast(_PREHASH_AssetUploadComplete, processUploadComplete, (void **)this); @@ -430,7 +425,7 @@ void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) } if (tmp->mDownCallback) { - tmp->mDownCallback(mVFS, tmp->getUUID(), tmp->getType(), tmp->mUserData, error, LLExtStat::NONE); + tmp->mDownCallback(tmp->getUUID(), tmp->getType(), tmp->mUserData, error, LLExtStat::NONE); } if (tmp->mInfoCallback) { @@ -443,10 +438,10 @@ void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) BOOL LLAssetStorage::hasLocalAsset(const LLUUID &uuid, const LLAssetType::EType type) { - return mStaticVFS->getExists(uuid, type) || mVFS->getExists(uuid, type); + return LLFileSystem::getExists(uuid, type); } -bool LLAssetStorage::findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, +bool LLAssetStorage::findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, void *user_data) { if (user_data) @@ -455,17 +450,17 @@ bool LLAssetStorage::findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAsse llassert(callback != NULL); } - BOOL exists = mStaticVFS->getExists(uuid, type); + BOOL exists = LLFileSystem::getExists(uuid, type); if (exists) { - LLVFile file(mStaticVFS, uuid, type); + LLFileSystem file(uuid, type); U32 size = file.getSize(); if (size > 0) { // we've already got the file if (callback) { - callback(mStaticVFS, uuid, type, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); + callback(uuid, type, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); } return true; } @@ -506,7 +501,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, if (callback) { add(sFailedDownloadCount, 1); - callback(mVFS, uuid, type, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::NONE); + callback(uuid, type, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::NONE); } return; } @@ -517,20 +512,19 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, if (callback) { add(sFailedDownloadCount, 1); - callback(mVFS, uuid, type, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); + callback(uuid, type, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); } return; } - // Try static VFS first. - if (findInStaticVFSAndInvokeCallback(uuid,type,callback,user_data)) + if (findInCacheAndInvokeCallback(uuid,type,callback,user_data)) { - LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in static VFS" << LL_ENDL; + LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in cache" << LL_ENDL; return; } - BOOL exists = mVFS->getExists(uuid, type); - LLVFile file(mVFS, uuid, type); + BOOL exists = LLFileSystem::getExists(uuid, type); + LLFileSystem file(uuid, type); U32 size = exists ? file.getSize() : 0; if (size > 0) @@ -540,10 +534,10 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, // unless there's a weird error if (callback) { - callback(mVFS, uuid, type, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); + callback(uuid, type, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); } - LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in VFS" << LL_ENDL; + LL_DEBUGS("AssetStorage") << "ASSET_TRACE asset " << uuid << " found in cache" << LL_ENDL; } else { @@ -616,7 +610,7 @@ void LLAssetStorage::removeAndCallbackPendingDownloads(const LLUUID& file_id, LL { add(sFailedDownloadCount, 1); } - tmp->mDownCallback(gAssetStorage->mVFS, callback_id, callback_type, tmp->mUserData, result_code, ext_status); + tmp->mDownCallback(callback_id, callback_type, tmp->mUserData, result_code, ext_status); } delete tmp; } @@ -670,7 +664,7 @@ void LLAssetStorage::downloadCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLVFile vfile(gAssetStorage->mVFS, callback_id, callback_type); + LLFileSystem vfile(callback_id, callback_type); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset " << callback_id << LL_ENDL; @@ -719,19 +713,19 @@ void LLAssetStorage::getEstateAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(mVFS, asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); + callback(asset_id, atype, user_data, LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE, LLExtStat::NULL_UUID); } return; } - // Try static VFS first. - if (findInStaticVFSAndInvokeCallback(asset_id,atype,callback,user_data)) + // Try static first. + if (findInCacheAndInvokeCallback(asset_id,atype,callback,user_data)) { return; } - BOOL exists = mVFS->getExists(asset_id, atype); - LLVFile file(mVFS, asset_id, atype); + BOOL exists = LLFileSystem::getExists(asset_id, atype); + LLFileSystem file(asset_id, atype); U32 size = exists ? file.getSize() : 0; if (size > 0) @@ -741,7 +735,7 @@ void LLAssetStorage::getEstateAsset( // unless there's a weird error if (callback) { - callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); + callback(asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); } } else @@ -792,7 +786,7 @@ void LLAssetStorage::getEstateAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); + callback(asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); } } } @@ -824,7 +818,7 @@ void LLAssetStorage::downloadEstateAssetCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getAType()); + LLFileSystem vfile(req->getUUID(), req->getAType()); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset!" << LL_ENDL; @@ -838,7 +832,7 @@ void LLAssetStorage::downloadEstateAssetCompleteCallback( { add(sFailedDownloadCount, 1); } - req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getAType(), req->mUserData, result, ext_status); + req->mDownCallback(req->getUUID(), req->getAType(), req->mUserData, result, ext_status); } void LLAssetStorage::getInvItemAsset( @@ -861,14 +855,13 @@ void LLAssetStorage::getInvItemAsset( if(asset_id.notNull()) { - // Try static VFS first. - if (findInStaticVFSAndInvokeCallback( asset_id, atype, callback, user_data)) + if (findInCacheAndInvokeCallback( asset_id, atype, callback, user_data)) { return; } - exists = mVFS->getExists(asset_id, atype); - LLVFile file(mVFS, asset_id, atype); + exists = LLFileSystem::getExists(asset_id, atype); + LLFileSystem file(asset_id, atype); size = exists ? file.getSize() : 0; if(exists && size < 1) { @@ -885,7 +878,7 @@ void LLAssetStorage::getInvItemAsset( // unless there's a weird error if (callback) { - callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::VFS_CACHED); + callback(asset_id, atype, user_data, LL_ERR_NOERR, LLExtStat::CACHE_CACHED); } } else @@ -936,7 +929,7 @@ void LLAssetStorage::getInvItemAsset( if (callback) { add(sFailedDownloadCount, 1); - callback(mVFS, asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); + callback(asset_id, atype, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); } } } @@ -968,7 +961,7 @@ void LLAssetStorage::downloadInvItemCompleteCallback( if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file - LLVFile vfile(gAssetStorage->mVFS, req->getUUID(), req->getType()); + LLFileSystem vfile(req->getUUID(), req->getType()); if (vfile.getSize() <= 0) { LL_WARNS("AssetStorage") << "downloadCompleteCallback has non-existent or zero-size asset!" << LL_ENDL; @@ -982,7 +975,7 @@ void LLAssetStorage::downloadInvItemCompleteCallback( { add(sFailedDownloadCount, 1); } - req->mDownCallback(gAssetStorage->mVFS, req->getUUID(), req->getType(), req->mUserData, result, ext_status); + req->mDownCallback(req->getUUID(), req->getType(), req->mUserData, result, ext_status); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1293,7 +1286,7 @@ bool LLAssetStorage::deletePendingRequestImpl(LLAssetStorage::request_list_t* re if (req->mDownCallback) { add(sFailedDownloadCount, 1); - req->mDownCallback(mVFS, req->getUUID(), req->getType(), req->mUserData, error, LLExtStat::REQUEST_DROPPED); + req->mDownCallback(req->getUUID(), req->getType(), req->mUserData, error, LLExtStat::REQUEST_DROPPED); } if (req->mInfoCallback) { @@ -1363,8 +1356,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, { LLAssetRequest* tmp = *iter++; - //void(*const* cbptr)(LLVFS *, const LLUUID &, LLAssetType::EType, void *, S32, LLExtStat) - auto cbptr = tmp->mDownCallback.target(); + auto cbptr = tmp->mDownCallback.target(); if (type == tmp->getType() && uuid == tmp->getUUID() && @@ -1389,8 +1381,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, } // static -void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, - const LLUUID &uuid, +void LLAssetStorage::legacyGetDataCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 status, @@ -1405,7 +1396,7 @@ void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, if ( !status && !toxic ) { - LLVFile file(vfs, uuid, type); + LLFileSystem file(uuid, type); std::string uuid_str; diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h index c799d8eefc..e0f22f1160 100644 --- a/indra/llmessage/llassetstorage.h +++ b/indra/llmessage/llassetstorage.h @@ -44,7 +44,6 @@ class LLMessageSystem; class LLXferManager; class LLAssetStorage; -class LLVFS; class LLSD; // anything that takes longer than this to download will abort. @@ -60,11 +59,11 @@ const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4; const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5; const int LL_ERR_PRICE_MISMATCH = -23018; -// *TODO: these typedefs are passed into the VFS via a legacy C function pointer +// *TODO: these typedefs are passed into the cache via a legacy C function pointer // future project would be to convert these to C++ callables (std::function<>) so that // we can use bind and remove the userData parameter. // -typedef std::function LLGetAssetCallback; +typedef std::function LLGetAssetCallback; typedef std::function LLStoreAssetCallback; @@ -120,7 +119,6 @@ protected: public: LLGetAssetCallback mDownCallback; -// void(*mDownCallback)(LLVFS*, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat); void *mUserData; LLHost mHost; @@ -128,7 +126,7 @@ public: F64Seconds mTime; // Message system time BOOL mIsPriority; BOOL mDataSentInFirstPacket; - BOOL mDataIsInVFS; + BOOL mDataIsInCache; }; class LLAssetRequest : public LLBaseDownloadRequest @@ -198,9 +196,6 @@ typedef std::map toxic_asset_map_t; class LLAssetStorage { public: - // VFS member is public because static child methods need it :( - LLVFS *mVFS; - LLVFS *mStaticVFS; typedef ::LLStoreAssetCallback LLStoreAssetCallback; typedef ::LLGetAssetCallback LLGetAssetCallback; @@ -230,11 +225,9 @@ protected: toxic_asset_map_t mToxicAssetMap; // Objects in this list are known to cause problems and are not loaded public: - LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host); + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); - LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs); + LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); virtual ~LLAssetStorage(); void setUpstream(const LLHost &upstream_host); @@ -284,7 +277,7 @@ public: void markAssetToxic( const LLUUID& uuid ); protected: - bool findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, + bool findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, void *user_data); LLSD getPendingDetailsImpl(const request_list_t* requests, @@ -375,7 +368,7 @@ public: bool user_waiting = false, F64Seconds timeout = LL_ASSET_STORAGE_TIMEOUT) = 0; - static void legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType, void *user_data, S32 status, LLExtStat ext_status); + static void legacyGetDataCallback(const LLUUID &uuid, LLAssetType::EType, void *user_data, S32 status, LLExtStat ext_status); static void legacyStoreDataCallback(const LLUUID &uuid, void *user_data, S32 status, LLExtStat ext_status); // add extra methods to handle metadata @@ -385,15 +378,12 @@ protected: void _callUploadCallbacks(const LLUUID &uuid, const LLAssetType::EType asset_type, BOOL success, LLExtStat ext_status); virtual void _queueDataRequest(const LLUUID& uuid, LLAssetType::EType type, LLGetAssetCallback callback, -// void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), void *user_data, BOOL duplicate, BOOL is_priority) = 0; private: void _init(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, - LLVFS *static_vfs, const LLHost &upstream_host); protected: @@ -408,7 +398,7 @@ protected: MR_FILE_NONEXIST = 3, // Old format store call - source file does not exist MR_NO_FILENAME = 4, // Old format store call - source filename is NULL/0-length MR_NO_UPSTREAM = 5, // Upstream provider is missing - MR_VFS_CORRUPTION = 6 // VFS is corrupt - too-large or mismatched stated/returned sizes + MR_CACHE_CORRUPTION = 6 // cache is corrupt - too-large or mismatched stated/returned sizes }; static class LLMetrics *metric_recipient; diff --git a/indra/llmessage/llcorehttputil.cpp b/indra/llmessage/llcorehttputil.cpp index 116a798935..f823f1b845 100644 --- a/indra/llmessage/llcorehttputil.cpp +++ b/indra/llmessage/llcorehttputil.cpp @@ -37,7 +37,7 @@ #include "llsdserialize.h" #include "json/reader.h" // JSON #include "json/writer.h" // JSON -#include "llvfile.h" +#include "llfilesystem.h" #include "message.h" // for getting the port @@ -784,7 +784,7 @@ LLSD HttpCoroutineAdapter::postFileAndSuspend(LLCore::HttpRequest::ptr_t request // scoping for our streams so that they go away when we no longer need them. { LLCore::BufferArrayStream outs(fileData.get()); - LLVFile vfile(gVFS, assetId, assetType, LLVFile::READ); + LLFileSystem vfile(assetId, assetType, LLFileSystem::READ); S32 fileSize = vfile.getSize(); U8* fileBuffer; diff --git a/indra/llmessage/llextendedstatus.h b/indra/llmessage/llextendedstatus.h index 9923d73c1a..2a53dced80 100644 --- a/indra/llmessage/llextendedstatus.h +++ b/indra/llmessage/llextendedstatus.h @@ -1,7 +1,7 @@ /** * @file llextendedstatus.h * @date August 2007 - * @brief extended status codes for curl/vfs/resident asset storage and delivery + * @brief extended status codes for curl/resident asset storage and delivery * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code @@ -32,9 +32,9 @@ enum class LLExtStat: uint32_t { // Status provider groups - Top bits indicate which status type it is // Zero is common status code (next section) - CURL_RESULT = 1UL<<30, // serviced by curl - use 1L if we really implement the below - RES_RESULT = 2UL<<30, // serviced by resident copy - VFS_RESULT = 3UL<<30, // serviced by vfs + CURL_RESULT = 1UL<<30, // serviced by curl - use 1L if we really implement the below + RES_RESULT = 2UL<<30, // serviced by resident copy + CACHE_RESULT = 3UL<<30, // serviced by cache // Common Status Codes @@ -54,9 +54,9 @@ enum class LLExtStat: uint32_t // Memory-Resident status codes: // None at present - // VFS status codes: - VFS_CACHED = VFS_RESULT | 0x0001, - VFS_CORRUPT = VFS_RESULT | 0x0002, + // CACHE status codes: + CACHE_CACHED = CACHE_RESULT | 0x0001, + CACHE_CORRUPT = CACHE_RESULT | 0x0002, }; diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index 80ed3340c6..027283232d 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -32,7 +32,7 @@ #include "message.h" #include "lldatapacker.h" #include "lldir.h" -#include "llvfile.h" +#include "llfilesystem.h" LLTransferSourceAsset::LLTransferSourceAsset(const LLUUID &request_id, const F32 priority) : LLTransferSource(LLTST_ASSET, request_id, priority), @@ -99,7 +99,7 @@ LLTSCode LLTransferSourceAsset::dataCallback(const S32 packet_id, return LLTS_SKIP; } - LLVFile vf(gAssetStorage->mVFS, mParams.getAssetID(), mParams.getAssetType(), LLVFile::READ); + LLFileSystem vf(mParams.getAssetID(), mParams.getAssetType(), LLFileSystem::READ); if (!vf.getSize()) { @@ -171,7 +171,7 @@ BOOL LLTransferSourceAsset::unpackParams(LLDataPacker &dp) } -void LLTransferSourceAsset::responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, +void LLTransferSourceAsset::responderCallback(const LLUUID& uuid, LLAssetType::EType type, void *user_data, S32 result, LLExtStat ext_status ) { LLUUID *tidp = ((LLUUID*) user_data); @@ -198,7 +198,7 @@ void LLTransferSourceAsset::responderCallback(LLVFS *vfs, const LLUUID& uuid, LL if (LL_ERR_NOERR == result) { // Everything's OK. - LLVFile vf(gAssetStorage->mVFS, uuid, type, LLVFile::READ); + LLFileSystem vf(uuid, type, LLFileSystem::READ); tsap->mSize = vf.getSize(); status = LLTS_OK; } diff --git a/indra/llmessage/lltransfersourceasset.h b/indra/llmessage/lltransfersourceasset.h index 3abda83cf8..585e683cb3 100644 --- a/indra/llmessage/lltransfersourceasset.h +++ b/indra/llmessage/lltransfersourceasset.h @@ -30,7 +30,7 @@ #include "lltransfermanager.h" #include "llassetstorage.h" -class LLVFile; +class LLFileSystem; class LLTransferSourceParamsAsset : public LLTransferSourceParams { @@ -56,7 +56,7 @@ public: LLTransferSourceAsset(const LLUUID &request_id, const F32 priority); virtual ~LLTransferSourceAsset(); - static void responderCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, + static void responderCallback(const LLUUID& uuid, LLAssetType::EType type, void *user_data, S32 result, LLExtStat ext_status ); protected: /*virtual*/ void initTransfer(); diff --git a/indra/llmessage/lltransfertargetvfile.cpp b/indra/llmessage/lltransfertargetvfile.cpp index b27f0881e0..f6faadf87f 100644 --- a/indra/llmessage/lltransfertargetvfile.cpp +++ b/indra/llmessage/lltransfertargetvfile.cpp @@ -30,7 +30,7 @@ #include "lldatapacker.h" #include "llerror.h" -#include "llvfile.h" +#include "llfilesystem.h" //static void LLTransferTargetVFile::updateQueue(bool shutdown) @@ -138,10 +138,9 @@ LLTSCode LLTransferTargetVFile::dataCallback(const S32 packet_id, U8 *in_datap, //LL_INFOS() << "LLTransferTargetFile::dataCallback" << LL_ENDL; //LL_INFOS() << "Packet: " << packet_id << LL_ENDL; - LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); + LLFileSystem vf(mTempID, mParams.getAssetType(), LLFileSystem::APPEND); if (mNeedsCreate) { - vf.setMaxSize(mSize); mNeedsCreate = FALSE; } @@ -176,7 +175,7 @@ void LLTransferTargetVFile::completionCallback(const LLTSCode status) case LLTS_DONE: if (!mNeedsCreate) { - LLVFile file(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::WRITE); + LLFileSystem file(mTempID, mParams.getAssetType(), LLFileSystem::WRITE); if (!file.rename(mParams.getAssetID(), mParams.getAssetType())) { LL_ERRS() << "LLTransferTargetVFile: rename failed" << LL_ENDL; @@ -195,7 +194,7 @@ void LLTransferTargetVFile::completionCallback(const LLTSCode status) { // We're aborting this transfer, we don't want to keep this file. LL_WARNS() << "Aborting vfile transfer for " << mParams.getAssetID() << LL_ENDL; - LLVFile vf(gAssetStorage->mVFS, mTempID, mParams.getAssetType(), LLVFile::APPEND); + LLFileSystem vf(mTempID, mParams.getAssetType(), LLFileSystem::APPEND); vf.remove(); } break; diff --git a/indra/llmessage/lltransfertargetvfile.h b/indra/llmessage/lltransfertargetvfile.h index c819c1e2f2..39a9125f1b 100644 --- a/indra/llmessage/lltransfertargetvfile.h +++ b/indra/llmessage/lltransfertargetvfile.h @@ -29,9 +29,9 @@ #include "lltransfermanager.h" #include "llassetstorage.h" -#include "llvfile.h" +#include "llfilesystem.h" -class LLVFile; +class LLFileSystem; // Lame, an S32 for now until I figure out the deal with how we want to do // error codes. diff --git a/indra/llmessage/llxfer_vfile.cpp b/indra/llmessage/llxfer_vfile.cpp index ddc24342f6..12419b342d 100644 --- a/indra/llmessage/llxfer_vfile.cpp +++ b/indra/llmessage/llxfer_vfile.cpp @@ -30,8 +30,7 @@ #include "lluuid.h" #include "llerror.h" #include "llmath.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "lldir.h" // size of chunks read from/written to disk @@ -42,13 +41,13 @@ const U32 LL_MAX_XFER_FILE_BUFFER = 65536; LLXfer_VFile::LLXfer_VFile () : LLXfer(-1) { - init(NULL, LLUUID::null, LLAssetType::AT_NONE); + init(LLUUID::null, LLAssetType::AT_NONE); } -LLXfer_VFile::LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) +LLXfer_VFile::LLXfer_VFile (const LLUUID &local_id, LLAssetType::EType type) : LLXfer(-1) { - init(vfs, local_id, type); + init(local_id, type); } /////////////////////////////////////////////////////////// @@ -60,10 +59,8 @@ LLXfer_VFile::~LLXfer_VFile () /////////////////////////////////////////////////////////// -void LLXfer_VFile::init (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type) +void LLXfer_VFile::init (const LLUUID &local_id, LLAssetType::EType type) { - - mVFS = vfs; mLocalID = local_id; mType = type; @@ -82,14 +79,14 @@ void LLXfer_VFile::cleanup () if (mTempID.notNull() && mDeleteTempFile) { - if (mVFS->getExists(mTempID, mType)) + if (LLFileSystem::getExists(mTempID, mType)) { - LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); + LLFileSystem file(mTempID, mType, LLFileSystem::WRITE); file.remove(); } else { - LL_WARNS("Xfer") << "LLXfer_VFile::cleanup() can't open to delete VFS file " << mTempID << "." << LLAssetType::lookup(mType) + LL_WARNS("Xfer") << "LLXfer_VFile::cleanup() can't open to delete cache file " << mTempID << "." << LLAssetType::lookup(mType) << ", mRemoteID is " << mRemoteID << LL_ENDL; } } @@ -103,7 +100,6 @@ void LLXfer_VFile::cleanup () /////////////////////////////////////////////////////////// S32 LLXfer_VFile::initializeRequest(U64 xfer_id, - LLVFS* vfs, const LLUUID& local_id, const LLUUID& remote_id, LLAssetType::EType type, @@ -115,7 +111,6 @@ S32 LLXfer_VFile::initializeRequest(U64 xfer_id, mRemoteHost = remote_host; - mVFS = vfs; mLocalID = local_id; mRemoteID = remote_id; mType = type; @@ -192,13 +187,13 @@ S32 LLXfer_VFile::startSend (U64 xfer_id, const LLHost &remote_host) delete mVFile; mVFile = NULL; - if(mVFS->getExists(mLocalID, mType)) + if(LLFileSystem::getExists(mLocalID, mType)) { - mVFile = new LLVFile(mVFS, mLocalID, mType, LLVFile::READ); + mVFile = new LLFileSystem(mLocalID, mType, LLFileSystem::READ); if (mVFile->getSize() <= 0) { - LL_WARNS("Xfer") << "LLXfer_VFile::startSend() VFS file " << mLocalID << "." << LLAssetType::lookup(mType) + LL_WARNS("Xfer") << "LLXfer_VFile::startSend() cache file " << mLocalID << "." << LLAssetType::lookup(mType) << " has unexpected file size of " << mVFile->getSize() << LL_ENDL; delete mVFile; mVFile = NULL; @@ -214,7 +209,7 @@ S32 LLXfer_VFile::startSend (U64 xfer_id, const LLHost &remote_host) } else { - LL_WARNS("Xfer") << "LLXfer_VFile::startSend() can't read VFS file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::startSend() can't read cache file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; retval = LL_ERR_FILE_NOT_FOUND; } @@ -240,13 +235,13 @@ S32 LLXfer_VFile::reopenFileHandle() if (mVFile == NULL) { - if (mVFS->getExists(mLocalID, mType)) + if (LLFileSystem::getExists(mLocalID, mType)) { - mVFile = new LLVFile(mVFS, mLocalID, mType, LLVFile::READ); + mVFile = new LLFileSystem(mLocalID, mType, LLFileSystem::READ); } else { - LL_WARNS("Xfer") << "LLXfer_VFile::reopenFileHandle() can't read VFS file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::reopenFileHandle() can't read cache file " << mLocalID << "." << LLAssetType::lookup(mType) << LL_ENDL; retval = LL_ERR_FILE_NOT_FOUND; } } @@ -265,8 +260,7 @@ void LLXfer_VFile::setXferSize (S32 xfer_size) // It would be nice if LLXFers could tell which end of the pipe they were if (! mVFile) { - LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); - file.setMaxSize(xfer_size); + LLFileSystem file(mTempID, mType, LLFileSystem::APPEND); } } @@ -320,7 +314,7 @@ S32 LLXfer_VFile::flush() S32 retval = 0; if (mBufferLength) { - LLVFile file(mVFS, mTempID, mType, LLVFile::APPEND); + LLFileSystem file(mTempID, mType, LLFileSystem::APPEND); file.write((U8*)mBuffer, mBufferLength); @@ -340,22 +334,15 @@ S32 LLXfer_VFile::processEOF() if (!mCallbackResult) { - if (mVFS->getExists(mTempID, mType)) + if (LLFileSystem::getExists(mTempID, mType)) { - LLVFile file(mVFS, mTempID, mType, LLVFile::WRITE); + LLFileSystem file(mTempID, mType, LLFileSystem::WRITE); if (!file.rename(mLocalID, mType)) { - LL_WARNS("Xfer") << "VFS rename of temp file failed: unable to rename " << mTempID << " to " << mLocalID << LL_ENDL; + LL_WARNS("Xfer") << "Cache rename of temp file failed: unable to rename " << mTempID << " to " << mLocalID << LL_ENDL; } else - { - #ifdef VFS_SPAM - // Debugging spam - LL_INFOS("Xfer") << "VFS rename of temp file done: renamed " << mTempID << " to " << mLocalID - << " LLVFile size is " << file.getSize() - << LL_ENDL; - #endif - + { // Rename worked: the original file is gone. Clear mDeleteTempFile // so we don't attempt to delete the file in cleanup() mDeleteTempFile = FALSE; @@ -363,7 +350,7 @@ S32 LLXfer_VFile::processEOF() } else { - LL_WARNS("Xfer") << "LLXfer_VFile::processEOF() can't open for renaming VFS file " << mTempID << "." << LLAssetType::lookup(mType) << LL_ENDL; + LL_WARNS("Xfer") << "LLXfer_VFile::processEOF() can't open for renaming cache file " << mTempID << "." << LLAssetType::lookup(mType) << LL_ENDL; } } diff --git a/indra/llmessage/llxfer_vfile.h b/indra/llmessage/llxfer_vfile.h index 5bf9a5cfba..d82bab5f6c 100644 --- a/indra/llmessage/llxfer_vfile.h +++ b/indra/llmessage/llxfer_vfile.h @@ -30,8 +30,7 @@ #include "llxfer.h" #include "llassetstorage.h" -class LLVFS; -class LLVFile; +class LLFileSystem; class LLXfer_VFile : public LLXfer { @@ -41,9 +40,7 @@ class LLXfer_VFile : public LLXfer LLUUID mTempID; LLAssetType::EType mType; - LLVFile *mVFile; - - LLVFS *mVFS; + LLFileSystem *mVFile; std::string mName; @@ -51,14 +48,13 @@ class LLXfer_VFile : public LLXfer public: LLXfer_VFile (); - LLXfer_VFile (LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); + LLXfer_VFile (const LLUUID &local_id, LLAssetType::EType type); virtual ~LLXfer_VFile(); - virtual void init(LLVFS *vfs, const LLUUID &local_id, LLAssetType::EType type); + virtual void init(const LLUUID &local_id, LLAssetType::EType type); virtual void cleanup(); virtual S32 initializeRequest(U64 xfer_id, - LLVFS *vfs, const LLUUID &local_id, const LLUUID &remote_id, const LLAssetType::EType type, diff --git a/indra/llmessage/llxfermanager.cpp b/indra/llmessage/llxfermanager.cpp index 4cea886c8a..f9b59d7e42 100644 --- a/indra/llmessage/llxfermanager.cpp +++ b/indra/llmessage/llxfermanager.cpp @@ -56,9 +56,9 @@ const S32 LL_DEFAULT_MAX_HARD_LIMIT_SIMULTANEOUS_XFERS = 500; /////////////////////////////////////////////////////////// -LLXferManager::LLXferManager (LLVFS *vfs) +LLXferManager::LLXferManager () { - init(vfs); + init(); } /////////////////////////////////////////////////////////// @@ -70,7 +70,7 @@ LLXferManager::~LLXferManager () /////////////////////////////////////////////////////////// -void LLXferManager::init (LLVFS *vfs) +void LLXferManager::init() { cleanup(); @@ -78,8 +78,6 @@ void LLXferManager::init (LLVFS *vfs) setHardLimitOutgoingXfersPerCircuit(LL_DEFAULT_MAX_HARD_LIMIT_SIMULTANEOUS_XFERS); setMaxIncomingXfers(LL_DEFAULT_MAX_REQUEST_FIFO_XFERS); - mVFS = vfs; - // Turn on or off ack throttling mUseAckThrottling = FALSE; setAckThrottleBPS(100000); @@ -462,7 +460,7 @@ U64 LLXferManager::requestFile(const std::string& local_filename, void LLXferManager::requestVFile(const LLUUID& local_id, const LLUUID& remote_id, - LLAssetType::EType type, LLVFS* vfs, + LLAssetType::EType type, const LLHost& remote_host, void (*callback)(void**,S32,LLExtStat), void** user_data, @@ -508,7 +506,6 @@ void LLXferManager::requestVFile(const LLUUID& local_id, addToList(xfer_p, mReceiveList, is_priority); ((LLXfer_VFile *)xfer_p)->initializeRequest(getNextID(), - vfs, local_id, remote_id, type, @@ -784,33 +781,17 @@ void LLXferManager::processFileRequest (LLMessageSystem *mesgsys, void ** /*user LLXfer *xferp; if (uuid != LLUUID::null) - { // Request for an asset - use a VFS file + { // Request for an asset - use a cache file if(NULL == LLAssetType::lookup(type)) { LL_WARNS("Xfer") << "Invalid type for xfer request: " << uuid << ":" << type_s16 << " to " << mesgsys->getSender() << LL_ENDL; return; } - - if (! mVFS) - { - LL_WARNS("Xfer") << "Attempt to send VFile w/o available VFS" << LL_ENDL; - return; - } - - /* Present in fireengine, not used by viewer - if (!validateVFileForTransfer(uuid.asString())) - { - // it is up to the app sending the file to mark it for expected - // transfer before the request arrives or it will be dropped - LL_WARNS("Xfer") << "SECURITY: Unapproved VFile '" << uuid << "'" << LL_ENDL; - return; - } - */ LL_INFOS("Xfer") << "starting vfile transfer: " << uuid << "," << LLAssetType::lookup(type) << " to " << mesgsys->getSender() << LL_ENDL; - xferp = (LLXfer *)new LLXfer_VFile(mVFS, uuid, type); + xferp = (LLXfer *)new LLXfer_VFile(uuid, type); if (xferp) { mSendList.push_front(xferp); @@ -1273,9 +1254,9 @@ void LLXferManager::addToList(LLXfer* xferp, xfer_list_t & xfer_list, BOOL is_pr LLXferManager *gXferManager = NULL; -void start_xfer_manager(LLVFS *vfs) +void start_xfer_manager() { - gXferManager = new LLXferManager(vfs); + gXferManager = new LLXferManager(); } void cleanup_xfer_manager() diff --git a/indra/llmessage/llxfermanager.h b/indra/llmessage/llxfermanager.h index 45ae2ffdd3..f49209bed0 100644 --- a/indra/llmessage/llxfermanager.h +++ b/indra/llmessage/llxfermanager.h @@ -35,7 +35,6 @@ //Forward declaration to avoid circular dependencies class LLXfer; -class LLVFS; #include "llxfer.h" #include "message.h" @@ -72,9 +71,6 @@ public: class LLXferManager { - private: - LLVFS *mVFS; - protected: S32 mMaxOutgoingXfersPerCircuit; S32 mHardLimitOutgoingXfersPerCircuit; // At this limit, kill off the connection @@ -111,10 +107,10 @@ class LLXferManager std::multiset mExpectedVFileRequests; // files that are authorized to be downloaded on top of public: - LLXferManager(LLVFS *vfs); + LLXferManager(); virtual ~LLXferManager(); - virtual void init(LLVFS *vfs); + virtual void init(); virtual void cleanup(); void setUseAckThrottling(const BOOL use); @@ -166,7 +162,7 @@ class LLXferManager // vfile requesting // .. to vfile virtual void requestVFile(const LLUUID &local_id, const LLUUID& remote_id, - LLAssetType::EType type, LLVFS* vfs, + LLAssetType::EType type, const LLHost& remote_host, void (*callback)(void**,S32,LLExtStat), void** user_data, BOOL is_priority = FALSE); @@ -213,7 +209,7 @@ class LLXferManager extern LLXferManager* gXferManager; // initialization and garbage collection -void start_xfer_manager(LLVFS *vfs); +void start_xfer_manager(); void cleanup_xfer_manager(); // message system callbacks diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index 47e7ad915b..baab09a104 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -9,10 +9,9 @@ include(LLCommon) include(LLImage) include(LLMath) include(LLRender) -include(LLVFS) include(LLWindow) include(LLXML) -include(LLVFS) +include(LLFileSystem) include_directories( ${FREETYPE_INCLUDE_DIRS} @@ -20,10 +19,9 @@ include_directories( ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} ) include_directories(SYSTEM ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -104,9 +102,8 @@ if (BUILD_HEADLESS) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_HEADLESS_LIBRARIES} - ${LLVFS_LIBRARIES} ${LLXML_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLWINDOW_HEADLESS_LIBRARIES} ${OPENGL_HEADLESS_LIBRARIES}) @@ -126,9 +123,8 @@ target_link_libraries(llrender ${LLCOMMON_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLXML_LIBRARIES} - ${LLVFS_LIBRARIES} ${LLWINDOW_LIBRARIES} ${FREETYPE_LIBRARIES} ${OPENGL_LIBRARIES}) diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 58444926c9..3ff8f19b87 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -13,7 +13,7 @@ include(LLCoreHttp) include(LLRender) include(LLWindow) include(LLCoreHttp) -include(LLVFS) +include(LLFileSystem) include(LLXML) include_directories( @@ -25,7 +25,7 @@ include_directories( ${LLMESSAGE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LIBS_PREBUILD_DIR}/include/hunspell ) @@ -287,7 +287,7 @@ target_link_libraries(llui ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLCOREHTTP_LIBRARIES} - ${LLVFS_LIBRARIES} # ugh, just for LLDir + ${LLFILESYSTEM_LIBRARIES} ${LLXUIXML_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} diff --git a/indra/llui/llviewereventrecorder.h b/indra/llui/llviewereventrecorder.h index d1059d55de..94e66f5dc4 100644 --- a/indra/llui/llviewereventrecorder.h +++ b/indra/llui/llviewereventrecorder.h @@ -32,7 +32,6 @@ #include "lldir.h" #include "llsd.h" #include "llfile.h" -#include "llvfile.h" #include "lldate.h" #include "llsdserialize.h" #include "llkeyboard.h" diff --git a/indra/llvfs/llpidlock.cpp b/indra/llvfs/llpidlock.cpp deleted file mode 100644 index f770e93d45..0000000000 --- a/indra/llvfs/llpidlock.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/** - * @file llformat.cpp - * @date January 2007 - * @brief string formatting utility - * - * $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$ - */ - -#include "linden_common.h" - -#include "llapr.h" // thread-related functions -#include "llpidlock.h" -#include "lldir.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llnametable.h" -#include "llframetimer.h" - -#if LL_WINDOWS //For windows platform. - -#include - -bool isProcessAlive(U32 pid) -{ - return (bool) GetProcessVersion((DWORD)pid); -} - -#else //Everyone Else -bool isProcessAlive(U32 pid) -{ - return (bool) kill( (pid_t)pid, 0); -} -#endif //Everyone else. - - - -class LLPidLockFile -{ - public: - LLPidLockFile( ) : - mAutosave(false), - mSaving(false), - mWaiting(false), - mPID(getpid()), - mNameTable(NULL), - mClean(true) - { - mLockName = gDirUtilp->getTempDir() + gDirUtilp->getDirDelimiter() + "savelock"; - } - bool requestLock(LLNameTable *name_table, bool autosave, - bool force_immediate=FALSE, F32 timeout=300.0); - bool checkLock(); - void releaseLock(); - - private: - void writeLockFile(LLSD pids); - public: - static LLPidLockFile& instance(); // return the singleton black list file - - bool mAutosave; - bool mSaving; - bool mWaiting; - LLFrameTimer mTimer; - U32 mPID; - std::string mLockName; - std::string mSaveName; - LLSD mPIDS_sd; - LLNameTable *mNameTable; - bool mClean; -}; - -LLPidLockFile& LLPidLockFile::instance() -{ - static LLPidLockFile the_file; - return the_file; -} - -void LLPidLockFile::writeLockFile(LLSD pids) -{ - llofstream ofile(mLockName.c_str()); - - if (!LLSDSerialize::toXML(pids,ofile)) - { - LL_WARNS() << "Unable to write concurrent save lock file." << LL_ENDL; - } - ofile.close(); -} - -bool LLPidLockFile::requestLock(LLNameTable *name_table, bool autosave, - bool force_immediate, F32 timeout) -{ - bool readyToSave = FALSE; - - if (mSaving) return FALSE; //Bail out if we're currently saving. Will not queue another save. - - if (!mWaiting){ - mNameTable=name_table; - mAutosave = autosave; - } - - LLSD out_pids; - out_pids.append( (LLSD::Integer)mPID ); - - llifstream ifile(mLockName.c_str()); - - if (ifile.is_open()) - { //If file exists, we need to decide whether or not to continue. - if ( force_immediate - || mTimer.hasExpired() ) //Only deserialize if we REALLY need to. - { - - LLSD in_pids; - - LLSDSerialize::fromXML(in_pids, ifile); - - //Clean up any dead PIDS that might be in there. - for (LLSD::array_iterator i=in_pids.beginArray(); - i !=in_pids.endArray(); - ++i) - { - U32 stored_pid=(*i).asInteger(); - - if (isProcessAlive(stored_pid)) - { - out_pids.append( (*i) ); - } - } - - readyToSave=TRUE; - } - ifile.close(); - } - else - { - readyToSave=TRUE; - } - - if (!mWaiting) //Not presently waiting to save. Queue up. - { - mTimer.resetWithExpiry(timeout); - mWaiting=TRUE; - } - - if (readyToSave) - { //Potential race condition won't kill us. Ignore it. - writeLockFile(out_pids); - mSaving=TRUE; - } - - return readyToSave; -} - -bool LLPidLockFile::checkLock() -{ - return mWaiting; -} - -void LLPidLockFile::releaseLock() -{ - llifstream ifile(mLockName.c_str()); - LLSD in_pids; - LLSD out_pids; - bool write_file=FALSE; - - LLSDSerialize::fromXML(in_pids, ifile); - - //Clean up this PID and any dead ones. - for (LLSD::array_iterator i=in_pids.beginArray(); - i !=in_pids.endArray(); - ++i) - { - U32 stored_pid=(*i).asInteger(); - - if (stored_pid != mPID && isProcessAlive(stored_pid)) - { - out_pids.append( (*i) ); - write_file=TRUE; - } - } - ifile.close(); - - if (write_file) - { - writeLockFile(out_pids); - } - else - { - unlink(mLockName.c_str()); - } - - mSaving=FALSE; - mWaiting=FALSE; -} - -//LLPidLock - -void LLPidLock::initClass() { - (void) LLPidLockFile::instance(); -} - -bool LLPidLock::checkLock() -{ - return LLPidLockFile::instance().checkLock(); -} - -bool LLPidLock::requestLock(LLNameTable *name_table, bool autosave, - bool force_immediate, F32 timeout) -{ - return LLPidLockFile::instance().requestLock(name_table,autosave,force_immediate,timeout); -} - -void LLPidLock::releaseLock() -{ - return LLPidLockFile::instance().releaseLock(); -} - -bool LLPidLock::isClean() -{ - return LLPidLockFile::instance().mClean; -} - -//getters -LLNameTable * LLPidLock::getNameTable() -{ - return LLPidLockFile::instance().mNameTable; -} - -bool LLPidLock::getAutosave() -{ - return LLPidLockFile::instance().mAutosave; -} - -bool LLPidLock::getClean() -{ - return LLPidLockFile::instance().mClean; -} - -std::string LLPidLock::getSaveName() -{ - return LLPidLockFile::instance().mSaveName; -} - -//setters -void LLPidLock::setClean(bool clean) -{ - LLPidLockFile::instance().mClean=clean; -} - -void LLPidLock::setSaveName(std::string savename) -{ - LLPidLockFile::instance().mSaveName=savename; -} - -S32 LLPidLock::getPID() -{ - return (S32)getpid(); -} diff --git a/indra/llvfs/llpidlock.h b/indra/llvfs/llpidlock.h deleted file mode 100644 index 334f26bb29..0000000000 --- a/indra/llvfs/llpidlock.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file llpidlock.h - * @brief System information debugging classes. - * - * $LicenseInfo:firstyear=2001&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_PIDLOCK_H -#define LL_PIDLOCK_H -#include "llnametable.h" - -class LLSD; -class LLFrameTimer; - -#if !LL_WINDOWS //For non-windows platforms. -#include -#endif - -namespace LLPidLock -{ - void initClass(); // { (void) LLPidLockFile::instance(); } - - bool requestLock( LLNameTable *name_table=NULL, bool autosave=TRUE, - bool force_immediate=FALSE, F32 timeout=300.0); - bool checkLock(); - void releaseLock(); - bool isClean(); - - //getters - LLNameTable * getNameTable(); - bool getAutosave(); - bool getClean(); - std::string getSaveName(); - S32 getPID(); - - //setters - void setClean(bool clean); - void setSaveName(std::string savename); -}; - -#endif // LL_PIDLOCK_H diff --git a/indra/llvfs/llvfile.cpp b/indra/llvfs/llvfile.cpp deleted file mode 100644 index b8588e99f4..0000000000 --- a/indra/llvfs/llvfile.cpp +++ /dev/null @@ -1,437 +0,0 @@ -/** - * @file llvfile.cpp - * @brief Implementation of virtual file - * - * $LicenseInfo:firstyear=2002&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 "llvfile.h" - -#include "llerror.h" -#include "llthread.h" -#include "lltimer.h" -#include "llfasttimer.h" -#include "llmemory.h" -#include "llvfs.h" - -const S32 LLVFile::READ = 0x00000001; -const S32 LLVFile::WRITE = 0x00000002; -const S32 LLVFile::READ_WRITE = 0x00000003; // LLVFile::READ & LLVFile::WRITE -const S32 LLVFile::APPEND = 0x00000006; // 0x00000004 & LLVFile::WRITE - -static LLTrace::BlockTimerStatHandle FTM_VFILE_WAIT("VFile Wait"); - -//---------------------------------------------------------------------------- -LLVFSThread* LLVFile::sVFSThread = NULL; -BOOL LLVFile::sAllocdVFSThread = FALSE; -//---------------------------------------------------------------------------- - -//============================================================================ - -LLVFile::LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode) -{ - mFileType = file_type; - - mFileID = file_id; - mPosition = 0; - mMode = mode; - mVFS = vfs; - - mBytesRead = 0; - mHandle = LLVFSThread::nullHandle(); - mPriority = 128.f; - - mVFS->incLock(mFileID, mFileType, VFSLOCK_OPEN); -} - -LLVFile::~LLVFile() -{ - if (!isReadComplete()) - { - if (mHandle != LLVFSThread::nullHandle()) - { - if (!(mMode & LLVFile::WRITE)) - { - //LL_WARNS() << "Destroying LLVFile with pending async read/write, aborting..." << LL_ENDL; - sVFSThread->setFlags(mHandle, LLVFSThread::FLAG_AUTO_COMPLETE | LLVFSThread::FLAG_ABORT); - } - else // WRITE - { - sVFSThread->setFlags(mHandle, LLVFSThread::FLAG_AUTO_COMPLETE); - } - } - } - mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); -} - -BOOL LLVFile::read(U8 *buffer, S32 bytes, BOOL async, F32 priority) -{ - if (! (mMode & READ)) - { - LL_WARNS() << "Attempt to read from file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; - return FALSE; - } - - if (mHandle != LLVFSThread::nullHandle()) - { - LL_WARNS() << "Attempt to read from vfile object " << mFileID << " with pending async operation" << LL_ENDL; - return FALSE; - } - mPriority = priority; - - BOOL success = TRUE; - - // We can't do a read while there are pending async writes - waitForLock(VFSLOCK_APPEND); - - // *FIX: (?) - if (async) - { - mHandle = sVFSThread->read(mVFS, mFileID, mFileType, buffer, mPosition, bytes, threadPri()); - } - else - { - // We can't do a read while there are pending async writes on this file - mBytesRead = sVFSThread->readImmediate(mVFS, mFileID, mFileType, buffer, mPosition, bytes); - mPosition += mBytesRead; - if (! mBytesRead) - { - success = FALSE; - } - } - - return success; -} - -//static -U8* LLVFile::readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read) -{ - U8 *data; - LLVFile file(vfs, uuid, type, LLVFile::READ); - S32 file_size = file.getSize(); - if (file_size == 0) - { - // File is empty. - data = NULL; - } - else - { - data = (U8*) ll_aligned_malloc<16>(file_size); - file.read(data, file_size); /* Flawfinder: ignore */ - - if (file.getLastBytesRead() != (S32)file_size) - { - ll_aligned_free<16>(data); - data = NULL; - file_size = 0; - } - } - if (bytes_read) - { - *bytes_read = file_size; - } - return data; -} - -void LLVFile::setReadPriority(const F32 priority) -{ - mPriority = priority; - if (mHandle != LLVFSThread::nullHandle()) - { - sVFSThread->setPriority(mHandle, threadPri()); - } -} - -BOOL LLVFile::isReadComplete() -{ - BOOL res = TRUE; - if (mHandle != LLVFSThread::nullHandle()) - { - LLVFSThread::Request* req = (LLVFSThread::Request*)sVFSThread->getRequest(mHandle); - LLVFSThread::status_t status = req->getStatus(); - if (status == LLVFSThread::STATUS_COMPLETE) - { - mBytesRead = req->getBytesRead(); - mPosition += mBytesRead; - sVFSThread->completeRequest(mHandle); - mHandle = LLVFSThread::nullHandle(); - } - else - { - res = FALSE; - } - } - return res; -} - -S32 LLVFile::getLastBytesRead() -{ - return mBytesRead; -} - -BOOL LLVFile::eof() -{ - return mPosition >= getSize(); -} - -BOOL LLVFile::write(const U8 *buffer, S32 bytes) -{ - if (! (mMode & WRITE)) - { - LL_WARNS() << "Attempt to write to file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; - } - if (mHandle != LLVFSThread::nullHandle()) - { - LL_ERRS() << "Attempt to write to vfile object " << mFileID << " with pending async operation" << LL_ENDL; - return FALSE; - } - BOOL success = TRUE; - - // *FIX: allow async writes? potential problem wit mPosition... - if (mMode == APPEND) // all appends are async (but WRITEs are not) - { - U8* writebuf = new U8[bytes]; - memcpy(writebuf, buffer, bytes); - S32 offset = -1; - mHandle = sVFSThread->write(mVFS, mFileID, mFileType, - writebuf, offset, bytes, - LLVFSThread::FLAG_AUTO_COMPLETE | LLVFSThread::FLAG_AUTO_DELETE); - mHandle = LLVFSThread::nullHandle(); // FLAG_AUTO_COMPLETE means we don't track this - } - else - { - // We can't do a write while there are pending reads or writes on this file - waitForLock(VFSLOCK_READ); - waitForLock(VFSLOCK_APPEND); - - S32 pos = (mMode & APPEND) == APPEND ? -1 : mPosition; - - S32 wrote = sVFSThread->writeImmediate(mVFS, mFileID, mFileType, (U8*)buffer, pos, bytes); - - mPosition += wrote; - - if (wrote < bytes) - { - LL_WARNS() << "Tried to write " << bytes << " bytes, actually wrote " << wrote << LL_ENDL; - - success = FALSE; - } - } - return success; -} - -//static -BOOL LLVFile::writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) -{ - LLVFile file(vfs, uuid, type, LLVFile::WRITE); - file.setMaxSize(bytes); - return file.write(buffer, bytes); -} - -BOOL LLVFile::seek(S32 offset, S32 origin) -{ - if (mMode == APPEND) - { - LL_WARNS() << "Attempt to seek on append-only file" << LL_ENDL; - return FALSE; - } - - if (-1 == origin) - { - origin = mPosition; - } - - S32 new_pos = origin + offset; - - S32 size = getSize(); // Calls waitForLock(VFSLOCK_APPEND) - - if (new_pos > size) - { - LL_WARNS() << "Attempt to seek past end of file" << LL_ENDL; - - mPosition = size; - return FALSE; - } - else if (new_pos < 0) - { - LL_WARNS() << "Attempt to seek past beginning of file" << LL_ENDL; - - mPosition = 0; - return FALSE; - } - - mPosition = new_pos; - return TRUE; -} - -S32 LLVFile::tell() const -{ - return mPosition; -} - -S32 LLVFile::getSize() -{ - waitForLock(VFSLOCK_APPEND); - S32 size = mVFS->getSize(mFileID, mFileType); - - return size; -} - -S32 LLVFile::getMaxSize() -{ - S32 size = mVFS->getMaxSize(mFileID, mFileType); - - return size; -} - -BOOL LLVFile::setMaxSize(S32 size) -{ - if (! (mMode & WRITE)) - { - LL_WARNS() << "Attempt to change size of file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; - - return FALSE; - } - - if (!mVFS->checkAvailable(size)) - { - //LL_RECORD_BLOCK_TIME(FTM_VFILE_WAIT); - S32 count = 0; - while (sVFSThread->getPending() > 1000) - { - if (count % 100 == 0) - { - LL_INFOS() << "VFS catching up... Pending: " << sVFSThread->getPending() << LL_ENDL; - } - if (sVFSThread->isPaused()) - { - sVFSThread->update(0); - } - ms_sleep(10); - } - } - return mVFS->setMaxSize(mFileID, mFileType, size); -} - -BOOL LLVFile::rename(const LLUUID &new_id, const LLAssetType::EType new_type) -{ - if (! (mMode & WRITE)) - { - LL_WARNS() << "Attempt to rename file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; - - return FALSE; - } - - if (mHandle != LLVFSThread::nullHandle()) - { - LL_WARNS() << "Renaming file with pending async read" << LL_ENDL; - } - - waitForLock(VFSLOCK_READ); - waitForLock(VFSLOCK_APPEND); - - // we need to release / replace our own lock - // since the renamed file will inherit locks from the new name - mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); - mVFS->renameFile(mFileID, mFileType, new_id, new_type); - mVFS->incLock(new_id, new_type, VFSLOCK_OPEN); - - mFileID = new_id; - mFileType = new_type; - - return TRUE; -} - -BOOL LLVFile::remove() -{ -// LL_INFOS() << "Removing file " << mFileID << LL_ENDL; - - if (! (mMode & WRITE)) - { - // Leaving paranoia warning just because this should be a very infrequent - // operation. - LL_WARNS() << "Remove file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << LL_ENDL; - } - - if (mHandle != LLVFSThread::nullHandle()) - { - LL_WARNS() << "Removing file with pending async read" << LL_ENDL; - } - - // why not seek back to the beginning of the file too? - mPosition = 0; - - waitForLock(VFSLOCK_READ); - waitForLock(VFSLOCK_APPEND); - mVFS->removeFile(mFileID, mFileType); - - return TRUE; -} - -// static -void LLVFile::initClass(LLVFSThread* vfsthread) -{ - if (!vfsthread) - { - if (LLVFSThread::sLocal != NULL) - { - vfsthread = LLVFSThread::sLocal; - } - else - { - vfsthread = new LLVFSThread(); - sAllocdVFSThread = TRUE; - } - } - sVFSThread = vfsthread; -} - -// static -void LLVFile::cleanupClass() -{ - if (sAllocdVFSThread) - { - delete sVFSThread; - } - sVFSThread = NULL; -} - -bool LLVFile::isLocked(EVFSLock lock) -{ - return mVFS->isLocked(mFileID, mFileType, lock) ? true : false; -} - -void LLVFile::waitForLock(EVFSLock lock) -{ - //LL_RECORD_BLOCK_TIME(FTM_VFILE_WAIT); - // spin until the lock clears - while (isLocked(lock)) - { - if (sVFSThread->isPaused()) - { - sVFSThread->update(0); - } - ms_sleep(1); - } -} diff --git a/indra/llvfs/llvfile.h b/indra/llvfs/llvfile.h deleted file mode 100644 index 7e9d9f73e5..0000000000 --- a/indra/llvfs/llvfile.h +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @file llvfile.h - * @brief Definition of virtual file - * - * $LicenseInfo:firstyear=2002&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_LLVFILE_H -#define LL_LLVFILE_H - -#include "lluuid.h" -#include "llassettype.h" -#include "llvfs.h" -#include "llvfsthread.h" - -class LLVFile -{ -public: - LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode = LLVFile::READ); - ~LLVFile(); - - BOOL read(U8 *buffer, S32 bytes, BOOL async = FALSE, F32 priority = 128.f); /* Flawfinder: ignore */ - static U8* readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read = 0); - void setReadPriority(const F32 priority); - BOOL isReadComplete(); - S32 getLastBytesRead(); - BOOL eof(); - - BOOL write(const U8 *buffer, S32 bytes); - static BOOL writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); - BOOL seek(S32 offset, S32 origin = -1); - S32 tell() const; - - S32 getSize(); - S32 getMaxSize(); - BOOL setMaxSize(S32 size); - BOOL rename(const LLUUID &new_id, const LLAssetType::EType new_type); - BOOL remove(); - - bool isLocked(EVFSLock lock); - void waitForLock(EVFSLock lock); - - static void initClass(LLVFSThread* vfsthread = NULL); - static void cleanupClass(); - static LLVFSThread* getVFSThread() { return sVFSThread; } - -protected: - static LLVFSThread* sVFSThread; - static BOOL sAllocdVFSThread; - U32 threadPri() { return LLVFSThread::PRIORITY_NORMAL + llmin((U32)mPriority,(U32)0xfff); } - -public: - static const S32 READ; - static const S32 WRITE; - static const S32 READ_WRITE; - static const S32 APPEND; - -protected: - LLAssetType::EType mFileType; - - LLUUID mFileID; - S32 mPosition; - S32 mMode; - LLVFS *mVFS; - F32 mPriority; - - S32 mBytesRead; - LLVFSThread::handle_t mHandle; -}; - -#endif diff --git a/indra/llvfs/llvfs.cpp b/indra/llvfs/llvfs.cpp deleted file mode 100644 index cfc6397e92..0000000000 --- a/indra/llvfs/llvfs.cpp +++ /dev/null @@ -1,2260 +0,0 @@ -/** - * @file llvfs.cpp - * @brief Implementation of virtual file system - * - * $LicenseInfo:firstyear=2002&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 "llvfs.h" - -#include -#include -#include -#if LL_WINDOWS -#include -#elif LL_SOLARIS -#include -#include -#include -#else -#include -#endif - -#include "llstl.h" -#include "lltimer.h" - -const S32 FILE_BLOCK_MASK = 0x000003FF; // 1024-byte blocks -const S32 VFS_CLEANUP_SIZE = 5242880; // how much space we free up in a single stroke -const S32 BLOCK_LENGTH_INVALID = -1; // mLength for invalid LLVFSFileBlocks - -LLVFS *gVFS = NULL; - -// internal class definitions -class LLVFSBlock -{ -public: - LLVFSBlock() - { - mLocation = 0; - mLength = 0; - } - - LLVFSBlock(U32 loc, S32 size) - { - mLocation = loc; - mLength = size; - } - - static bool locationSortPredicate( - const LLVFSBlock* lhs, - const LLVFSBlock* rhs) - { - return lhs->mLocation < rhs->mLocation; - } - -public: - U32 mLocation; - S32 mLength; // allocated block size -}; - -LLVFSFileSpecifier::LLVFSFileSpecifier() -: mFileID(), - mFileType( LLAssetType::AT_NONE ) -{ -} - -LLVFSFileSpecifier::LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type) -{ - mFileID = file_id; - mFileType = file_type; -} - -bool LLVFSFileSpecifier::operator<(const LLVFSFileSpecifier &rhs) const -{ - return (mFileID == rhs.mFileID) - ? mFileType < rhs.mFileType - : mFileID < rhs.mFileID; -} - -bool LLVFSFileSpecifier::operator==(const LLVFSFileSpecifier &rhs) const -{ - return (mFileID == rhs.mFileID && - mFileType == rhs.mFileType); -} - - -class LLVFSFileBlock : public LLVFSBlock, public LLVFSFileSpecifier -{ -public: - LLVFSFileBlock() : LLVFSBlock(), LLVFSFileSpecifier() - { - init(); - } - - LLVFSFileBlock(const LLUUID &file_id, LLAssetType::EType file_type, U32 loc = 0, S32 size = 0) - : LLVFSBlock(loc, size), LLVFSFileSpecifier( file_id, file_type ) - { - init(); - } - - void init() - { - mSize = 0; - mIndexLocation = -1; - mAccessTime = (U32)time(NULL); - - for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) - { - mLocks[(EVFSLock)i] = 0; - } - } - - #ifdef LL_LITTLE_ENDIAN - inline void swizzleCopy(void *dst, void *src, int size) { memcpy(dst, src, size); /* Flawfinder: ignore */} - - #else - - inline U32 swizzle32(U32 x) - { - return(((x >> 24) & 0x000000FF) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) |((x << 24) & 0xFF000000)); - } - - inline U16 swizzle16(U16 x) - { - return( ((x >> 8) & 0x000000FF) | ((x << 8) & 0x0000FF00) ); - } - - inline void swizzleCopy(void *dst, void *src, int size) - { - if(size == 4) - { - ((U32*)dst)[0] = swizzle32(((U32*)src)[0]); - } - else if(size == 2) - { - ((U16*)dst)[0] = swizzle16(((U16*)src)[0]); - } - else - { - // Perhaps this should assert... - memcpy(dst, src, size); /* Flawfinder: ignore */ - } - } - - #endif - - void serialize(U8 *buffer) - { - swizzleCopy(buffer, &mLocation, 4); - buffer += 4; - swizzleCopy(buffer, &mLength, 4); - buffer +=4; - swizzleCopy(buffer, &mAccessTime, 4); - buffer +=4; - memcpy(buffer, &mFileID.mData, 16); /* Flawfinder: ignore */ - buffer += 16; - S16 temp_type = mFileType; - swizzleCopy(buffer, &temp_type, 2); - buffer += 2; - swizzleCopy(buffer, &mSize, 4); - } - - void deserialize(U8 *buffer, const S32 index_loc) - { - mIndexLocation = index_loc; - - swizzleCopy(&mLocation, buffer, 4); - buffer += 4; - swizzleCopy(&mLength, buffer, 4); - buffer += 4; - swizzleCopy(&mAccessTime, buffer, 4); - buffer += 4; - memcpy(&mFileID.mData, buffer, 16); - buffer += 16; - S16 temp_type; - swizzleCopy(&temp_type, buffer, 2); - mFileType = (LLAssetType::EType)temp_type; - buffer += 2; - swizzleCopy(&mSize, buffer, 4); - } - - static BOOL insertLRU(LLVFSFileBlock* const& first, - LLVFSFileBlock* const& second) - { - return (first->mAccessTime == second->mAccessTime) - ? *first < *second - : first->mAccessTime < second->mAccessTime; - } - -public: - S32 mSize; - S32 mIndexLocation; // location of index entry - U32 mAccessTime; - BOOL mLocks[VFSLOCK_COUNT]; // number of outstanding locks of each type - - static const S32 SERIAL_SIZE; -}; - -// Helper structure for doing lru w/ stl... is there a simpler way? -struct LLVFSFileBlock_less -{ - bool operator()(LLVFSFileBlock* const& lhs, LLVFSFileBlock* const& rhs) const - { - return (LLVFSFileBlock::insertLRU(lhs, rhs)) ? true : false; - } -}; - - -const S32 LLVFSFileBlock::SERIAL_SIZE = 34; - - -LLVFS::LLVFS(const std::string& index_filename, const std::string& data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash) -: mRemoveAfterCrash(remove_after_crash), - mDataFP(NULL), - mIndexFP(NULL) -{ - mDataMutex = new LLMutex(); - - S32 i; - for (i = 0; i < VFSLOCK_COUNT; i++) - { - mLockCounts[i] = 0; - } - mValid = VFSVALID_OK; - mReadOnly = read_only; - mIndexFilename = index_filename; - mDataFilename = data_filename; - - const char *file_mode = mReadOnly ? "rb" : "r+b"; - - LL_INFOS("VFS") << "Attempting to open VFS index file " << mIndexFilename << LL_ENDL; - LL_INFOS("VFS") << "Attempting to open VFS data file " << mDataFilename << LL_ENDL; - - mDataFP = openAndLock(mDataFilename, file_mode, mReadOnly); - if (!mDataFP) - { - if (mReadOnly) - { - LL_WARNS("VFS") << "Can't find " << mDataFilename << " to open read-only VFS" << LL_ENDL; - mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; - return; - } - - mDataFP = openAndLock(mDataFilename, "w+b", FALSE); - if (mDataFP) - { - // Since we're creating this data file, assume any index file is bogus - // remove the index, since this vfs is now blank - LLFile::remove(mIndexFilename); - - // When recreating the cache, also add a marker when we did this (for about/sysinfo) - LLFile::remove(mIndexFilename + ".date" ); - LLFILE *fp = LLFile::fopen( mIndexFilename + ".date", "w" ); - if( fp ) - { - std::stringstream strm; - time_t tmin; - time( &tmin ); - tm *pTm = gmtime( &tmin ); - strm << std::setw(2) << std::setfill('0') << pTm->tm_year+1900 << "-" << pTm->tm_mon+1 << "-" << pTm->tm_mday << "T" << pTm->tm_hour << ":" << pTm->tm_min << ":" << pTm->tm_sec << " " << std::endl; - - size_t bytesWritten = fwrite( strm.str().c_str(), strm.str().size(), 1, fp ); - if( !bytesWritten ) - { - LL_WARNS() << "Eror during write to " << mIndexFilename + ".date" << LL_ENDL; - } - LLFile::close( fp ); - } - // - } - else - { - LL_WARNS("VFS") << "Couldn't open vfs data file " - << mDataFilename << LL_ENDL; - mValid = VFSVALID_BAD_CANNOT_CREATE; - return; - } - - if (presize) - { - presizeDataFile(presize); - } - } - - // Did we leave this file open for writing last time? - // If so, close it and start over. - if (!mReadOnly && mRemoveAfterCrash) - { - llstat marker_info; - std::string marker = mDataFilename + ".open"; - if (!LLFile::stat(marker, &marker_info)) - { - // marker exists, kill the lock and the VFS files - unlockAndClose(mDataFP); - mDataFP = NULL; - - LL_WARNS("VFS") << "VFS: File left open on last run, removing old VFS file " << mDataFilename << LL_ENDL; - LLFile::remove(mIndexFilename); - LLFile::remove(mDataFilename); - LLFile::remove(marker); - - mDataFP = openAndLock(mDataFilename, "w+b", FALSE); - if (!mDataFP) - { - LL_WARNS("VFS") << "Can't open VFS data file in crash recovery" << LL_ENDL; - mValid = VFSVALID_BAD_CANNOT_CREATE; - return; - } - - if (presize) - { - presizeDataFile(presize); - } - } - } - - // determine the real file size - fseek(mDataFP, 0, SEEK_END); - U32 data_size = ftell(mDataFP); - - // read the index file - // make sure there's at least one file in it too - // if not, we'll treat this as a new vfs - llstat fbuf; - if (! LLFile::stat(mIndexFilename, &fbuf) && - fbuf.st_size >= LLVFSFileBlock::SERIAL_SIZE && - (mIndexFP = openAndLock(mIndexFilename, file_mode, mReadOnly)) // Yes, this is an assignment and not '==' - ) - { - std::vector buffer(fbuf.st_size); - size_t buf_offset = 0; - size_t nread = fread(&buffer[0], 1, fbuf.st_size, mIndexFP); - - std::vector files_by_loc; - - while (buf_offset < nread) - { - LLVFSFileBlock *block = new LLVFSFileBlock(); - - block->deserialize(&buffer[buf_offset], (S32)buf_offset); - - // Do sanity check on this block. - // Note that this skips zero size blocks, which helps VFS - // to heal after some errors. JC - if (block->mLength > 0 && - (U32)block->mLength <= data_size && - block->mLocation < data_size && - block->mSize > 0 && - block->mSize <= block->mLength && - block->mFileType >= LLAssetType::AT_NONE && - block->mFileType < LLAssetType::AT_COUNT) - { - mFileBlocks.insert(fileblock_map::value_type(*block, block)); - files_by_loc.push_back(block); - } - else - if (block->mLength && block->mSize > 0) - { - // this is corrupt, not empty - LL_WARNS("VFS") << "VFS corruption: " << block->mFileID << " (" << block->mFileType << ") at index " << block->mIndexLocation << " DS: " << data_size << LL_ENDL; - LL_WARNS("VFS") << "Length: " << block->mLength << "\tLocation: " << block->mLocation << "\tSize: " << block->mSize << LL_ENDL; - LL_WARNS("VFS") << "File has bad data - VFS removed" << LL_ENDL; - - delete block; - - unlockAndClose( mIndexFP ); - mIndexFP = NULL; - LLFile::remove( mIndexFilename ); - - unlockAndClose( mDataFP ); - mDataFP = NULL; - LLFile::remove( mDataFilename ); - - LL_WARNS("VFS") << "Deleted corrupt VFS files " - << mDataFilename - << " and " - << mIndexFilename - << LL_ENDL; - - mValid = VFSVALID_BAD_CORRUPT; - return; - } - else - { - // this is a null or bad entry, skip it - mIndexHoles.push_back(buf_offset); - - delete block; - } - - buf_offset += LLVFSFileBlock::SERIAL_SIZE; - } - - std::sort( - files_by_loc.begin(), - files_by_loc.end(), - LLVFSFileBlock::locationSortPredicate); - - // There are 3 cases that have to be considered. - // 1. No blocks - // 2. One block. - // 3. Two or more blocks. - if (!files_by_loc.empty()) - { - // cur walks through the list. - std::vector::iterator cur = files_by_loc.begin(); - std::vector::iterator end = files_by_loc.end(); - LLVFSFileBlock* last_file_block = *cur; - - // Check to see if there is an empty space before the first file. - if (last_file_block->mLocation > 0) - { - // If so, create a free block. - addFreeBlock(new LLVFSBlock(0, last_file_block->mLocation)); - } - - // Walk through the 2nd+ block. If there is a free space - // between cur_file_block and last_file_block, add it to - // the free space collection. This block will not need to - // run in the case there is only one entry in the VFS. - ++cur; - while( cur != end ) - { - LLVFSFileBlock* cur_file_block = *cur; - - // Dupe check on the block - if (cur_file_block->mLocation == last_file_block->mLocation - && cur_file_block->mLength == last_file_block->mLength) - { - LL_WARNS("VFS") << "VFS: removing duplicate entry" - << " at " << cur_file_block->mLocation - << " length " << cur_file_block->mLength - << " size " << cur_file_block->mSize - << " ID " << cur_file_block->mFileID - << " type " << cur_file_block->mFileType - << LL_ENDL; - - // Duplicate entries. Nuke them both for safety. - mFileBlocks.erase(*cur_file_block); // remove ID/type entry - if (cur_file_block->mLength > 0) - { - // convert to hole - addFreeBlock( - new LLVFSBlock( - cur_file_block->mLocation, - cur_file_block->mLength)); - } - lockData(); // needed for sync() - sync(cur_file_block, TRUE); // remove first on disk - sync(last_file_block, TRUE); // remove last on disk - unlockData(); // needed for sync() - last_file_block = cur_file_block; - ++cur; - continue; - } - - // Figure out where the last block ended. - S32 loc = last_file_block->mLocation+last_file_block->mLength; - - // Figure out how much space there is between where - // the last block ended and this block begins. - S32 length = cur_file_block->mLocation - loc; - - // Check for more errors... Seeing if the current - // entry and the last entry make sense together. - if (length < 0 || loc < 0 || (U32)loc > data_size) - { - // Invalid VFS - unlockAndClose( mIndexFP ); - mIndexFP = NULL; - LLFile::remove( mIndexFilename ); - - unlockAndClose( mDataFP ); - mDataFP = NULL; - LLFile::remove( mDataFilename ); - - LL_WARNS("VFS") << "VFS: overlapping entries" - << " at " << cur_file_block->mLocation - << " length " << cur_file_block->mLength - << " ID " << cur_file_block->mFileID - << " type " << cur_file_block->mFileType - << LL_ENDL; - - LL_WARNS("VFS") << "Deleted corrupt VFS files " - << mDataFilename - << " and " - << mIndexFilename - << LL_ENDL; - - mValid = VFSVALID_BAD_CORRUPT; - return; - } - - // we don't want to add empty blocks to the list... - if (length > 0) - { - addFreeBlock(new LLVFSBlock(loc, length)); - } - last_file_block = cur_file_block; - ++cur; - } - - // also note any empty space at the end - U32 loc = last_file_block->mLocation + last_file_block->mLength; - if (loc < data_size) - { - addFreeBlock(new LLVFSBlock(loc, data_size - loc)); - } - } - else // There where no blocks in the file. - { - addFreeBlock(new LLVFSBlock(0, data_size)); - } - } - else // Pre-existing index file wasn't opened - { - if (mReadOnly) - { - LL_WARNS("VFS") << "Can't find " << mIndexFilename << " to open read-only VFS" << LL_ENDL; - mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; - return; - } - - - mIndexFP = openAndLock(mIndexFilename, "w+b", FALSE); - if (!mIndexFP) - { - LL_WARNS("VFS") << "Couldn't open an index file for the VFS, probably a sharing violation!" << LL_ENDL; - - unlockAndClose( mDataFP ); - mDataFP = NULL; - LLFile::remove( mDataFilename ); - - mValid = VFSVALID_BAD_CANNOT_CREATE; - return; - } - - // no index file, start from scratch w/ 1GB allocation - LLVFSBlock *first_block = new LLVFSBlock(0, data_size ? data_size : 0x40000000); - addFreeBlock(first_block); - } - - // Open marker file to look for bad shutdowns - if (!mReadOnly && mRemoveAfterCrash) - { - std::string marker = mDataFilename + ".open"; - LLFILE* marker_fp = LLFile::fopen(marker, "w"); /* Flawfinder: ignore */ - if (marker_fp) - { - fclose(marker_fp); - marker_fp = NULL; - } - } - - LL_INFOS("VFS") << "Using VFS index file " << mIndexFilename << LL_ENDL; - LL_INFOS("VFS") << "Using VFS data file " << mDataFilename << LL_ENDL; - - mValid = VFSVALID_OK; -} - -LLVFS::~LLVFS() -{ - if (mDataMutex->isLocked()) - { - LL_ERRS("VFS") << "LLVFS destroyed with mutex locked" << LL_ENDL; - } - - unlockAndClose(mIndexFP); - mIndexFP = NULL; - - fileblock_map::const_iterator it; - for (it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - delete (*it).second; - } - mFileBlocks.clear(); - - mFreeBlocksByLength.clear(); - - for_each(mFreeBlocksByLocation.begin(), mFreeBlocksByLocation.end(), DeletePairedPointer()); - mFreeBlocksByLocation.clear(); - - unlockAndClose(mDataFP); - mDataFP = NULL; - - // Remove marker file - if (!mReadOnly && mRemoveAfterCrash) - { - std::string marker = mDataFilename + ".open"; - LLFile::remove(marker); - } - - delete mDataMutex; -} - - -// Use this function normally to create LLVFS files. -// Will append digits to the end of the filename with multiple re-trys -// static -LLVFS * LLVFS::createLLVFS(const std::string& index_filename, - const std::string& data_filename, - const BOOL read_only, - const U32 presize, - const BOOL remove_after_crash) -{ - LLVFS * new_vfs = new LLVFS(index_filename, data_filename, read_only, presize, remove_after_crash); - - if( !new_vfs->isValid() ) - { // First name failed, retry with new names - std::string retry_vfs_index_name; - std::string retry_vfs_data_name; - S32 count = 0; - while (!new_vfs->isValid() && - count < 256) - { // Append '.' to end of filenames - retry_vfs_index_name = index_filename + llformat(".%u",count); - retry_vfs_data_name = data_filename + llformat(".%u", count); - - delete new_vfs; // Delete bad VFS and try again - new_vfs = new LLVFS(retry_vfs_index_name, retry_vfs_data_name, read_only, presize, remove_after_crash); - - count++; - } - } - - if( !new_vfs->isValid() ) - { - delete new_vfs; // Delete bad VFS - new_vfs = NULL; // Total failure - } - - return new_vfs; -} - - - -void LLVFS::presizeDataFile(const U32 size) -{ - if (!mDataFP) - { - LL_ERRS() << "LLVFS::presizeDataFile() with no data file open" << LL_ENDL; - return; - } - - // we're creating this file for the first time, size it - fseek(mDataFP, size-1, SEEK_SET); - S32 tmp = 0; - tmp = (S32)fwrite(&tmp, 1, 1, mDataFP); - // fflush(mDataFP); - - // also remove any index, since this vfs is now blank - LLFile::remove(mIndexFilename); - - if (tmp) - { - LL_INFOS() << "Pre-sized VFS data file to " << ftell(mDataFP) << " bytes" << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to pre-size VFS data file" << LL_ENDL; - } -} - -BOOL LLVFS::getExists(const LLUUID &file_id, const LLAssetType::EType file_type) -{ - LLVFSFileBlock *block = NULL; - - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - block = (*it).second; - block->mAccessTime = (U32)time(NULL); - } - - BOOL res = (block && block->mLength > 0) ? TRUE : FALSE; - - unlockData(); - - return res; -} - -S32 LLVFS::getSize(const LLUUID &file_id, const LLAssetType::EType file_type) -{ - S32 size = 0; - - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - - } - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - - block->mAccessTime = (U32)time(NULL); - size = block->mSize; - } - - unlockData(); - - return size; -} - -S32 LLVFS::getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type) -{ - S32 size = 0; - - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - - block->mAccessTime = (U32)time(NULL); - size = block->mLength; - } - - unlockData(); - - return size; -} - -BOOL LLVFS::checkAvailable(S32 max_size) -{ - lockData(); - - blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(max_size); // first entry >= size - const BOOL res(iter == mFreeBlocksByLength.end() ? FALSE : TRUE); - - unlockData(); - - return res; -} - -BOOL LLVFS::setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - if (mReadOnly) - { - LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; - } - if (max_size <= 0) - { - LL_WARNS() << "VFS: Attempt to assign size " << max_size << " to vfile " << file_id << LL_ENDL; - return FALSE; - } - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - LLVFSFileBlock *block = NULL; - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - block = (*it).second; - } - - // round all sizes upward to KB increments - // SJB: Need to not round for the new texture-pipeline code so we know the correct - // max file size. Need to investigate the potential problems with this... - if (file_type != LLAssetType::AT_TEXTURE) - { - if (max_size & FILE_BLOCK_MASK) - { - max_size += FILE_BLOCK_MASK; - max_size &= ~FILE_BLOCK_MASK; - } - } - - if (block && block->mLength > 0) - { - block->mAccessTime = (U32)time(NULL); - - if (max_size == block->mLength) - { - unlockData(); - return TRUE; - } - else if (max_size < block->mLength) - { - // this file is shrinking - LLVFSBlock *free_block = new LLVFSBlock(block->mLocation + max_size, block->mLength - max_size); - - addFreeBlock(free_block); - - block->mLength = max_size; - - if (block->mLength < block->mSize) - { - // JC: Was a warning, but Ian says it's bad. - LL_ERRS() << "Truncating virtual file " << file_id << " to " << block->mLength << " bytes" << LL_ENDL; - block->mSize = block->mLength; - } - - sync(block); - //mergeFreeBlocks(); - - unlockData(); - return TRUE; - } - else if (max_size > block->mLength) - { - // this file is growing - // first check for an adjacent free block to grow into - S32 size_increase = max_size - block->mLength; - - // Find the first free block with and addres > block->mLocation - LLVFSBlock *free_block; - blocks_location_map_t::iterator iter = mFreeBlocksByLocation.upper_bound(block->mLocation); - if (iter != mFreeBlocksByLocation.end()) - { - free_block = iter->second; - - if (free_block->mLocation == block->mLocation + block->mLength && - free_block->mLength >= size_increase) - { - // this free block is at the end of the file and is large enough - - // Must call useFreeSpace before sync(), as sync() - // unlocks data structures. - useFreeSpace(free_block, size_increase); - block->mLength += size_increase; - sync(block); - - unlockData(); - return TRUE; - } - } - - // no adjecent free block, find one in the list - free_block = findFreeBlock(max_size, block); - - if (free_block) - { - // Save location where data is going, useFreeSpace will move free_block->mLocation; - U32 new_data_location = free_block->mLocation; - - //mark the free block as used so it does not - //interfere with other operations such as addFreeBlock - useFreeSpace(free_block, max_size); // useFreeSpace takes ownership (and may delete) free_block - - if (block->mLength > 0) - { - // create a new free block where this file used to be - LLVFSBlock *new_free_block = new LLVFSBlock(block->mLocation, block->mLength); - - addFreeBlock(new_free_block); - - if (block->mSize > 0) - { - // move the file into the new block - std::vector buffer(block->mSize); - fseek(mDataFP, block->mLocation, SEEK_SET); - if (fread(&buffer[0], block->mSize, 1, mDataFP) == 1) - { - fseek(mDataFP, new_data_location, SEEK_SET); - if (fwrite(&buffer[0], block->mSize, 1, mDataFP) != 1) - { - LL_WARNS() << "Short write" << LL_ENDL; - } - } else { - LL_WARNS() << "Short read" << LL_ENDL; - } - } - } - - block->mLocation = new_data_location; - - block->mLength = max_size; - - - sync(block); - - unlockData(); - return TRUE; - } - else - { - LL_WARNS() << "VFS: No space (" << max_size << ") to resize existing vfile " << file_id << LL_ENDL; - //dumpMap(); - unlockData(); - dumpStatistics(); - return FALSE; - } - } - } - else - { - // find a free block in the list - LLVFSBlock *free_block = findFreeBlock(max_size); - - if (free_block) - { - if (block) - { - block->mLocation = free_block->mLocation; - block->mLength = max_size; - } - else - { - // this file doesn't exist, create it - block = new LLVFSFileBlock(file_id, file_type, free_block->mLocation, max_size); - mFileBlocks.insert(fileblock_map::value_type(spec, block)); - } - - // Must call useFreeSpace before sync(), as sync() - // unlocks data structures. - useFreeSpace(free_block, max_size); - block->mAccessTime = (U32)time(NULL); - - sync(block); - } - else - { - LL_WARNS() << "VFS: No space (" << max_size << ") for new virtual file " << file_id << LL_ENDL; - //dumpMap(); - unlockData(); - dumpStatistics(); - return FALSE; - } - } - unlockData(); - return TRUE; -} - - -// WARNING: HERE BE DRAGONS! -// rename is the weirdest VFS op, because the file moves but the locks don't! -void LLVFS::renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, - const LLUUID &new_id, const LLAssetType::EType &new_type) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - if (mReadOnly) - { - LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; - } - - lockData(); - - LLVFSFileSpecifier new_spec(new_id, new_type); - LLVFSFileSpecifier old_spec(file_id, file_type); - - fileblock_map::iterator it = mFileBlocks.find(old_spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *src_block = (*it).second; - - // this will purge the data but leave the file block in place, w/ locks, if any - // WAS: removeFile(new_id, new_type); NOW uses removeFileBlock() to avoid mutex lock recursion - fileblock_map::iterator new_it = mFileBlocks.find(new_spec); - if (new_it != mFileBlocks.end()) - { - LLVFSFileBlock *new_block = (*new_it).second; - removeFileBlock(new_block); - } - - // if there's something in the target location, remove it but inherit its locks - it = mFileBlocks.find(new_spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *dest_block = (*it).second; - - for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) - { - if(dest_block->mLocks[i]) - { - LL_ERRS() << "Renaming VFS block to a locked file: " << dest_block->mFileID << " - Lock type: " << i << LL_ENDL; - } - dest_block->mLocks[i] = src_block->mLocks[i]; - } - - mFileBlocks.erase(new_spec); - delete dest_block; - } - - src_block->mFileID = new_id; - src_block->mFileType = new_type; - src_block->mAccessTime = (U32)time(NULL); - - mFileBlocks.erase(old_spec); - mFileBlocks.insert(fileblock_map::value_type(new_spec, src_block)); - - sync(src_block); - } - else - { - LL_WARNS() << "VFS: Attempt to rename nonexistent vfile " << file_id << ":" << file_type << LL_ENDL; - } - unlockData(); -} - -// mDataMutex must be LOCKED before calling this -void LLVFS::removeFileBlock(LLVFSFileBlock *fileblock) -{ - // convert this into an unsaved, dummy fileblock to preserve locks - // a more rubust solution would store the locks in a seperate data structure - sync(fileblock, TRUE); - - if (fileblock->mLength > 0) - { - // turn this file into an empty block - LLVFSBlock *free_block = new LLVFSBlock(fileblock->mLocation, fileblock->mLength); - - addFreeBlock(free_block); - } - - fileblock->mLocation = 0; - fileblock->mSize = 0; - fileblock->mLength = BLOCK_LENGTH_INVALID; - fileblock->mIndexLocation = -1; - - //mergeFreeBlocks(); -} - -void LLVFS::removeFile(const LLUUID &file_id, const LLAssetType::EType file_type) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - if (mReadOnly) - { - LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; - } - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - removeFileBlock(block); - } - else - { - LL_WARNS() << "VFS: attempting to remove nonexistent file " << file_id << " type " << file_type << LL_ENDL; - } - - unlockData(); -} - - -S32 LLVFS::getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length) -{ - S32 bytesread = 0; - - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - llassert(location >= 0); - llassert(length >= 0); - - BOOL do_read = FALSE; - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - - block->mAccessTime = (U32)time(NULL); - - if (location > block->mSize) - { - LL_WARNS() << "VFS: Attempt to read location " << location << " in file " << file_id << " of length " << block->mSize << LL_ENDL; - } - else - { - if (length > block->mSize - location) - { - length = block->mSize - location; - } - location += block->mLocation; - do_read = TRUE; - } - } - - if (do_read) - { - fseek(mDataFP, location, SEEK_SET); - bytesread = (S32)fread(buffer, 1, length, mDataFP); - } - - unlockData(); - - return bytesread; -} - -S32 LLVFS::storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - if (mReadOnly) - { - LL_ERRS() << "Attempt to write to read-only VFS" << LL_ENDL; - } - - llassert(length > 0); - - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - - S32 in_loc = location; - if (location == -1) - { - location = block->mSize; - } - llassert(location >= 0); - - block->mAccessTime = (U32)time(NULL); - - if (block->mLength == BLOCK_LENGTH_INVALID) - { - // Block was removed, ignore write - LL_WARNS() << "VFS: Attempt to write to invalid block" - << " in file " << file_id - << " location: " << in_loc - << " bytes: " << length - << LL_ENDL; - unlockData(); - return length; - } - else if (location > block->mLength) - { - LL_WARNS() << "VFS: Attempt to write to location " << location - << " in file " << file_id - << " type " << S32(file_type) - << " of size " << block->mSize - << " block length " << block->mLength - << LL_ENDL; - unlockData(); - return length; - } - else - { - if (length > block->mLength - location ) - { - LL_WARNS() << "VFS: Truncating write to virtual file " << file_id << " type " << S32(file_type) << LL_ENDL; - length = block->mLength - location; - } - U32 file_location = location + block->mLocation; - - fseek(mDataFP, file_location, SEEK_SET); - S32 write_len = (S32)fwrite(buffer, 1, length, mDataFP); - if (write_len != length) - { - LL_WARNS() << llformat("VFS Write Error: %d != %d",write_len,length) << LL_ENDL; - } - // fflush(mDataFP); - - if (location + length > block->mSize) - { - block->mSize = location + write_len; - sync(block); - } - unlockData(); - - return write_len; - } - } - else - { - unlockData(); - return 0; - } -} - -void LLVFS::incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) -{ - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - LLVFSFileBlock *block; - - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - block = (*it).second; - } - else - { - // Create a dummy block which isn't saved - block = new LLVFSFileBlock(file_id, file_type, 0, BLOCK_LENGTH_INVALID); - block->mAccessTime = (U32)time(NULL); - mFileBlocks.insert(fileblock_map::value_type(spec, block)); - } - - block->mLocks[lock]++; - mLockCounts[lock]++; - - unlockData(); -} - -void LLVFS::decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) -{ - lockData(); - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - - if (block->mLocks[lock] > 0) - { - block->mLocks[lock]--; - } - else - { - LL_WARNS() << "VFS: Decrementing zero-value lock " << lock << LL_ENDL; - } - mLockCounts[lock]--; - } - - unlockData(); -} - -BOOL LLVFS::isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) -{ - lockData(); - - BOOL res = FALSE; - - LLVFSFileSpecifier spec(file_id, file_type); - fileblock_map::iterator it = mFileBlocks.find(spec); - if (it != mFileBlocks.end()) - { - LLVFSFileBlock *block = (*it).second; - res = (block->mLocks[lock] > 0); - } - - unlockData(); - - return res; -} - -//============================================================================ -// protected -//============================================================================ - -void LLVFS::eraseBlockLength(LLVFSBlock *block) -{ - // find the corresponding map entry in the length map and erase it - S32 length = block->mLength; - blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(length); - blocks_length_map_t::iterator end = mFreeBlocksByLength.end(); - bool found_block = false; - while(iter != end) - { - LLVFSBlock *tblock = iter->second; - llassert(tblock->mLength == length); // there had -better- be an entry with our length! - if (tblock == block) - { - mFreeBlocksByLength.erase(iter); - found_block = true; - break; - } - ++iter; - } - if(!found_block) - { - LL_ERRS() << "eraseBlock could not find block" << LL_ENDL; - } -} - - -// Remove block from both free lists (by location and by length). -void LLVFS::eraseBlock(LLVFSBlock *block) -{ - eraseBlockLength(block); - // find the corresponding map entry in the location map and erase it - U32 location = block->mLocation; - llverify(mFreeBlocksByLocation.erase(location) == 1); // we should only have one entry per location. -} - - -// Add the region specified by block location and length to the free lists. -// Also incrementally defragment by merging with previous and next free blocks. -void LLVFS::addFreeBlock(LLVFSBlock *block) -{ -#if LL_DEBUG - size_t dbgcount = mFreeBlocksByLocation.count(block->mLocation); - if(dbgcount > 0) - { - LL_ERRS() << "addFreeBlock called with block already in list" << LL_ENDL; - } -#endif - - // Get a pointer to the next free block (by location). - blocks_location_map_t::iterator next_free_it = mFreeBlocksByLocation.lower_bound(block->mLocation); - - // We can merge with previous if it ends at our requested location. - LLVFSBlock* prev_block = NULL; - bool merge_prev = false; - if (next_free_it != mFreeBlocksByLocation.begin()) - { - blocks_location_map_t::iterator prev_free_it = next_free_it; - --prev_free_it; - prev_block = prev_free_it->second; - merge_prev = (prev_block->mLocation + prev_block->mLength == block->mLocation); - } - - // We can merge with next if our block ends at the next block's location. - LLVFSBlock* next_block = NULL; - bool merge_next = false; - if (next_free_it != mFreeBlocksByLocation.end()) - { - next_block = next_free_it->second; - merge_next = (block->mLocation + block->mLength == next_block->mLocation); - } - - if (merge_prev && merge_next) - { - // LL_INFOS() << "VFS merge BOTH" << LL_ENDL; - // Previous block is changing length (a lot), so only need to update length map. - // Next block is going away completely. JC - eraseBlockLength(prev_block); - eraseBlock(next_block); - prev_block->mLength += block->mLength + next_block->mLength; - mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); - delete block; - block = NULL; - delete next_block; - next_block = NULL; - } - else if (merge_prev) - { - // LL_INFOS() << "VFS merge previous" << LL_ENDL; - // Previous block is maintaining location, only changing length, - // therefore only need to update the length map. JC - eraseBlockLength(prev_block); - prev_block->mLength += block->mLength; - mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); // multimap insert - delete block; - block = NULL; - } - else if (merge_next) - { - // LL_INFOS() << "VFS merge next" << LL_ENDL; - // Next block is changing both location and length, - // so both free lists must update. JC - eraseBlock(next_block); - next_block->mLocation = block->mLocation; - next_block->mLength += block->mLength; - // Don't hint here, next_free_it iterator may be invalid. - mFreeBlocksByLocation.insert(blocks_location_map_t::value_type(next_block->mLocation, next_block)); // multimap insert - mFreeBlocksByLength.insert(blocks_length_map_t::value_type(next_block->mLength, next_block)); // multimap insert - delete block; - block = NULL; - } - else - { - // Can't merge with other free blocks. - // Hint that insert should go near next_free_it. - mFreeBlocksByLocation.insert(next_free_it, blocks_location_map_t::value_type(block->mLocation, block)); // multimap insert - mFreeBlocksByLength.insert(blocks_length_map_t::value_type(block->mLength, block)); // multimap insert - } -} - -// Superceeded by new addFreeBlock which does incremental free space merging. -// Incremental is faster overall. -//void LLVFS::mergeFreeBlocks() -//{ -// if (!isValid()) -// { -// LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; -// } -// // TODO: could we optimize this with hints from the calling code? -// blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); -// blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); -// LLVFSBlock *first_block = iter->second; -// while(iter != end) -// { -// blocks_location_map_t::iterator first_iter = iter; // save for if we do a merge -// if (++iter == end) -// break; -// LLVFSBlock *second_block = iter->second; -// if (first_block->mLocation + first_block->mLength == second_block->mLocation) -// { -// // remove the first block from the length map -// eraseBlockLength(first_block); -// // merge first_block with second_block, since they're adjacent -// first_block->mLength += second_block->mLength; -// // add the first block to the length map (with the new size) -// mFreeBlocksByLength.insert(blocks_length_map_t::value_type(first_block->mLength, first_block)); // multimap insert -// -// // erase and delete the second block -// eraseBlock(second_block); -// delete second_block; -// -// // reset iterator -// iter = first_iter; // haven't changed first_block, so corresponding iterator is still valid -// end = mFreeBlocksByLocation.end(); -// } -// first_block = second_block; -// } -//} - -// length bytes from free_block are going to be used (so they are no longer free) -void LLVFS::useFreeSpace(LLVFSBlock *free_block, S32 length) -{ - if (free_block->mLength == length) - { - eraseBlock(free_block); - delete free_block; - } - else - { - eraseBlock(free_block); - - free_block->mLocation += length; - free_block->mLength -= length; - - addFreeBlock(free_block); - } -} - -// NOTE! mDataMutex must be LOCKED before calling this -// sync this index entry out to the index file -// we need to do this constantly to avoid corruption on viewer crash -void LLVFS::sync(LLVFSFileBlock *block, BOOL remove) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - if (mReadOnly) - { - LL_WARNS() << "Attempt to sync read-only VFS" << LL_ENDL; - return; - } - if (block->mLength == BLOCK_LENGTH_INVALID) - { - // This is a dummy file, don't save - return; - } - if (block->mLength == 0) - { - LL_ERRS() << "VFS syncing zero-length block" << LL_ENDL; - } - - BOOL set_index_to_end = FALSE; - long seek_pos = block->mIndexLocation; - - if (-1 == seek_pos) - { - if (!mIndexHoles.empty()) - { - seek_pos = mIndexHoles.front(); - mIndexHoles.pop_front(); - } - else - { - set_index_to_end = TRUE; - } - } - - if (set_index_to_end) - { - // Need fseek/ftell to update the seek_pos and hence data - // structures, so can't unlockData() before this. - fseek(mIndexFP, 0, SEEK_END); - seek_pos = ftell(mIndexFP); - } - - block->mIndexLocation = seek_pos; - if (remove) - { - mIndexHoles.push_back(seek_pos); - } - - U8 buffer[LLVFSFileBlock::SERIAL_SIZE]; - if (remove) - { - memset(buffer, 0, LLVFSFileBlock::SERIAL_SIZE); - } - else - { - block->serialize(buffer); - } - - // If set_index_to_end, file pointer is already at seek_pos - // and we don't need to do anything. Only seek if not at end. - if (!set_index_to_end) - { - fseek(mIndexFP, seek_pos, SEEK_SET); - } - - if (fwrite(buffer, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP) != 1) - { - LL_WARNS() << "Short write" << LL_ENDL; - } - - // *NOTE: Why was this commented out? - // fflush(mIndexFP); - - return; -} - -// mDataMutex must be LOCKED before calling this -// Can initiate LRU-based file removal to make space. -// The immune file block will not be removed. -LLVFSBlock *LLVFS::findFreeBlock(S32 size, LLVFSFileBlock *immune) -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - - LLVFSBlock *block = NULL; - BOOL have_lru_list = FALSE; - - typedef std::set lru_set; - lru_set lru_list; - - LLTimer timer; - - while (! block) - { - // look for a suitable free block - blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(size); // first entry >= size - if (iter != mFreeBlocksByLength.end()) - block = iter->second; - - // no large enough free blocks, time to clean out some junk - if (! block) - { - // create a list of files sorted by usage time - // this is far faster than sorting a linked list - if (! have_lru_list) - { - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileBlock *tmp = (*it).second; - - if (tmp != immune && - tmp->mLength > 0 && - ! tmp->mLocks[VFSLOCK_READ] && - ! tmp->mLocks[VFSLOCK_APPEND] && - ! tmp->mLocks[VFSLOCK_OPEN]) - { - lru_list.insert(tmp); - } - } - - have_lru_list = TRUE; - } - - if (lru_list.size() == 0) - { - // No more files to delete, and still not enough room! - LL_WARNS() << "VFS: Can't make " << size << " bytes of free space in VFS, giving up" << LL_ENDL; - break; - } - - // is the oldest file big enough? (Should be about half the time) - lru_set::iterator it = lru_list.begin(); - LLVFSFileBlock *file_block = *it; - if (file_block->mLength >= size && file_block != immune) - { - // ditch this file and look again for a free block - should find it - // TODO: it'll be faster just to assign the free block and break - LL_INFOS() << "LRU: Removing " << file_block->mFileID << ":" << file_block->mFileType << LL_ENDL; - lru_list.erase(it); - removeFileBlock(file_block); - file_block = NULL; - continue; - } - - - LL_INFOS() << "VFS: LRU: Aggressive: " << (S32)lru_list.size() << " files remain" << LL_ENDL; - dumpLockCounts(); - - // Now it's time to aggressively make more space - // Delete the oldest 5MB of the vfs or enough to hold the file, which ever is larger - // This may yield too much free space, but we'll use it up soon enough - U32 cleanup_target = (size > VFS_CLEANUP_SIZE) ? size : VFS_CLEANUP_SIZE; - U32 cleaned_up = 0; - for (it = lru_list.begin(); - it != lru_list.end() && cleaned_up < cleanup_target; - ) - { - file_block = *it; - - // TODO: it would be great to be able to batch all these sync() calls - // LL_INFOS() << "LRU2: Removing " << file_block->mFileID << ":" << file_block->mFileType << " last accessed" << file_block->mAccessTime << LL_ENDL; - - cleaned_up += file_block->mLength; - lru_list.erase(it++); - removeFileBlock(file_block); - file_block = NULL; - } - //mergeFreeBlocks(); - } - } - - F32 time = timer.getElapsedTimeF32(); - if (time > 0.5f) - { - LL_WARNS() << "VFS: Spent " << time << " seconds in findFreeBlock!" << LL_ENDL; - } - - return block; -} - -//============================================================================ -// public -//============================================================================ - -void LLVFS::pokeFiles() -{ - if (!isValid()) - { - LL_ERRS() << "Attempting to use invalid VFS!" << LL_ENDL; - } - U32 word; - - // only write data if we actually read 4 bytes - // otherwise we're writing garbage and screwing up the file - fseek(mDataFP, 0, SEEK_SET); - if (fread(&word, sizeof(word), 1, mDataFP) == 1) - { - fseek(mDataFP, 0, SEEK_SET); - if (fwrite(&word, sizeof(word), 1, mDataFP) != 1) - { - LL_WARNS() << "Could not write to data file" << LL_ENDL; - } - fflush(mDataFP); - } - - fseek(mIndexFP, 0, SEEK_SET); - if (fread(&word, sizeof(word), 1, mIndexFP) == 1) - { - fseek(mIndexFP, 0, SEEK_SET); - if (fwrite(&word, sizeof(word), 1, mIndexFP) != 1) - { - LL_WARNS() << "Could not write to index file" << LL_ENDL; - } - fflush(mIndexFP); - } -} - - -void LLVFS::dumpMap() -{ - LL_INFOS() << "Files:" << LL_ENDL; - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileBlock *file_block = (*it).second; - LL_INFOS() << "Location: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << LL_ENDL; - } - - LL_INFOS() << "Free Blocks:" << LL_ENDL; - for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), - end = mFreeBlocksByLocation.end(); - iter != end; iter++) - { - LLVFSBlock *free_block = iter->second; - LL_INFOS() << "Location: " << free_block->mLocation << "\tLength: " << free_block->mLength << LL_ENDL; - } -} - -// verify that the index file contents match the in-memory file structure -// Very slow, do not call routinely. JC -void LLVFS::audit() -{ - // Lock the mutex through this whole function. - LLMutexLock lock_data(mDataMutex); - - fflush(mIndexFP); - - fseek(mIndexFP, 0, SEEK_END); - size_t index_size = ftell(mIndexFP); - fseek(mIndexFP, 0, SEEK_SET); - - BOOL vfs_corrupt = FALSE; - - // since we take the address of element 0, we need to have at least one element. - std::vector buffer(llmax(index_size,1U)); - - if (fread(&buffer[0], 1, index_size, mIndexFP) != index_size) - { - LL_WARNS() << "Index truncated" << LL_ENDL; - vfs_corrupt = TRUE; - } - - size_t buf_offset = 0; - - std::map found_files; - U32 cur_time = (U32)time(NULL); - - std::vector audit_blocks; - while (!vfs_corrupt && buf_offset < index_size) - { - LLVFSFileBlock *block = new LLVFSFileBlock(); - audit_blocks.push_back(block); - - block->deserialize(&buffer[buf_offset], (S32)buf_offset); - buf_offset += block->SERIAL_SIZE; - - // do sanity check on this block - if (block->mLength >= 0 && - block->mSize >= 0 && - block->mSize <= block->mLength && - block->mFileType >= LLAssetType::AT_NONE && - block->mFileType < LLAssetType::AT_COUNT && - block->mAccessTime <= cur_time && - block->mFileID != LLUUID::null) - { - if (mFileBlocks.find(*block) == mFileBlocks.end()) - { - LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " on disk, not in memory, loc " << block->mIndexLocation << LL_ENDL; - } - else if (found_files.find(*block) != found_files.end()) - { - std::map::iterator it; - it = found_files.find(*block); - LLVFSFileBlock* dupe = it->second; - // try to keep data from being lost - unlockAndClose(mIndexFP); - mIndexFP = NULL; - unlockAndClose(mDataFP); - mDataFP = NULL; - LL_WARNS() << "VFS: Original block index " << block->mIndexLocation - << " location " << block->mLocation - << " length " << block->mLength - << " size " << block->mSize - << " id " << block->mFileID - << " type " << block->mFileType - << LL_ENDL; - LL_WARNS() << "VFS: Duplicate block index " << dupe->mIndexLocation - << " location " << dupe->mLocation - << " length " << dupe->mLength - << " size " << dupe->mSize - << " id " << dupe->mFileID - << " type " << dupe->mFileType - << LL_ENDL; - LL_WARNS() << "VFS: Index size " << index_size << LL_ENDL; - LL_WARNS() << "VFS: INDEX CORRUPT" << LL_ENDL; - vfs_corrupt = TRUE; - break; - } - else - { - found_files[*block] = block; - } - } - else - { - if (block->mLength) - { - LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " corrupt on disk" << LL_ENDL; - } - // else this is just a hole - } - } - - if (!vfs_corrupt) - { - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileBlock* block = (*it).second; - - if (block->mSize > 0) - { - if (! found_files.count(*block)) - { - LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " in memory, not on disk, loc " << block->mIndexLocation<< LL_ENDL; - fseek(mIndexFP, block->mIndexLocation, SEEK_SET); - U8 buf[LLVFSFileBlock::SERIAL_SIZE]; - if (fread(buf, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP) != 1) - { - LL_WARNS() << "VFile " << block->mFileID - << " gave short read" << LL_ENDL; - } - - LLVFSFileBlock disk_block; - disk_block.deserialize(buf, block->mIndexLocation); - - LL_WARNS() << "Instead found " << disk_block.mFileID << ":" << block->mFileType << LL_ENDL; - } - else - { - block = found_files.find(*block)->second; - found_files.erase(*block); - } - } - } - - for (std::map::iterator iter = found_files.begin(); - iter != found_files.end(); iter++) - { - LLVFSFileBlock* block = iter->second; - LL_WARNS() << "VFile " << block->mFileID << ":" << block->mFileType << " szie:" << block->mSize << " leftover" << LL_ENDL; - } - - LL_INFOS() << "VFS: audit OK" << LL_ENDL; - // mutex released by LLMutexLock() destructor. - } - - for_each(audit_blocks.begin(), audit_blocks.end(), DeletePointer()); - audit_blocks.clear(); -} - - -// quick check for uninitialized blocks -// Slow, do not call in release. -void LLVFS::checkMem() -{ - lockData(); - - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileBlock *block = (*it).second; - llassert(block->mFileType >= LLAssetType::AT_NONE && - block->mFileType < LLAssetType::AT_COUNT && - block->mFileID != LLUUID::null); - - for (std::deque::iterator iter = mIndexHoles.begin(); - iter != mIndexHoles.end(); ++iter) - { - S32 index_loc = *iter; - if (index_loc == block->mIndexLocation) - { - LL_WARNS() << "VFile block " << block->mFileID << ":" << block->mFileType << " is marked as a hole" << LL_ENDL; - } - } - } - - LL_INFOS() << "VFS: mem check OK" << LL_ENDL; - - unlockData(); -} - -void LLVFS::dumpLockCounts() -{ - S32 i; - for (i = 0; i < VFSLOCK_COUNT; i++) - { - LL_INFOS() << "LockType: " << i << ": " << mLockCounts[i] << LL_ENDL; - } -} - -void LLVFS::dumpStatistics() -{ - lockData(); - - // Investigate file blocks. - std::map size_counts; - std::map location_counts; - std::map > filetype_counts; - - S32 max_file_size = 0; - S32 total_file_size = 0; - S32 invalid_file_count = 0; - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileBlock *file_block = (*it).second; - if (file_block->mLength == BLOCK_LENGTH_INVALID) - { - invalid_file_count++; - } - else if (file_block->mLength <= 0) - { - LL_INFOS() << "Bad file block at: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << LL_ENDL; - size_counts[file_block->mLength]++; - location_counts[file_block->mLocation]++; - } - else - { - total_file_size += file_block->mLength; - } - - if (file_block->mLength > max_file_size) - { - max_file_size = file_block->mLength; - } - - filetype_counts[file_block->mFileType].first++; - filetype_counts[file_block->mFileType].second += file_block->mLength; - } - - for (std::map::iterator it = size_counts.begin(); it != size_counts.end(); ++it) - { - S32 size = it->first; - S32 size_count = it->second; - LL_INFOS() << "Bad files size " << size << " count " << size_count << LL_ENDL; - } - for (std::map::iterator it = location_counts.begin(); it != location_counts.end(); ++it) - { - U32 location = it->first; - S32 location_count = it->second; - LL_INFOS() << "Bad files location " << location << " count " << location_count << LL_ENDL; - } - - // Investigate free list. - S32 max_free_size = 0; - S32 total_free_size = 0; - std::map free_length_counts; - for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), - end = mFreeBlocksByLocation.end(); - iter != end; iter++) - { - LLVFSBlock *free_block = iter->second; - if (free_block->mLength <= 0) - { - LL_INFOS() << "Bad free block at: " << free_block->mLocation << "\tLength: " << free_block->mLength << LL_ENDL; - } - else - { - LL_INFOS() << "Block: " << free_block->mLocation - << "\tLength: " << free_block->mLength - << "\tEnd: " << free_block->mLocation + free_block->mLength - << LL_ENDL; - total_free_size += free_block->mLength; - } - - if (free_block->mLength > max_free_size) - { - max_free_size = free_block->mLength; - } - - free_length_counts[free_block->mLength]++; - } - - // Dump histogram of free block sizes - for (std::map::iterator it = free_length_counts.begin(); it != free_length_counts.end(); ++it) - { - LL_INFOS() << "Free length " << it->first << " count " << it->second << LL_ENDL; - } - - LL_INFOS() << "Invalid blocks: " << invalid_file_count << LL_ENDL; - LL_INFOS() << "File blocks: " << mFileBlocks.size() << LL_ENDL; - - S32 length_list_count = (S32)mFreeBlocksByLength.size(); - S32 location_list_count = (S32)mFreeBlocksByLocation.size(); - if (length_list_count == location_list_count) - { - LL_INFOS() << "Free list lengths match, free blocks: " << location_list_count << LL_ENDL; - } - else - { - LL_WARNS() << "Free list lengths do not match!" << LL_ENDL; - LL_WARNS() << "By length: " << length_list_count << LL_ENDL; - LL_WARNS() << "By location: " << location_list_count << LL_ENDL; - } - LL_INFOS() << "Max file: " << max_file_size/1024 << "K" << LL_ENDL; - LL_INFOS() << "Max free: " << max_free_size/1024 << "K" << LL_ENDL; - LL_INFOS() << "Total file size: " << total_file_size/1024 << "K" << LL_ENDL; - LL_INFOS() << "Total free size: " << total_free_size/1024 << "K" << LL_ENDL; - LL_INFOS() << "Sum: " << (total_file_size + total_free_size) << " bytes" << LL_ENDL; - LL_INFOS() << llformat("%.0f%% full",((F32)(total_file_size)/(F32)(total_file_size+total_free_size))*100.f) << LL_ENDL; - - LL_INFOS() << " " << LL_ENDL; - for (std::map >::iterator iter = filetype_counts.begin(); - iter != filetype_counts.end(); ++iter) - { - LL_INFOS() << "Type: " << LLAssetType::getDesc(iter->first) - << " Count: " << iter->second.first - << " Bytes: " << (iter->second.second>>20) << " MB" << LL_ENDL; - } - - // Look for potential merges - { - blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); - blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); - LLVFSBlock *first_block = iter->second; - while(iter != end) - { - if (++iter == end) - break; - LLVFSBlock *second_block = iter->second; - if (first_block->mLocation + first_block->mLength == second_block->mLocation) - { - LL_INFOS() << "Potential merge at " << first_block->mLocation << LL_ENDL; - } - first_block = second_block; - } - } - unlockData(); -} - -// Debug Only! -std::string get_extension(LLAssetType::EType type) -{ - std::string extension; - switch(type) - { - case LLAssetType::AT_TEXTURE: - extension = ".jp2"; // formerly ".j2c" - break; - case LLAssetType::AT_SOUND: - extension = ".ogg"; - break; - case LLAssetType::AT_SOUND_WAV: - extension = ".wav"; - break; - case LLAssetType::AT_TEXTURE_TGA: - extension = ".tga"; - break; - case LLAssetType::AT_ANIMATION: - extension = ".lla"; - break; - case LLAssetType::AT_MESH: - extension = ".slm"; - break; - default: - // Just use the asset server filename extension in most cases - extension += "."; - extension += LLAssetType::lookup(type); - break; - } - return extension; -} - -void LLVFS::listFiles() -{ - lockData(); - - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileSpecifier file_spec = it->first; - LLVFSFileBlock *file_block = it->second; - S32 length = file_block->mLength; - S32 size = file_block->mSize; - if (length != BLOCK_LENGTH_INVALID && size > 0) - { - LLUUID id = file_spec.mFileID; - std::string extension = get_extension(file_spec.mFileType); - LL_INFOS() << " File: " << id - << " Type: " << LLAssetType::getDesc(file_spec.mFileType) - << " Size: " << size - << LL_ENDL; - } - } - - unlockData(); -} - -#include "llapr.h" -void LLVFS::dumpFiles() -{ - lockData(); - - S32 files_extracted = 0; - for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) - { - LLVFSFileSpecifier file_spec = it->first; - LLVFSFileBlock *file_block = it->second; - S32 length = file_block->mLength; - S32 size = file_block->mSize; - if (length != BLOCK_LENGTH_INVALID && size > 0) - { - LLUUID id = file_spec.mFileID; - LLAssetType::EType type = file_spec.mFileType; - std::vector buffer(size); - - unlockData(); - getData(id, type, &buffer[0], 0, size); - lockData(); - - std::string extension = get_extension(type); - std::string filename = id.asString() + extension; - LL_INFOS() << " Writing " << filename << LL_ENDL; - - LLAPRFile outfile; - outfile.open(filename, LL_APR_WB); - outfile.write(&buffer[0], size); - outfile.close(); - - files_extracted++; - } - } - - unlockData(); - - LL_INFOS() << "Extracted " << files_extracted << " files out of " << mFileBlocks.size() << LL_ENDL; -} - -time_t LLVFS::creationTime() -{ - llstat data_file_stat; - int errors = LLFile::stat(mDataFilename, &data_file_stat); - if (0 == errors) - { - time_t creation_time = data_file_stat.st_ctime; -#if LL_DARWIN - creation_time = data_file_stat.st_birthtime; -#endif - return creation_time; - } - return 0; -} - -//============================================================================ -// protected -//============================================================================ - -// static -LLFILE *LLVFS::openAndLock(const std::string& filename, const char* mode, BOOL read_lock) -{ -#if LL_WINDOWS - - return LLFile::_fsopen(filename, mode, (read_lock ? _SH_DENYWR : _SH_DENYRW)); - -#else - - LLFILE *fp; - int fd; - - // first test the lock in a non-destructive way -#if LL_SOLARIS - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 1; -#else // !LL_SOLARIS - if (strchr(mode, 'w') != NULL) - { - fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ - if (fp) - { - fd = fileno(fp); - if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) - { - fclose(fp); - return NULL; - } - - fclose(fp); - } - } -#endif // !LL_SOLARIS - - // now actually open the file for use - fp = LLFile::fopen(filename, mode); /* Flawfinder: ignore */ - if (fp) - { - fd = fileno(fp); -#if LL_SOLARIS - fl.l_type = read_lock ? F_RDLCK : F_WRLCK; - if (fcntl(fd, F_SETLK, &fl) == -1) -#else - if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) -#endif - { - fclose(fp); - fp = NULL; - } - } - - return fp; - -#endif -} - -// static -void LLVFS::unlockAndClose(LLFILE *fp) -{ - if (fp) - { - // IW: we don't actually want to unlock on linux - // this is because a forked process can kill the parent's lock - // with an explicit unlock - // however, fclose() will implicitly remove the lock - // but only once both parent and child have closed the file - /* - #if !LL_WINDOWS - int fd = fileno(fp); - flock(fd, LOCK_UN); - #endif - */ -#if LL_SOLARIS - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 1; - fl.l_type = F_UNLCK; - fcntl(fileno(fp), F_SETLK, &fl); -#endif - fclose(fp); - } -} - - -// Query when this cache was created, Returns the time and date in UTC, or unknown, -std::string LLVFS::getCreationDataUTC() const -{ - llifstream date_file; - std::string fileName = mIndexFilename + ".date"; - date_file.open( fileName.c_str() ); - if (date_file.is_open()) - { - std::string date; - std::getline(date_file, date); - date_file.close(); - - if( date.size() ) - return date; - } - return "unknown"; -} -// diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h deleted file mode 100644 index 9258c77365..0000000000 --- a/indra/llvfs/llvfs.h +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @file llvfs.h - * @brief Definition of virtual file system - * - * $LicenseInfo:firstyear=2002&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_LLVFS_H -#define LL_LLVFS_H - -#include -#include "lluuid.h" -#include "llassettype.h" -#include "llthread.h" -#include "llmutex.h" - -enum EVFSValid -{ - VFSVALID_UNKNOWN = 0, - VFSVALID_OK = 1, - VFSVALID_BAD_CORRUPT = 2, - VFSVALID_BAD_CANNOT_OPEN_READONLY = 3, - VFSVALID_BAD_CANNOT_CREATE = 4 -}; - -// Lock types for open vfiles, pending async reads, and pending async appends -// (There are no async normal writes, currently) -enum EVFSLock -{ - VFSLOCK_OPEN = 0, - VFSLOCK_READ = 1, - VFSLOCK_APPEND = 2, - - VFSLOCK_COUNT = 3 -}; - -// internal classes -class LLVFSBlock; -class LLVFSFileBlock; -class LLVFSFileSpecifier -{ -public: - LLVFSFileSpecifier(); - LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type); - bool operator<(const LLVFSFileSpecifier &rhs) const; - bool operator==(const LLVFSFileSpecifier &rhs) const; - -public: - LLUUID mFileID; - LLAssetType::EType mFileType; -}; - -class LLVFS -{ -private: - // Use createLLVFS() to open a VFS file - // Pass 0 to not presize - LLVFS(const std::string& index_filename, - const std::string& data_filename, - const BOOL read_only, - const U32 presize, - const BOOL remove_after_crash); -public: - ~LLVFS(); - - // Use this function normally to create LLVFS files - // Pass 0 to not presize - static LLVFS * createLLVFS(const std::string& index_filename, - const std::string& data_filename, - const BOOL read_only, - const U32 presize, - const BOOL remove_after_crash); - - BOOL isValid() const { return (VFSVALID_OK == mValid); } - EVFSValid getValidState() const { return mValid; } - - // ---------- The following fucntions lock/unlock mDataMutex ---------- - BOOL getExists(const LLUUID &file_id, const LLAssetType::EType file_type); - S32 getSize(const LLUUID &file_id, const LLAssetType::EType file_type); - - BOOL checkAvailable(S32 max_size); - - S32 getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type); - BOOL setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size); - - void renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, - const LLUUID &new_id, const LLAssetType::EType &new_type); - void removeFile(const LLUUID &file_id, const LLAssetType::EType file_type); - - S32 getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length); - S32 storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length); - - void incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); - void decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); - BOOL isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); - // ---------------------------------------------------------------- - - // Used to trigger evil WinXP behavior of "preloading" entire file into memory. - void pokeFiles(); - - // Verify that the index file contents match the in-memory file structure - // Very slow, do not call routinely. JC - void audit(); - // Check for uninitialized blocks. Slow, do not call in release. JC - void checkMem(); - // for debugging, prints a map of the vfs - void dumpMap(); - void dumpLockCounts(); - void dumpStatistics(); - void listFiles(); - void dumpFiles(); - time_t creationTime(); - -protected: - void removeFileBlock(LLVFSFileBlock *fileblock); - - void eraseBlockLength(LLVFSBlock *block); - void eraseBlock(LLVFSBlock *block); - void addFreeBlock(LLVFSBlock *block); - //void mergeFreeBlocks(); - void useFreeSpace(LLVFSBlock *free_block, S32 length); - void sync(LLVFSFileBlock *block, BOOL remove = FALSE); - void presizeDataFile(const U32 size); - - static LLFILE *openAndLock(const std::string& filename, const char* mode, BOOL read_lock); - static void unlockAndClose(FILE *fp); - - // Can initiate LRU-based file removal to make space. - // The immune file block will not be removed. - LLVFSBlock *findFreeBlock(S32 size, LLVFSFileBlock *immune = NULL); - - // lock/unlock data mutex (mDataMutex) - void lockData() { mDataMutex->lock(); } - void unlockData() { mDataMutex->unlock(); } - -protected: - LLMutex* mDataMutex; - - typedef std::map fileblock_map; - fileblock_map mFileBlocks; - - typedef std::multimap blocks_length_map_t; - blocks_length_map_t mFreeBlocksByLength; - typedef std::multimap blocks_location_map_t; - blocks_location_map_t mFreeBlocksByLocation; - - LLFILE *mDataFP; - LLFILE *mIndexFP; - - std::deque mIndexHoles; - - std::string mIndexFilename; - std::string mDataFilename; - BOOL mReadOnly; - - EVFSValid mValid; - - S32 mLockCounts[VFSLOCK_COUNT]; - BOOL mRemoveAfterCrash; - - // Query when this cache was created, Returns the time and date in UTC, or unknown, -public: - std::string getCreationDataUTC() const; - // -}; - -extern LLVFS *gVFS; - -#endif diff --git a/indra/llvfs/llvfsthread.cpp b/indra/llvfs/llvfsthread.cpp deleted file mode 100644 index 8cd85929e2..0000000000 --- a/indra/llvfs/llvfsthread.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/** - * @file llvfsthread.cpp - * @brief LLVFSThread implementation - * - * $LicenseInfo:firstyear=2001&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 "llvfsthread.h" -#include "llstl.h" - -//============================================================================ - -/*static*/ std::string LLVFSThread::sDataPath = ""; - -/*static*/ LLVFSThread* LLVFSThread::sLocal = NULL; - -//============================================================================ -// Run on MAIN thread -//static -void LLVFSThread::initClass(bool local_is_threaded) -{ - llassert(sLocal == NULL); - sLocal = new LLVFSThread(local_is_threaded); -} - -//static -S32 LLVFSThread::updateClass(U32 ms_elapsed) -{ - sLocal->update((F32)ms_elapsed); - return sLocal->getPending(); -} - -//static -void LLVFSThread::cleanupClass() -{ - sLocal->setQuitting(); - while (sLocal->getPending()) - { - sLocal->update(0); - } - delete sLocal; - sLocal = 0; -} - -//---------------------------------------------------------------------------- - -LLVFSThread::LLVFSThread(bool threaded) : - LLQueuedThread("VFS", threaded) -{ -} - -LLVFSThread::~LLVFSThread() -{ - // ~LLQueuedThread() will be called here -} - -//---------------------------------------------------------------------------- - -LLVFSThread::handle_t LLVFSThread::read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) -{ - handle_t handle = generateHandle(); - - priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW - Request* req = new Request(handle, priority, flags, FILE_READ, vfs, file_id, file_type, - buffer, offset, numbytes); - - bool res = addRequest(req); - if (!res) - { - LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - handle = nullHandle(); - } - - return handle; -} - -S32 LLVFSThread::readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes) -{ - handle_t handle = generateHandle(); - - Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_READ, vfs, file_id, file_type, - buffer, offset, numbytes); - - S32 res = addRequest(req) ? 1 : 0; - if (res == 0) - { - LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - } - else - { - llverify(waitForResult(handle, false) == true); - res = req->getBytesRead(); - completeRequest(handle); - } - return res; -} - -LLVFSThread::handle_t LLVFSThread::write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes, U32 flags) -{ - handle_t handle = generateHandle(); - - Request* req = new Request(handle, 0, flags, FILE_WRITE, vfs, file_id, file_type, - buffer, offset, numbytes); - - bool res = addRequest(req); - if (!res) - { - LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - handle = nullHandle(); - } - - return handle; -} - -S32 LLVFSThread::writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes) -{ - handle_t handle = generateHandle(); - - Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_WRITE, vfs, file_id, file_type, - buffer, offset, numbytes); - - S32 res = addRequest(req) ? 1 : 0; - if (res == 0) - { - LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - } - else - { - llverify(waitForResult(handle, false) == true); - res = req->getBytesRead(); - completeRequest(handle); - } - return res; -} - - -// LLVFSThread::handle_t LLVFSThread::rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, -// const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags) -// { -// handle_t handle = generateHandle(); - -// LLUUID* new_idp = new LLUUID(new_id); // deleted with Request -// // new_type is passed as "numbytes" -// Request* req = new Request(handle, 0, flags, FILE_RENAME, vfs, file_id, file_type, -// (U8*)new_idp, 0, (S32)new_type); - -// bool res = addRequest(req); -// if (!res) -// { -// LL_ERRS() << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << LL_ENDL; -// req->deleteRequest(); -// handle = nullHandle(); -// } - -// return handle; -// } - -//============================================================================ - -LLVFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, - operation_t op, LLVFS* vfs, - const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes) : - QueuedRequest(handle, priority, flags), - mOperation(op), - mVFS(vfs), - mFileID(file_id), - mFileType(file_type), - mBuffer(buffer), - mOffset(offset), - mBytes(numbytes), - mBytesRead(0) -{ - llassert(mBuffer); - - if (numbytes <= 0 && mOperation != FILE_RENAME) - { - LL_WARNS() << "LLVFSThread: Request with numbytes = " << numbytes - << " operation = " << op - << " offset " << offset - << " file_type " << file_type << LL_ENDL; - } - if (mOperation == FILE_WRITE) - { - S32 blocksize = mVFS->getMaxSize(mFileID, mFileType); - if (blocksize < 0) - { - LL_WARNS() << "VFS write to temporary block (shouldn't happen)" << LL_ENDL; - } - mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); - } - else if (mOperation == FILE_RENAME) - { - mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); - } - else // if (mOperation == FILE_READ) - { - mVFS->incLock(mFileID, mFileType, VFSLOCK_READ); - } -} - -// dec locks as soon as a request finishes -void LLVFSThread::Request::finishRequest(bool completed) -{ - if (mOperation == FILE_WRITE) - { - mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); - } - else if (mOperation == FILE_RENAME) - { - mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); - } - else // if (mOperation == FILE_READ) - { - mVFS->decLock(mFileID, mFileType, VFSLOCK_READ); - } -} - -void LLVFSThread::Request::deleteRequest() -{ - if (getStatus() == STATUS_QUEUED) - { - LL_ERRS() << "Attempt to delete a queued LLVFSThread::Request!" << LL_ENDL; - } - if (mOperation == FILE_WRITE) - { - if (mFlags & FLAG_AUTO_DELETE) - { - delete [] mBuffer; - } - } - else if (mOperation == FILE_RENAME) - { - LLUUID* new_idp = (LLUUID*)mBuffer; - delete new_idp; - } - LLQueuedThread::QueuedRequest::deleteRequest(); -} - -bool LLVFSThread::Request::processRequest() -{ - bool complete = false; - if (mOperation == FILE_READ) - { - llassert(mOffset >= 0); - mBytesRead = mVFS->getData(mFileID, mFileType, mBuffer, mOffset, mBytes); - complete = true; - //LL_INFOS() << llformat("LLVFSThread::READ '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; - } - else if (mOperation == FILE_WRITE) - { - mBytesRead = mVFS->storeData(mFileID, mFileType, mBuffer, mOffset, mBytes); - complete = true; - //LL_INFOS() << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; - } - else if (mOperation == FILE_RENAME) - { - LLUUID* new_idp = (LLUUID*)mBuffer; - LLAssetType::EType new_type = (LLAssetType::EType)mBytes; - mVFS->renameFile(mFileID, mFileType, *new_idp, new_type); - mFileID = *new_idp; - complete = true; - //LL_INFOS() << llformat("LLVFSThread::RENAME '%s': %d bytes arg:%d",getFilename(),mBytesRead) << LL_ENDL; - } - else - { - LL_ERRS() << llformat("LLVFSThread::unknown operation: %d", mOperation) << LL_ENDL; - } - return complete; -} - -//============================================================================ diff --git a/indra/llvfs/llvfsthread.h b/indra/llvfs/llvfsthread.h deleted file mode 100644 index 7814de4a2d..0000000000 --- a/indra/llvfs/llvfsthread.h +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @file llvfsthread.h - * @brief LLVFSThread definition - * - * $LicenseInfo:firstyear=2001&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_LLVFSTHREAD_H -#define LL_LLVFSTHREAD_H - -#include -#include -#include -#include - -#include "llqueuedthread.h" - -#include "llvfs.h" - -//============================================================================ - -class LLVFSThread : public LLQueuedThread -{ - //------------------------------------------------------------------------ -public: - enum operation_t { - FILE_READ, - FILE_WRITE, - FILE_RENAME - }; - - //------------------------------------------------------------------------ -public: - - class Request : public QueuedRequest - { - protected: - ~Request() {}; // use deleteRequest() - - public: - Request(handle_t handle, U32 priority, U32 flags, - operation_t op, LLVFS* vfs, - const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes); - - S32 getBytesRead() - { - return mBytesRead; - } - S32 getOperation() - { - return mOperation; - } - U8* getBuffer() - { - return mBuffer; - } - LLVFS* getVFS() - { - return mVFS; - } - std::string getFilename() - { - std::string tstring; - mFileID.toString(tstring); - return tstring; - } - - /*virtual*/ bool processRequest(); - /*virtual*/ void finishRequest(bool completed); - /*virtual*/ void deleteRequest(); - - private: - operation_t mOperation; - - LLVFS* mVFS; - LLUUID mFileID; - LLAssetType::EType mFileType; - - U8* mBuffer; // dest for reads, source for writes, new UUID for rename - S32 mOffset; // offset into file, -1 = append (WRITE only) - S32 mBytes; // bytes to read from file, -1 = all (new mFileType for rename) - S32 mBytesRead; // bytes read from file - }; - - //------------------------------------------------------------------------ -public: - static std::string sDataPath; - static LLVFSThread* sLocal; // Default worker thread - -public: - LLVFSThread(bool threaded = TRUE); - ~LLVFSThread(); - - // Return a Request handle - handle_t read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, /* Flawfinder: ignore */ - U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); - handle_t write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes, U32 flags); - // SJB: rename seems to have issues, especially when threaded -// handle_t rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, -// const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags); - // Return number of bytes read - S32 readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes); - S32 writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, - U8* buffer, S32 offset, S32 numbytes); - - /*virtual*/ bool processRequest(QueuedRequest* req); - -public: - static void initClass(bool local_is_threaded = TRUE); // Setup sLocal - static S32 updateClass(U32 ms_elapsed); - static void cleanupClass(); // Delete sLocal - static void setDataPath(const std::string& path) { sDataPath = path; } -}; - -//============================================================================ - - -#endif // LL_LLVFSTHREAD_H diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index e9d41ed9ba..bb003a77e3 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -16,7 +16,7 @@ include(LLCommon) include(LLImage) include(LLMath) include(LLRender) -include(LLVFS) +include(LLFileSystem) include(LLWindow) include(LLXML) include(UI) @@ -26,7 +26,7 @@ include_directories( ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) @@ -72,7 +72,7 @@ if (LINUX) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} #${LLWINDOW_LIBRARIES} # Don't link to itself - causes CMP0038 ${LLXML_LIBRARIES} ${UI_LIBRARIES} # for GTK @@ -95,7 +95,7 @@ if (LINUX) ${LLIMAGE_LIBRARIES} ${LLMATH_LIBRARIES} ${LLRENDER_HEADLESS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLWINDOW_HEADLESS_LIBRARIES} ${LLXML_LIBRARIES} fontconfig # For FCInit and other FC* functions. diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index 013a422d35..3a7a54e51d 100644 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -5,13 +5,13 @@ project(llxml) include(00-Common) include(LLCommon) include(LLMath) -include(LLVFS) +include(LLFileSystem) include(LLXML) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ) include_directories( ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -42,7 +42,7 @@ add_library (llxml ${llxml_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level target_link_libraries( llxml - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${EXPAT_LIBRARIES} diff --git a/indra/mac_crash_logger/CMakeLists.txt b/indra/mac_crash_logger/CMakeLists.txt index 95637c9a28..75621d7b75 100644 --- a/indra/mac_crash_logger/CMakeLists.txt +++ b/indra/mac_crash_logger/CMakeLists.txt @@ -8,7 +8,7 @@ include(LLCoreHttp) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFilesystem) include(LLXML) include(Linking) include(LLSharedLibs) @@ -19,7 +19,7 @@ include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCRASHLOGGER_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ) include_directories(SYSTEM @@ -68,11 +68,10 @@ find_library(COCOA_LIBRARY Cocoa) target_link_libraries(mac-crash-logger ${LLCRASHLOGGER_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${COCOA_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} - ${LLVFS_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOREHTTP_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/mac_crash_logger/mac_crash_logger.cpp b/indra/mac_crash_logger/mac_crash_logger.cpp index 54e41a1954..66d8cfa590 100644 --- a/indra/mac_crash_logger/mac_crash_logger.cpp +++ b/indra/mac_crash_logger/mac_crash_logger.cpp @@ -27,7 +27,6 @@ #include "linden_common.h" #include "llcrashloggermac.h" #include "indra_constants.h" -#include "llpidlock.h" #include diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 53e0e277dd..49bbd8520d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -39,7 +39,7 @@ include(LLPlugin) include(LLPrimitive) include(LLRender) include(LLUI) -include(LLVFS) +include(LLFileSystem) include(LLWindow) include(LLXML) include(NDOF) @@ -95,7 +95,7 @@ include_directories( ${LLPRIMITIVE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLUI_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LLLOGIN_INCLUDE_DIRS} @@ -2471,7 +2471,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLRENDER_LIBRARIES} ${FREETYPE_LIBRARIES} ${LLUI_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -3004,7 +3004,7 @@ if (LL_TESTS) set(test_libs ${LLMESSAGE_LIBRARIES} ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${GOOGLEMOCK_LIBRARIES} @@ -3019,7 +3019,7 @@ if (LL_TESTS) set(test_libs ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${LLMESSAGE_LIBRARIES} diff --git a/indra/newview/aoengine.cpp b/indra/newview/aoengine.cpp index 901a61bbf6..0176d38959 100644 --- a/indra/newview/aoengine.cpp +++ b/indra/newview/aoengine.cpp @@ -34,11 +34,11 @@ #include "llagentcamera.h" #include "llanimationstates.h" #include "llassetstorage.h" +#include "llfilesystem.h" #include "llinventoryfunctions.h" // for ROOT_FIRESTORM_FOLDER #include "llinventorymodel.h" #include "llnotificationsutil.h" #include "llstring.h" -#include "llvfs.h" #include "llviewercontrol.h" #include "llviewerinventory.h" @@ -1964,7 +1964,7 @@ bool AOEngine::importNotecard(const LLInventoryItem* item) } // static -void AOEngine::onNotecardLoadComplete(LLVFS* vfs, const LLUUID& assetUUID, LLAssetType::EType type, +void AOEngine::onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, void* userdata, S32 status, LLExtStat extStatus) { if (status != LL_ERR_NOERR) @@ -1977,10 +1977,13 @@ void AOEngine::onNotecardLoadComplete(LLVFS* vfs, const LLUUID& assetUUID, LLAss } LL_DEBUGS("AOEngine") << "Downloading import notecard complete." << LL_ENDL; - S32 notecardSize = vfs->getSize(assetUUID, type); - char* buffer = new char[notecardSize]; + LLFileSystem file(assetUUID, type, LLFileSystem::READ); - S32 ret = vfs->getData(assetUUID, type, reinterpret_cast(buffer), 0, notecardSize); + S32 notecardSize = file.getSize(); + char* buffer = new char[notecardSize + 1]; + buffer[notecardSize] = 0; + + S32 ret = file.read((U8*)buffer, notecardSize); if (ret > 0) { AOEngine::instance().parseNotecard(buffer); diff --git a/indra/newview/aoengine.h b/indra/newview/aoengine.h index 23b6dd9ba3..8be4dc0001 100644 --- a/indra/newview/aoengine.h +++ b/indra/newview/aoengine.h @@ -185,7 +185,7 @@ class AOEngine void onToggleAOStandsControl(); void onPauseAO(); - static void onNotecardLoadComplete(LLVFS* vfs, const LLUUID& assetUUID, LLAssetType::EType type, + static void onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, void* userdata, S32 status, LLExtStat extStatus); void parseNotecard(const char* buffer); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b79e4ed7fb..4c4cc72d53 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2930,6 +2930,39 @@ Value 23 + EnableCacheDebugInfo + + Comment + When set, display additional cache debugging information + Persist + 1 + Type + Boolean + Value + 0 + + DiskCachePercentOfTotal + + Comment + The percent of total cache size (defined by CacheSize) to use for the disk cache + Persist + 1 + Type + F32 + Value + 20.0 + + DiskCacheDirName + + Comment + The name of the disk cache (within the standard Viewer disk cache directory) + Persist + 1 + Type + String + Value + cache + CacheLocation Comment @@ -4646,17 +4679,6 @@ Value -1 - DebugStatModeVFSPendingOps - - Comment - Mode of stat in Statistics floater - Persist - 1 - Type - S32 - Value - -1 - DebugStatModeTimeDialation Comment @@ -5702,19 +5724,6 @@ Value 4 - DumpVFSCaches - - Comment - Dump VFS caches on startup. - Persist - 1 - Type - Boolean - Value - 0 - Backup - 0 - DynamicCameraStrength Comment @@ -14668,7 +14677,7 @@ Change of this parameter will affect the layout of buttons in notification toast Boolean Value 0 - + NearbyListShowIcons Comment @@ -17878,33 +17887,7 @@ Change of this parameter will affect the layout of buttons in notification toast Type Boolean Value - 1 - - VFSOldSize - - Comment - [DO NOT MODIFY] Controls resizing of local file cache - Persist 1 - Type - U32 - Value - 0 - Backup - 0 - - VFSSalt - - Comment - [DO NOT MODIFY] Controls local file caching behavior - Persist - 1 - Type - U32 - Value - 1 - Backup - 0 VelocityInterpolate diff --git a/indra/newview/app_settings/static_data.db2 b/indra/newview/app_settings/static_data.db2 deleted file mode 100644 index f85aa81601787e0643162d85f1086f548577809d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 576578 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�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 diff --git a/indra/newview/app_settings/static_index.db2 b/indra/newview/app_settings/static_index.db2 deleted file mode 100644 index a5440f96f2b16bd93bb9eeffe561ea1db8b93c25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9894 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

KmtC<(>d4qd0zj` zNBA}wKNj4ZH;-@6%SnwtK3avL$9m(m%?PchW{s<8AOuQ)3X=FMu|7gCWpaB{xKn&Y zgN8f)-NikUnAaM*SEqewW0L7zN#bo~1J3nYNWcgAvB2o9eCFlUmg6mEdVFTz#|dyB zMXZ_B_~(4*J;l1^-{~W{7IWNZ9q?|el_VBd5Fd1%)$0f3Ja~V$eTH>?X;!w=5u$=( Tu1Tg_{HGecd?G%d`H%P?%$X-n diff --git a/indra/newview/fsassetblacklist.cpp b/indra/newview/fsassetblacklist.cpp index b024a0589b..c5ac6d0a56 100644 --- a/indra/newview/fsassetblacklist.cpp +++ b/indra/newview/fsassetblacklist.cpp @@ -34,7 +34,7 @@ #include "llaudioengine.h" #include "llfloaterreg.h" #include "llsdserialize.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "llxorcipher.h" #include "llviewerobjectlist.h" @@ -175,7 +175,7 @@ void FSAssetBlacklist::addNewItemToBlacklistData(const LLUUID& id, const LLSD& d if (type == LLAssetType::AT_SOUND) { - gVFS->removeFile(id, LLAssetType::AT_SOUND); + LLFileSystem::removeFile(id, LLAssetType::AT_SOUND); std::string wav_path = gDirUtilp->getExpandedFilename(LL_PATH_FS_SOUND_CACHE, id.asString()) + ".dsf"; if (gDirUtilp->fileExists(wav_path)) { diff --git a/indra/newview/fsdata.cpp b/indra/newview/fsdata.cpp index 40414960c3..d99f6363aa 100644 --- a/indra/newview/fsdata.cpp +++ b/indra/newview/fsdata.cpp @@ -53,7 +53,7 @@ #include "llviewermedia.h" #include "llviewernetwork.h" #include "llxorcipher.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "message.h" // [RLVa:KB] @@ -1071,7 +1071,7 @@ LLSD FSData::getSystemInfo() { sysinfo2 += llformat("Texture memory: %d MB (%.2f)\n", info["TEXTUREMEMORY"].asInteger(), info["TEXTUREMEMORYMULTIPLIER"].asReal()); } - sysinfo2 += "VFS (cache) creation time (UTC) " + info["VFS_DATE"].asString(); + sysinfo2 += "Disk cache: " + info["DISK_CACHE_INFO"].asString(); LLSD sysinfos; sysinfos["Part1"] = sysinfo1; diff --git a/indra/newview/fsfloaterexport.cpp b/indra/newview/fsfloaterexport.cpp index 4e6bd43315..af55d0388c 100644 --- a/indra/newview/fsfloaterexport.cpp +++ b/indra/newview/fsfloaterexport.cpp @@ -51,7 +51,7 @@ #include "lltexturectrl.h" #include "lltrans.h" #include "llversioninfo.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" @@ -619,7 +619,7 @@ bool FSFloaterObjectExport::exportTexture(const LLUUID& texture_id) return mTextureChecked[texture_id]; } - if (gAssetStorage->mStaticVFS->getExists(texture_id, LLAssetType::AT_TEXTURE)) + if (gAssetStorage->hasLocalAsset(texture_id, LLAssetType::AT_TEXTURE)) { LL_DEBUGS("export") << "Texture " << texture_id.asString() << " is local static." << LL_ENDL; // no need to save the texture data as the viewer already has it in a local file. @@ -838,7 +838,7 @@ void FSFloaterObjectExport::inventoryChanged(LLViewerObject* object, LLInventory } // static -void FSFloaterObjectExport::onLoadComplete(LLVFS* vfs, const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) +void FSFloaterObjectExport::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { FSAssetResourceData* data = (FSAssetResourceData*)user_data; FSFloaterObjectExport* self = (FSFloaterObjectExport*)data->user_data; @@ -853,7 +853,7 @@ void FSFloaterObjectExport::onLoadComplete(LLVFS* vfs, const LLUUID& asset_uuid, } LL_DEBUGS("export") << "Saving asset " << asset_uuid.asString() << " of item " << item_uuid.asString() << LL_ENDL; - LLVFile file(vfs, asset_uuid, type); + LLFileSystem file(asset_uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length); file.read(&buffer[0], file_length); diff --git a/indra/newview/fsfloaterexport.h b/indra/newview/fsfloaterexport.h index ff15dae701..3e06b90d1c 100644 --- a/indra/newview/fsfloaterexport.h +++ b/indra/newview/fsfloaterexport.h @@ -71,7 +71,7 @@ public: LLInventoryObject::object_list_t* inventory, S32 serial_num, void* user_data); - static void onLoadComplete(LLVFS *vfs, const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/fsfloaterimport.cpp b/indra/newview/fsfloaterimport.cpp index c03e92db1a..3a45f3d342 100644 --- a/indra/newview/fsfloaterimport.cpp +++ b/indra/newview/fsfloaterimport.cpp @@ -57,8 +57,7 @@ #include "llviewerparcelmgr.h" #include "llviewerstats.h" #include "llviewerregion.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "llvolumemessage.h" #include "lltrace.h" #include "fsexportperms.h" @@ -1459,7 +1458,8 @@ void FSFloaterImport::uploadAsset(LLUUID asset_id, LLUUID inventory_item) tid.generate(); LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - LLVFile::writeFile((U8*)&asset_data[0], (S32)asset_data.size(), gVFS, new_asset_id, asset_type); + LLFileSystem file(new_asset_id, asset_type, LLFileSystem::WRITE); + file.write((U8*)&asset_data[0], (S32)asset_data.size()); LLResourceData* data( new LLResourceData ); data->mAssetInfo.mTransactionID = tid; @@ -1907,7 +1907,7 @@ void uploadCoroutine( LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &a_httpAdapter return; } - if (!gVFS->getExists(aAssetId, aAssetType)) + if (!LLFileSystem::getExists(aAssetId, aAssetType)) { LL_WARNS() << "Asset doesn't exist in VFS anymore. Nothing uploaded." << LL_ENDL; self->pushNextAsset( LLUUID::null, fs_data->uuid, aResourceData->mAssetInfo.mType ); @@ -1940,7 +1940,7 @@ void uploadCoroutine( LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &a_httpAdapter LL_DEBUGS( "import" ) << "result: " << responseResult << " new_id: " << new_id << LL_ENDL; // rename the file in the VFS to the actual asset id - gVFS->renameFile(aAssetId, aAssetType, new_id, aAssetType); + LLFileSystem::renameFile(aAssetId, aAssetType, new_id, aAssetType); if( item_id.isNull() ) { diff --git a/indra/newview/fslslpreproc.cpp b/indra/newview/fslslpreproc.cpp index 5ac6c6dcd9..b2e815df0a 100644 --- a/indra/newview/fslslpreproc.cpp +++ b/indra/newview/fslslpreproc.cpp @@ -40,7 +40,7 @@ #include "llappviewer.h" #include "llinventoryfunctions.h" #include "lltrans.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewercontrol.h" #include "llcompilequeue.h" #include "llnotificationsutil.h" @@ -720,7 +720,7 @@ void cache_script(std::string name, std::string content) infile.close(); } -void FSLSLPreprocessor::FSProcCacheCallback(LLVFS *vfs, const LLUUID& iuuid, LLAssetType::EType type, void *userdata, S32 result, LLExtStat extstat) +void FSLSLPreprocessor::FSProcCacheCallback(const LLUUID& iuuid, LLAssetType::EType type, void *userdata, S32 result, LLExtStat extstat) { LLUUID uuid = iuuid; LL_DEBUGS("FSLSLPreprocessor") << "cachecallback called" << LL_ENDL; @@ -732,7 +732,7 @@ void FSLSLPreprocessor::FSProcCacheCallback(LLVFS *vfs, const LLUUID& iuuid, LLA std::string name = item->getName(); if (result == LL_ERR_NOERR) { - LLVFile file(vfs, uuid, type); + LLFileSystem file(uuid, type); S32 file_length = file.getSize(); std::string content; diff --git a/indra/newview/fslslpreproc.h b/indra/newview/fslslpreproc.h index 210bfc6412..4c94d1f131 100644 --- a/indra/newview/fslslpreproc.h +++ b/indra/newview/fslslpreproc.h @@ -63,7 +63,7 @@ public: std::string lslcomp(std::string script); static LLUUID findInventoryByName(std::string name); - static void FSProcCacheCallback(LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, + static void FSProcCacheCallback(const LLUUID& uuid, LLAssetType::EType type, void *userdata, S32 result, LLExtStat extstat); void preprocess_script(BOOL close = FALSE, bool sync = false, bool defcache = false); void preprocess_script(const LLUUID& asset_id, LLScriptQueueData* data, LLAssetType::EType type, const std::string& script_data); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index edaceff3bb..67ebdb43e2 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -98,6 +98,7 @@ #include "lllogininstance.h" #include "llprogressview.h" #include "llvocache.h" +#include "lldiskcache.h" #include "llvopartgroup.h" // [SL:KB] - Patch: Appearance-Misc | Checked: 2013-02-12 (Catznip-3.4) #include "llappearancemgr.h" @@ -129,8 +130,6 @@ #include "llprimitive.h" #include "llurlaction.h" #include "llurlentry.h" -#include "llvfile.h" -#include "llvfsthread.h" #include "llvolumemgr.h" #include "llxfermanager.h" #include "llphysicsextensions.h" @@ -370,9 +369,6 @@ bool gUseWireframe = FALSE; //use for remember deferred mode in wireframe switch bool gInitialDeferredModeForWireframe = FALSE; -// VFS globals - see llappviewer.h -LLVFS* gStaticVFS = NULL; - LLMemoryInfo gSysMemory; U64Bytes gMemoryAllocated(0); // updated in display_stats() in llviewerdisplay.cpp @@ -493,11 +489,6 @@ void init_default_trans_args() default_trans_args.insert("APP_NAME_ABBR"); // Appreviated application title } -//---------------------------------------------------------------------------- -// File scope definitons -const char *VFS_DATA_FILE_BASE = "data.db2.x."; -const char *VFS_INDEX_FILE_BASE = "index.db2.x."; - std::string gWindowTitle; struct SettingsFile : public LLInitParam::Block @@ -1172,10 +1163,6 @@ bool LLAppViewer::init() // *Note: this is where gViewerStats used to be created. - // - // Initialize the VFS, and gracefully handle initialization errors - // - if (!initCache()) { LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL; @@ -1609,7 +1596,6 @@ static LLTrace::BlockTimerStatHandle FTM_TEXTURE_CACHE("Texture Cache"); static LLTrace::BlockTimerStatHandle FTM_DECODE("Image Decode"); static LLTrace::BlockTimerStatHandle FTM_FETCH("Image Fetch"); -static LLTrace::BlockTimerStatHandle FTM_VFS("VFS Thread"); static LLTrace::BlockTimerStatHandle FTM_LFS("LFS Thread"); static LLTrace::BlockTimerStatHandle FTM_PAUSE_THREADS("Pause Threads"); static LLTrace::BlockTimerStatHandle FTM_IDLE("Idle"); @@ -1887,10 +1873,6 @@ bool LLAppViewer::doFrame() work_pending += updateTextureThreads(max_time); - { - LL_RECORD_BLOCK_TIME(FTM_VFS); - io_pending += LLVFSThread::updateClass(1); - } { LL_RECORD_BLOCK_TIME(FTM_LFS); io_pending += LLLFSThread::updateClass(1); @@ -1898,7 +1880,7 @@ bool LLAppViewer::doFrame() if (io_pending > 1000) { - ms_sleep(llmin(io_pending/100,100)); // give the vfs some time to catch up + ms_sleep(llmin(io_pending/100,100)); // give the lfs some time to catch up } total_work_pending += work_pending ; @@ -1915,7 +1897,6 @@ bool LLAppViewer::doFrame() } if(!total_io_pending) //pause file threads if nothing to process. { - LLVFSThread::sLocal->pause(); LLLFSThread::sLocal->pause(); } @@ -1994,12 +1975,11 @@ S32 LLAppViewer::updateTextureThreads(F32 max_time) return work_pending; } -void LLAppViewer::flushVFSIO() +void LLAppViewer::flushLFSIO() { while (1) { - S32 pending = LLVFSThread::updateClass(0); - pending += LLLFSThread::updateClass(0); + S32 pending = LLLFSThread::updateClass(0); if (!pending) { break; @@ -2135,7 +2115,7 @@ bool LLAppViewer::cleanup() LLKeyframeDataCache::clear(); - // End TransferManager before deleting systems it depends on (Audio, VFS, AssetStorage) + // End TransferManager before deleting systems it depends on (Audio, AssetStorage) #if 0 // this seems to get us stuck in an infinite loop... gTransferManager.cleanup(); #endif @@ -2209,8 +2189,8 @@ bool LLAppViewer::cleanup() LL_INFOS() << "Cache files removed" << LL_ENDL; - // Wait for any pending VFS IO - flushVFSIO(); + // Wait for any pending LFS IO + flushLFSIO(); LL_INFOS() << "Shutting down Views" << LL_ENDL; // Destroy the UI @@ -2294,15 +2274,6 @@ bool LLAppViewer::cleanup() SUBSYSTEM_CLEANUP(LLWorldMapView); SUBSYSTEM_CLEANUP(LLFolderViewItem); - // - // Shut down the VFS's AFTER the decode manager cleans up (since it cleans up vfiles). - // Also after viewerwindow is deleted, since it may have image pointers (which have vfiles) - // Also after shutting down the messaging system since it has VFS dependencies - - // - LL_INFOS() << "Cleaning up VFS" << LL_ENDL; - SUBSYSTEM_CLEANUP(LLVFile); - LL_INFOS() << "Saving Data" << LL_ENDL; // Store the time of our current logoff @@ -2427,7 +2398,6 @@ bool LLAppViewer::cleanup() pending += LLAppViewer::getTextureCache()->update(1); // unpauses the worker thread pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread - pending += LLVFSThread::updateClass(0); pending += LLLFSThread::updateClass(0); F64 idle_time = idleTimer.getElapsedTimeF64(); if(!pending) @@ -2502,28 +2472,11 @@ bool LLAppViewer::cleanup() gTextureList.shutdown(); // shutdown again in case a callback added something LLUIImageList::getInstance()->cleanUp(); - // This should eventually be done in LLAppViewer SUBSYSTEM_CLEANUP(LLImage); - SUBSYSTEM_CLEANUP(LLVFSThread); SUBSYSTEM_CLEANUP(LLLFSThread); -#ifndef LL_RELEASE_FOR_DOWNLOAD - LL_INFOS() << "Auditing VFS" << LL_ENDL; - if(gVFS) - { - gVFS->audit(); - } -#endif - LL_INFOS() << "Misc Cleanup" << LL_ENDL; - // For safety, the LLVFS has to be deleted *after* LLVFSThread. This should be cleaned up. - // (LLVFS doesn't know about LLVFSThread so can't kill pending requests) -Steve - delete gStaticVFS; - gStaticVFS = NULL; - delete gVFS; - gVFS = NULL; - gSavedSettings.cleanup(); LLUIColorTable::instance().clear(); @@ -2605,7 +2558,6 @@ bool LLAppViewer::initThreads() LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); - LLVFSThread::initClass(enable_threads && false); LLLFSThread::initClass(enable_threads && false); // Image decoding @@ -3894,10 +3846,6 @@ LLSD LLAppViewer::getViewerInfo() const //info["RENDER_QUALITY"] = (F32)gSavedSettings.getU32("RenderQualityPerformance"); //info["GPU_SHADERS"] = gSavedSettings.getBOOL("RenderDeferred") ? "Enabled" : "Disabled"; //info["TEXTURE_MEMORY"] = gSavedSettings.getS32("TextureMemory"); - - //LLSD substitution; - //substitution["datetime"] = (S32)(gVFS ? gVFS->creationTime() : 0); - //info["VFS_TIME"] = LLTrans::getString("AboutTime", substitution); // #if LL_DARWIN @@ -3991,6 +3939,9 @@ LLSD LLAppViewer::getViewerInfo() const info["SERVER_RELEASE_NOTES_URL"] = mServerReleaseNotesURL; } + // populate field for new local disk cache with some details + info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); + // FIRE-4785: Current render quality setting in sysinfo / about floater switch (gSavedSettings.getU32("RenderQualityPerformance")) { @@ -4051,13 +4002,6 @@ LLSD LLAppViewer::getViewerInfo() const info["TEXTUREMEMORYGPURESERVE"] = gSavedSettings.getS32("FSDynamicTextureMemoryGPUReserve"); // - // Add creation time of VFS (cache) - if( gVFS ) - info["VFS_DATE"] = gVFS->getCreationDataUTC(); - else - info["VFS_DATE"] = "unknown"; - // - return info; } @@ -4140,9 +4084,9 @@ std::string LLAppViewer::getViewerInfoString(bool default_string) const support << "\n" << LLTrans::getString("AboutTextureMemory", args, default_string); } } - if (info.has("VFS_DATE")) + if (info.has("DISK_CACHE_INFO")) { - support << "\n" << LLTrans::getString("AboutVFS", args, default_string); + support << "\n" << LLTrans::getString("AboutCache", args, default_string); } // if (info.has("COMPILER")) @@ -4760,7 +4704,7 @@ void LLAppViewer::forceQuit() void LLAppViewer::fastQuit(S32 error_code) { // finish pending transfers - flushVFSIO(); + flushLFSIO(); // let sim know we're logging out sendLogoutRequest(); // flush network buffers by shutting down messaging system @@ -4961,39 +4905,6 @@ void LLAppViewer::migrateCacheDirectory() #endif // LL_WINDOWS || LL_DARWIN } -void dumpVFSCaches() -{ - LL_INFOS() << "======= Static VFS ========" << LL_ENDL; - gStaticVFS->listFiles(); -#if LL_WINDOWS - LL_INFOS() << "======= Dumping static VFS to StaticVFSDump ========" << LL_ENDL; - WCHAR w_str[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, w_str); - S32 res = LLFile::mkdir("StaticVFSDump"); - if (res == -1) - { - LL_WARNS() << "Couldn't create dir StaticVFSDump" << LL_ENDL; - } - SetCurrentDirectory(utf8str_to_utf16str("StaticVFSDump").c_str()); - gStaticVFS->dumpFiles(); - SetCurrentDirectory(w_str); -#endif - - LL_INFOS() << "========= Dynamic VFS ====" << LL_ENDL; - gVFS->listFiles(); -#if LL_WINDOWS - LL_INFOS() << "========= Dumping dynamic VFS to VFSDump ====" << LL_ENDL; - res = LLFile::mkdir("VFSDump"); - if (res == -1) - { - LL_WARNS() << "Couldn't create dir VFSDump" << LL_ENDL; - } - SetCurrentDirectory(utf8str_to_utf16str("VFSDump").c_str()); - gVFS->dumpFiles(); - SetCurrentDirectory(w_str); -#endif -} - //static U32 LLAppViewer::getTextureCacheVersion() { @@ -5020,6 +4931,21 @@ bool LLAppViewer::initCache() LLAppViewer::getTextureCache()->setReadOnly(read_only) ; LLVOCache::initParamSingleton(read_only); + // initialize the new disk cache using saved settings + const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName"); + + // note that the maximum size of this cache is defined as a percentage of the + // total cache size - the 'CacheSize' pref - for all caches. + const unsigned int cache_total_size_mb = gSavedSettings.getU32("CacheSize"); + const double disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal"); + const unsigned int disk_cache_mb = cache_total_size_mb * disk_cache_percent / 100; + const unsigned int disk_cache_bytes = disk_cache_mb * 1024 * 1024; + const bool enable_cache_debug_info = gSavedSettings.getBOOL("EnableDiskCacheDebugInfo"); + // Don't ignore cache path for asset cache; Moved further down until cache path has been set correctly + //const std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name); + //LLDiskCache::initParamSingleton(cache_dir, disk_cache_bytes, enable_cache_debug_info); + // + bool texture_cache_mismatch = false; if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion()) { @@ -5088,11 +5014,27 @@ bool LLAppViewer::initCache() gSavedSettings.setString("FSSoundCacheLocation", ""); } // - - if (mPurgeCache && !read_only) + + // Don't ignore cache path for asset cache + const std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name); + LLDiskCache::initParamSingleton(cache_dir, disk_cache_bytes, enable_cache_debug_info); + // + + if (!read_only) { - LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); - purgeCache(); + if (mPurgeCache) + { + LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); + purgeCache(); + + // clear the new C++ file system based cache + LLDiskCache::getInstance()->clearCache(); + } + else + { + // purge excessive files from the new file system based cache + LLDiskCache::getInstance()->purge(); + } } // FIRE-13066 @@ -5127,172 +5069,18 @@ bool LLAppViewer::initCache() const S32 MB = 1024 * 1024; const S64 MIN_CACHE_SIZE = 256 * MB; const S64 MAX_CACHE_SIZE = 9984ll * MB; - const S64 MAX_VFS_SIZE = 1024 * MB; // 1 GB S64 cache_size = (S64)(gSavedSettings.getU32("CacheSize")) * MB; cache_size = llclamp(cache_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE); - S64 vfs_size = llmin((S64)((cache_size * 2) / 10), MAX_VFS_SIZE); - S64 texture_cache_size = cache_size - vfs_size; + S64 texture_cache_size = cache_size; S64 extra = LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, texture_cache_mismatch); texture_cache_size -= extra; - LLVOCache::getInstance()->initCache(LL_PATH_CACHE, gSavedSettings.getU32("CacheNumberOfRegionsForObjects"), getObjectCacheVersion()); - LLSplashScreen::update(LLTrans::getString("StartupInitializingVFS")); - - // Init the VFS - vfs_size = llmin(vfs_size + extra, MAX_VFS_SIZE); - vfs_size = (vfs_size / MB) * MB; // make sure it is MB aligned - U32 vfs_size_u32 = (U32)vfs_size; - U32 old_vfs_size = gSavedSettings.getU32("VFSOldSize") * MB; - bool resize_vfs = (vfs_size_u32 != old_vfs_size); - if (resize_vfs) - { - gSavedSettings.setU32("VFSOldSize", vfs_size_u32 / MB); - } - LL_INFOS("AppCache") << "VFS CACHE SIZE: " << vfs_size / (1024*1024) << " MB" << LL_ENDL; - - // This has to happen BEFORE starting the vfs - // time_t ltime; - srand(time(NULL)); // Flawfinder: ignore - U32 old_salt = gSavedSettings.getU32("VFSSalt"); - U32 new_salt; - std::string old_vfs_data_file; - std::string old_vfs_index_file; - std::string new_vfs_data_file; - std::string new_vfs_index_file; - std::string static_vfs_index_file; - std::string static_vfs_data_file; - - if (gSavedSettings.getBOOL("AllowMultipleViewers")) - { - // don't mess with renaming the VFS in this case - new_salt = old_salt; - } - else - { - do - { - new_salt = rand(); - } while(new_salt == old_salt); - } - - old_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_DATA_FILE_BASE) + llformat("%u", old_salt); - - // make sure this file exists - llstat s; - S32 stat_result = LLFile::stat(old_vfs_data_file, &s); - if (stat_result) - { - // doesn't exist, look for a data file - std::string mask; - mask = VFS_DATA_FILE_BASE; - mask += "*"; - - std::string dir; - dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); - - std::string found_file; - LLDirIterator iter(dir, mask); - if (iter.next(found_file)) - { - old_vfs_data_file = gDirUtilp->add(dir, found_file); - - S32 start_pos = found_file.find_last_of('.'); - if (start_pos > 0) - { - sscanf(found_file.substr(start_pos+1).c_str(), "%d", &old_salt); - } - LL_DEBUGS("AppCache") << "Default vfs data file not present, found: " << old_vfs_data_file << " Old salt: " << old_salt << LL_ENDL; - } - } - - old_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_INDEX_FILE_BASE) + llformat("%u", old_salt); - - stat_result = LLFile::stat(old_vfs_index_file, &s); - if (stat_result) - { - // We've got a bad/missing index file, nukem! - LL_WARNS("AppCache") << "Bad or missing vfx index file " << old_vfs_index_file << LL_ENDL; - LL_WARNS("AppCache") << "Removing old vfs data file " << old_vfs_data_file << LL_ENDL; - LLFile::remove(old_vfs_data_file); - LLFile::remove(old_vfs_index_file); - - // Just in case, nuke any other old cache files in the directory. - std::string dir; - dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); - - std::string mask; - mask = VFS_DATA_FILE_BASE; - mask += "*"; - - gDirUtilp->deleteFilesInDir(dir, mask); - - mask = VFS_INDEX_FILE_BASE; - mask += "*"; - - gDirUtilp->deleteFilesInDir(dir, mask); - } - - new_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_DATA_FILE_BASE) + llformat("%u", new_salt); - new_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, VFS_INDEX_FILE_BASE) + llformat("%u", new_salt); - - static_vfs_data_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "static_data.db2"); - static_vfs_index_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "static_index.db2"); - - if (resize_vfs) - { - LL_DEBUGS("AppCache") << "Removing old vfs and re-sizing" << LL_ENDL; - - LLFile::remove(old_vfs_data_file); - LLFile::remove(old_vfs_index_file); - } - else if (old_salt != new_salt) - { - // move the vfs files to a new name before opening - LL_DEBUGS("AppCache") << "Renaming " << old_vfs_data_file << " to " << new_vfs_data_file << LL_ENDL; - LL_DEBUGS("AppCache") << "Renaming " << old_vfs_index_file << " to " << new_vfs_index_file << LL_ENDL; - LLFile::rename(old_vfs_data_file, new_vfs_data_file); - LLFile::rename(old_vfs_index_file, new_vfs_index_file); - } - - // Startup the VFS... - gSavedSettings.setU32("VFSSalt", new_salt); - - // Don't remove VFS after viewer crashes. If user has corrupt data, they can reinstall. JC - gVFS = LLVFS::createLLVFS(new_vfs_index_file, new_vfs_data_file, false, vfs_size_u32, false); - if (!gVFS) - { - return false; - } - - gStaticVFS = LLVFS::createLLVFS(static_vfs_index_file, static_vfs_data_file, true, 0, false); - if (!gStaticVFS) - { - return false; - } - - BOOL success = gVFS->isValid() && gStaticVFS->isValid(); - if (!success) - { - return false; - } - else - { - LLVFile::initClass(); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - if (gSavedSettings.getBOOL("DumpVFSCaches")) - { - dumpVFSCaches(); - } -#endif - - return true; - } + return true; } void LLAppViewer::addOnIdleCallback(const boost::function& cb) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 92efa9e26e..7dd0d043c3 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -1,4 +1,5 @@ /** + * @mainpage * @mainpage * * This is the sources for the Second Life Viewer; @@ -83,7 +84,7 @@ public: virtual bool frame(); // Override for application body logic // Application control - void flushVFSIO(); // waits for vfs transfers to complete + void flushLFSIO(); // waits for lfs transfers to complete void forceQuit(); // Puts the viewer into 'shutting down without error' mode. void fastQuit(S32 error_code = 0); // Shuts down the viewer immediately after sending a logout message void requestQuit(); // Request a quit. A kinder, gentler quit. @@ -418,12 +419,6 @@ extern BOOL gRestoreGL; extern bool gUseWireframe; extern bool gInitialDeferredModeForWireframe; -// VFS globals - gVFS is for general use -// gStaticVFS is read-only and is shipped w/ the viewer -// it has pre-cache data like the UI .TGAs -class LLVFS; -extern LLVFS *gStaticVFS; - extern LLMemoryInfo gSysMemory; extern U64Bytes gMemoryAllocated; diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index f56cbcd089..818e09999f 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -631,7 +631,7 @@ void LLAppViewerWin32::disableWinErrorReporting() } } -const S32 MAX_CONSOLE_LINES = 500; +const S32 MAX_CONSOLE_LINES = 7500; // Only defined in newer SDKs than we currently use #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4 diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index bacd855e68..49b07a0ac7 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -52,7 +52,7 @@ #include "lldir.h" #include "llnotificationsutil.h" #include "llviewerstats.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "lluictrlfactory.h" #include "lltrans.h" @@ -135,7 +135,7 @@ namespace } // *NOTE$: A minor specialization of LLScriptAssetUpload, it does not require a buffer -// (and does not save a buffer to the vFS) and it finds the compile queue window and +// (and does not save a buffer to the cache) and it finds the compile queue window and // displays a compiling message. class LLQueuedScriptAssetUpload : public LLScriptAssetUpload { @@ -153,8 +153,8 @@ public: virtual LLSD prepareUpload() { /* *NOTE$: The parent class (LLScriptAssetUpload will attempt to save - * the script buffer into to the VFS. Since the resource is already in - * the VFS we don't want to do that. Just put a compiling message in + * the script buffer into to the cache. Since the resource is already in + * the cache we don't want to do that. Just put a compiling message in * the window and move on */ LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", LLSD(mQueueId)); @@ -360,11 +360,11 @@ void LLFloaterCompileQueue::handleHTTPResponse(std::string pumpName, const LLSD LLEventPumps::instance().post(pumpName, expresult); } -// *TODO: handleSCriptRetrieval is passed into the VFS via a legacy C function pointer +// *TODO: handleSCriptRetrieval is passed into the cache via a legacy C function pointer // future project would be to convert these to C++ callables (std::function<>) so that // we can use bind and remove the userData parameter. // -void LLFloaterCompileQueue::handleScriptRetrieval(LLVFS *vfs, const LLUUID& assetId, +void LLFloaterCompileQueue::handleScriptRetrieval(const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus) { LLSD result(LLSD::emptyMap()); @@ -401,7 +401,7 @@ void LLFloaterCompileQueue::handleScriptRetrieval(LLVFS *vfs, const LLUUID& asse if (queue && queue->mLSLProc) { - LLVFile file(vfs, assetId, type); + LLFileSystem file(assetId, type); S32 file_length = file.getSize(); std::vector script_data(file_length + 1); file.read((U8*)&script_data[0], file_length); diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index fbee3d9fb6..edd7900ec4 100644 --- a/indra/newview/llcompilequeue.h +++ b/indra/newview/llcompilequeue.h @@ -162,7 +162,7 @@ protected: //bool checkAssetId(const LLUUID &assetId); static void handleHTTPResponse(std::string pumpName, const LLSD &expresult); - static void handleScriptRetrieval(LLVFS *vfs, const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); + static void handleScriptRetrieval(const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); private: static void processExperienceIdResults(LLSD result, LLUUID parent); diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 163997b31f..53ea7fd316 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -144,7 +144,7 @@ public: S32 getFileCount() const { return (S32)mFiles.size(); } - // See llvfs/lldir.h : getBaseFileName and getDirName to extract base or directory names + // see lldir.h : getBaseFileName and getDirName to extract base or directory names // clear any lists of buffers or whatever, and make sure the file // picker isn't locked. diff --git a/indra/newview/llfloaterauction.cpp b/indra/newview/llfloaterauction.cpp index 2edbf7c0cd..0bac4cff3f 100644 --- a/indra/newview/llfloaterauction.cpp +++ b/indra/newview/llfloaterauction.cpp @@ -32,8 +32,7 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "llparcel.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "llwindow.h" #include "message.h" @@ -208,7 +207,9 @@ void LLFloaterAuction::onClickSnapshot(void* data) LLPointer tga = new LLImageTGA; tga->encode(raw); - LLVFile::writeFile(tga->getData(), tga->getDataSize(), gVFS, self->mImageID, LLAssetType::AT_IMAGE_TGA); + + LLFileSystem tga_file(self->mImageID, LLAssetType::AT_IMAGE_TGA, LLFileSystem::WRITE); + tga_file.write(tga->getData(), tga->getDataSize()); raw->biasedScaleToPowerOfTwo(LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT); @@ -216,7 +217,9 @@ void LLFloaterAuction::onClickSnapshot(void* data) LLPointer j2c = new LLImageJ2C; j2c->encode(raw, 0.0f); - LLVFile::writeFile(j2c->getData(), j2c->getDataSize(), gVFS, self->mImageID, LLAssetType::AT_TEXTURE); + + LLFileSystem j2c_file(self->mImageID, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + j2c_file.write(j2c->getData(), j2c->getDataSize()); self->mImage = LLViewerTextureManager::getLocalTexture((LLImageRaw*)raw, FALSE); gGL.getTexUnit(0)->bind(self->mImage); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index d5248267f1..28639a352a 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -32,7 +32,7 @@ #include "lldatapacker.h" #include "lldir.h" #include "llnotificationsutil.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llapr.h" #include "llstring.h" @@ -1416,10 +1416,9 @@ void LLFloaterBvhPreview::onBtnOK(void* userdata) LLDataPackerBinaryBuffer dp(buffer, file_size); if (motionp->serialize(dp)) { - LLVFile file(gVFS, motionp->getID(), LLAssetType::AT_ANIMATION, LLVFile::APPEND); + LLFileSystem file(motionp->getID(), LLAssetType::AT_ANIMATION, LLFileSystem::APPEND); S32 size = dp.getCurrentSize(); - file.setMaxSize(size); if (file.write((U8*)buffer, size)) { std::string name = floaterp->getChild("name_form")->getValue().asString(); diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 89f3629bb0..213513c8a2 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -60,6 +60,7 @@ #include "lltabcontainer.h" #include "llcolorswatch.h" // #include "lltrans.h" +#include "llfilesystem.h" #include "llcallbacklist.h" #include "llviewertexteditor.h" #include "llviewernetwork.h" diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index d2a7b655e4..2a3b075167 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -116,7 +116,7 @@ protected: // void onBtnCancel(const LLSD& userdata); - //void onClickClearCache(); // Clear viewer texture cache, vfs, and VO cache on next startup // AO: was protected, moved to public + //void onClickClearCache(); // Clear viewer texture cache, file cache on next startup // AO: was protected, moved to public void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above void onLanguageChange(); void onNotificationsChange(const std::string& OptionName); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 4f81af2755..379f3fdce5 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -36,7 +36,7 @@ #include "llglheaders.h" #include "llregionflags.h" #include "llstl.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llxfermanager.h" #include "indra_constants.h" #include "message.h" @@ -2468,10 +2468,9 @@ void LLPanelEstateCovenant::loadInvItem(LLInventoryItem *itemp) } // static -void LLPanelEstateCovenant::onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPanelEstateCovenant::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_INFOS() << "LLPanelEstateCovenant::onLoadComplete()" << LL_ENDL; LLPanelEstateCovenant* panelp = (LLPanelEstateCovenant*)user_data; @@ -2479,7 +2478,7 @@ void LLPanelEstateCovenant::onLoadComplete(LLVFS *vfs, { if(0 == status) { - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 file_length = file.getSize(); diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index ffe8ab5e40..b8b3967208 100644 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -55,7 +55,6 @@ class LLRadioGroup; class LLSliderCtrl; class LLSpinCtrl; class LLTextBox; -class LLVFS; class LLPanelRegionGeneralInfo; // Aurora Sim - Region Settings Panel @@ -387,8 +386,7 @@ public: static bool confirmResetCovenantCallback(const LLSD& notification, const LLSD& response); void sendChangeCovenantID(const LLUUID &asset_id); void loadInvItem(LLInventoryItem *itemp); - static void onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 4d4f954a18..20a0ae94ce 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -44,8 +44,7 @@ #include "llnotificationsutil.h" #include "llstring.h" #include "llsys.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "mean_collision_data.h" #include "message.h" #include "v3math.h" @@ -913,12 +912,9 @@ void LLFloaterReporter::takeScreenshot(bool use_prev_screenshot) mResourceDatap->mAssetInfo.setName("screenshot_name"); mResourceDatap->mAssetInfo.setDescription("screenshot_descr"); - // store in VFS - LLVFile::writeFile(upload_data->getData(), - upload_data->getDataSize(), - gVFS, - mResourceDatap->mAssetInfo.mUuid, - mResourceDatap->mAssetInfo.mType); + // store in cache + LLFileSystem j2c_file(mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType, LLFileSystem::WRITE); + j2c_file.write(upload_data->getData(), upload_data->getDataSize()); // store in the image list so it doesn't try to fetch from the server LLPointer image_in_list = diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index 81af6de5a7..c1d5b60a17 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -41,7 +41,7 @@ #include "lltextbox.h" #include "llui.h" #include "lluictrlfactory.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "message.h" #include "llstartup.h" // login_alert_done #include "llviewernetwork.h" // FIX FIRE 3143 SJ diff --git a/indra/newview/llfloatertos.h b/indra/newview/llfloatertos.h index 85033acf4d..7c2f0705b7 100644 --- a/indra/newview/llfloatertos.h +++ b/indra/newview/llfloatertos.h @@ -36,7 +36,6 @@ class LLButton; class LLRadioGroup; -class LLVFS; class LLTextEditor; class LLUUID; diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index ec82c17a00..00a5037c08 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -42,7 +42,7 @@ #include "llnotificationsutil.h" #include "llstl.h" #include "llstring.h" // todo: remove -#include "llvfile.h" +#include "llfilesystem.h" #include "message.h" // newview @@ -564,7 +564,7 @@ void LLGestureMgr::playGesture(LLMultiGesture* gesture) LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; const LLUUID& anim_id = anim_step->mAnimAssetID; - // Don't request the animation if this step stops it or if it is already in Static VFS + // Don't request the animation if this step stops it or if it is already in the cache if (!(anim_id.isNull() || anim_step->mFlags & ANIM_FLAG_STOP || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION))) @@ -1065,10 +1065,9 @@ void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step) // static -void LLGestureMgr::onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLLoadInfo* info = (LLLoadInfo*)user_data; @@ -1083,7 +1082,7 @@ void LLGestureMgr::onLoadComplete(LLVFS *vfs, if (0 == status) { - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 size = file.getSize(); std::vector buffer(size+1); @@ -1186,8 +1185,7 @@ void LLGestureMgr::onLoadComplete(LLVFS *vfs, } // static -void LLGestureMgr::onAssetLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, +void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -1199,7 +1197,7 @@ void LLGestureMgr::onAssetLoadComplete(LLVFS *vfs, { case LLAssetType::AT_ANIMATION: { - LLKeyframeMotion::onLoadComplete(vfs, asset_uuid, type, user_data, status, ext_status); + LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status); self.mLoadingAssets.erase(asset_uuid); @@ -1207,7 +1205,7 @@ void LLGestureMgr::onAssetLoadComplete(LLVFS *vfs, } case LLAssetType::AT_SOUND: { - LLAudioEngine::assetCallback(vfs, asset_uuid, type, user_data, status, ext_status); + LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status); self.mLoadingAssets.erase(asset_uuid); diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index 402bdf6039..91ab445273 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -40,7 +40,6 @@ class LLMultiGesture; class LLGestureListener; class LLGestureStep; class LLUUID; -class LLVFS; class LLGestureManagerObserver { @@ -154,15 +153,13 @@ protected: void done(); // Used by loadGesture - static void onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); // Used by playGesture to load an asset file // required to play a gesture step - static void onAssetLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, + static void onAssetLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index 9d43602a73..71c74f88db 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -33,7 +33,7 @@ #include "llappviewer.h" #include "llagent.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewerstats.h" // Globals @@ -120,7 +120,6 @@ LLLandmark* LLLandmarkList::getAsset(const LLUUID& asset_uuid, loaded_callback_t // static void LLLandmarkList::processGetAssetReply( - LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void* user_data, @@ -129,7 +128,7 @@ void LLLandmarkList::processGetAssetReply( { if( status == 0 ) { - LLVFile file(vfs, uuid, type); + LLFileSystem file(uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length + 1); diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h index 2e7bd25610..0e4859dbc9 100644 --- a/indra/newview/lllandmarklist.h +++ b/indra/newview/lllandmarklist.h @@ -52,7 +52,6 @@ public: BOOL assetExists(const LLUUID& asset_uuid); LLLandmark* getAsset(const LLUUID& asset_uuid, loaded_callback_t cb = NULL); static void processGetAssetReply( - LLVFS *vfs, const LLUUID& uuid, LLAssetType::EType type, void* user_data, diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index f674a89971..fc9b5174d7 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -49,7 +49,7 @@ #include "llsdutil_math.h" #include "llsdserialize.h" #include "llthread.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" @@ -295,8 +295,6 @@ // * Header parse failures come without much explanation. Elaborate. // * Work queue for uploads? Any need for this or is the current scheme good // enough? -// * Various temp buffers used in VFS I/O might be allocated once or even -// statically. Look for some wins here. // * Move data structures holding mesh data used by main thread into main- // thread-only access so that no locking is needed. May require duplication // of some data so that worker thread has a minimal data set to guide @@ -1402,8 +1400,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check VFS for mesh skin info - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1436,7 +1434,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) delete[] buffer; } - //reading from VFS failed for whatever reason, fetch from sim + //reading from cache failed for whatever reason, fetch from sim std::string http_url; // [UDP Assets] //constructUrl(mesh_id, &http_url); @@ -1505,8 +1503,8 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check VFS for mesh skin info - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1540,7 +1538,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) delete[] buffer; } - //reading from VFS failed for whatever reason, fetch from sim + //reading from cache failed for whatever reason, fetch from sim std::string http_url; // [UDP Assets] //constructUrl(mesh_id, &http_url); @@ -1609,8 +1607,8 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check VFS for mesh physics shape info - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); + //check cache for mesh physics shape info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; @@ -1643,7 +1641,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) delete[] buffer; } - //reading from VFS failed for whatever reason, fetch from sim + //reading from cache failed for whatever reason, fetch from sim std::string http_url; // [UDP Assets] //constructUrl(mesh_id, &http_url); @@ -1721,8 +1719,8 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c ++LLMeshRepository::sMeshRequestCount; { - //look for mesh in asset in vfs - LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH); + //look for mesh in asset in cache + LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); S32 size = file.getSize(); @@ -1736,7 +1734,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) { - // Found mesh in VFS cache + // Found mesh in cache return true; } } @@ -1807,8 +1805,8 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check VFS for mesh asset - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH); + //check cache for mesh asset + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); if (file.getSize() >= offset+size) { U8* buffer = new(std::nothrow) U8[size]; @@ -1843,7 +1841,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, delete[] buffer; } - //reading from VFS failed for whatever reason, fetch from sim + //reading from cache failed for whatever reason, fetch from sim std::string http_url; // [UDP Assets] //constructUrl(mesh_id, &http_url); @@ -3312,7 +3310,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b } else if (data && data_size > 0) { - // header was successfully retrieved from sim and parsed, cache in vfs + // header was successfully retrieved from sim and parsed and is in cache S32 header_bytes = 0; LLSD header; @@ -3351,31 +3349,32 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b // It's possible for the remote asset to have more data than is needed for the local cache - // only allocate as much space in the VFS as is needed for the local cache + // only allocate as much space in the cache as is needed for the local cache data_size = llmin(data_size, bytes); - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); - if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) + // Fix asset caching + //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + if (file.getMaxSize() >= bytes) { LLMeshRepository::sCacheBytesWritten += data_size; ++LLMeshRepository::sCacheWrites; file.write(data, data_size); - - // zero out the rest of the file - U8 block[MESH_HEADER_SIZE]; - memset(block, 0, sizeof(block)); - while (bytes-file.tell() > sizeof(block)) - { - file.write(block, sizeof(block)); - } - - S32 remaining = bytes-file.tell(); + // Fix asset caching + S32 remaining = bytes - file.tell(); if (remaining > 0) { - file.write(block, remaining); + U8* block = new(std::nothrow) U8[remaining]; + if (block) + { + memset(block, 0, remaining); + file.write(block, remaining); + delete[] block; + } } + // } } else @@ -3427,8 +3426,10 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); if (result == MESH_OK) { - // good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3491,8 +3492,10 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) { - // good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3539,8 +3542,10 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) { - // good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3586,8 +3591,10 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S3 && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) { - // good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache for caching + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 038bfabdc1..cf4d1a1b47 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -50,7 +50,6 @@ class LLVOVolume; class LLMutex; class LLCondition; -class LLVFS; class LLMeshRepository; typedef enum e_mesh_processing_result_enum diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index d00b239a7b..4550c71e6d 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -32,7 +32,7 @@ // llcommon #include "llcommonutils.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llaccordionctrltab.h" #include "llappearancemgr.h" diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index 6dd8a6298f..ce5c090134 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -38,7 +38,6 @@ #include -class LLVFS; class LLOutfitGallery; class LLOutfitGalleryItem; class LLOutfitListGearMenuBase; diff --git a/indra/newview/llpostcard.cpp b/indra/newview/llpostcard.cpp index 064977bc6a..d292a4763a 100644 --- a/indra/newview/llpostcard.cpp +++ b/indra/newview/llpostcard.cpp @@ -28,8 +28,7 @@ #include "llpostcard.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "llviewerregion.h" #include "message.h" diff --git a/indra/newview/llpreviewgesture.cpp b/indra/newview/llpreviewgesture.cpp index f8d44b8e56..56f20c7341 100644 --- a/indra/newview/llpreviewgesture.cpp +++ b/indra/newview/llpreviewgesture.cpp @@ -30,7 +30,7 @@ #include "llagent.h" #include "llanimstatelabels.h" #include "llanimationstates.h" -#include "llappviewer.h" // gVFS +#include "llappviewer.h" #include "llcheckboxctrl.h" #include "llcombobox.h" #include "lldatapacker.h" @@ -47,7 +47,7 @@ #include "llradiogroup.h" #include "llresmgr.h" #include "lltrans.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewerstats.h" @@ -849,10 +849,9 @@ void LLPreviewGesture::loadAsset() // static -void LLPreviewGesture::onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LLUUID* item_idp = (LLUUID*)user_data; @@ -861,7 +860,7 @@ void LLPreviewGesture::onLoadComplete(LLVFS *vfs, { if (0 == status) { - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 size = file.getSize(); std::vector buffer(size+1); @@ -1146,10 +1145,9 @@ void LLPreviewGesture::saveIfNeeded() tid.generate(); assetId = tid.makeAssetID(gAgent.getSecureSessionID()); - LLVFile file(gVFS, assetId, LLAssetType::AT_GESTURE, LLVFile::APPEND); + LLFileSystem file(assetId, LLAssetType::AT_GESTURE, LLFileSystem::APPEND); S32 size = dp.getCurrentSize(); - file.setMaxSize(size); file.write((U8*)buffer, size); LLLineEditor* descEditor = getChild("desc"); diff --git a/indra/newview/llpreviewgesture.h b/indra/newview/llpreviewgesture.h index 3ba4f56295..19bccf35bd 100644 --- a/indra/newview/llpreviewgesture.h +++ b/indra/newview/llpreviewgesture.h @@ -39,7 +39,6 @@ class LLScrollListCtrl; class LLScrollListItem; class LLButton; class LLRadioGroup; -class LLVFS; class LLPreviewGesture : public LLPreview { @@ -80,8 +79,7 @@ protected: void loadAsset(); - static void onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index 5b9ae1ac1a..e6eedd76bf 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -46,7 +46,7 @@ #include "llselectmgr.h" #include "lltrans.h" #include "llviewertexteditor.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llviewerinventory.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" @@ -393,8 +393,7 @@ void LLPreviewNotecard::loadAsset() } // static -void LLPreviewNotecard::onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, +void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -405,7 +404,7 @@ void LLPreviewNotecard::onLoadComplete(LLVFS *vfs, { if(0 == status) { - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 file_length = file.getSize(); @@ -520,7 +519,7 @@ void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", LLSD(itemId)); if (nc) { - // *HACK: we have to delete the asset in the VFS so + // *HACK: we have to delete the asset in the cache so // that the viewer will redownload it. This is only // really necessary if the asset had to be modified by // the uploader, so this can be optimized away in some @@ -528,7 +527,7 @@ void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, // script actually changed the asset. if (nc->hasEmbeddedInventory()) { - gVFS->removeFile(newAssetId, LLAssetType::AT_NOTECARD); + LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); } if (newItemId.isNull()) { @@ -555,7 +554,7 @@ void LLPreviewNotecard::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUI { if (nc->hasEmbeddedInventory()) { - gVFS->removeFile(newAssetId, LLAssetType::AT_NOTECARD); + LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); } nc->setAssetId(newAssetId); nc->refreshFromInventory(); @@ -640,14 +639,13 @@ bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem, bool sync) tid.generate(); asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - LLVFile file(gVFS, asset_id, LLAssetType::AT_NOTECARD, LLVFile::APPEND); + LLFileSystem file(asset_id, LLAssetType::AT_NOTECARD, LLFileSystem::APPEND); LLSaveNotecardInfo* info = new LLSaveNotecardInfo(this, mItemUUID, mObjectUUID, tid, copyitem); S32 size = buffer.length() + 1; - file.setMaxSize(size); file.write((U8*)buffer.c_str(), size); gAssetStorage->storeAssetData(tid, LLAssetType::AT_NOTECARD, diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h index 5b8e092551..69d949da7c 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -101,8 +101,7 @@ protected: void deleteNotecard(); - static void onLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 2a47aecae9..120b27b6df 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -52,7 +52,7 @@ #include "llsdserialize.h" #include "llslider.h" #include "lltooldraganddrop.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llagent.h" #include "llmenugl.h" @@ -2430,12 +2430,16 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) std::string buffer(mScriptEd->getScriptText()); // Script Preprocessor + LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); + //LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, - // [](LLUUID itemId, LLUUID, LLUUID, LLSD response) { + // [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { + // LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); // LLPreviewLSL::finishedLSLUpload(itemId, response); // })); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, - [](LLUUID itemId, LLUUID, LLUUID, LLSD response) { + [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { + LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); LLPreviewLSL::finishedLSLUpload(itemId, response); }, domono)); @@ -2446,8 +2450,8 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) } // static -void LLPreviewLSL::onLoadComplete( LLVFS *vfs, const LLUUID& asset_uuid, LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS() << "LLPreviewLSL::onLoadComplete: got uuid " << asset_uuid << LL_ENDL; @@ -2457,7 +2461,7 @@ void LLPreviewLSL::onLoadComplete( LLVFS *vfs, const LLUUID& asset_uuid, LLAsset { if(0 == status) { - LLVFile file(vfs, asset_uuid, type); + LLFileSystem file(asset_uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length+1); @@ -2484,6 +2488,7 @@ void LLPreviewLSL::onLoadComplete( LLVFS *vfs, const LLUUID& asset_uuid, LLAsset } preview->mScriptEd->setScriptName(script_name); preview->mScriptEd->setEnableEditing(is_modifiable); + preview->mScriptEd->setAssetID(asset_uuid); preview->mAssetStatus = PREVIEW_ASSET_LOADED; // [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2.0) | Added: Catznip-3.2.0 @@ -2753,7 +2758,7 @@ void LLLiveLSLEditor::loadAsset() } // static -void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, +void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status) { @@ -2777,9 +2782,10 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, } // - instance->loadScriptText(vfs, asset_id, type); + instance->loadScriptText(asset_id, type); instance->mScriptEd->setEnableEditing(TRUE); instance->mAssetStatus = PREVIEW_ASSET_LOADED; + instance->mScriptEd->setAssetID(asset_id); // [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2.0) | Added: Catznip-3.2.0 // Start the timer which will perform regular backup saves @@ -2808,9 +2814,9 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, delete floater_key; } -void LLLiveLSLEditor::loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +void LLLiveLSLEditor::loadScriptText(const LLUUID &uuid, LLAssetType::EType type) { - LLVFile file(vfs, uuid, type); + LLFileSystem file(uuid, type); S32 file_length = file.getSize(); std::vector buffer(file_length + 1); file.read((U8*)&buffer[0], file_length); @@ -2984,6 +2990,7 @@ void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAs if (preview) { preview->mItem->setAssetUUID(newAssetId); + preview->mScriptEd->setAssetID(newAssetId); // Bytecode save completed if (response["compiled"]) @@ -3067,12 +3074,14 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) // std::string buffer(mScriptEd->mEditor->getText()); std::string buffer(mScriptEd->getScriptText()); // Script Preprocessor + LLUUID old_asset_id = mScriptEd->getAssetID(); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mObjectUUID, mItemUUID, monoChecked() ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, isRunning, mScriptEd->getAssociatedExperience(), buffer, - [isRunning](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { - LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); + [isRunning, old_asset_id](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { + LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); + LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); })); LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 20dfb7d5c9..4d709d07a6 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -49,7 +49,6 @@ struct LLEntryAndEdCore; class LLMenuBarGL; //class LLFloaterScriptSearch; class LLKeywordToken; -class LLVFS; class LLViewerInventoryItem; class LLScriptEdContainer; class LLFloaterGotoLine; @@ -165,6 +164,9 @@ public: void setItemRemoved(bool script_removed){mScriptRemoved = script_removed;}; + void setAssetID( const LLUUID& asset_id){ mAssetID = asset_id; }; + LLUUID getAssetID() { return mAssetID; } + private: // NaCl - LSL Preprocessor void onToggleProc(); @@ -236,6 +238,7 @@ private: LLUUID mAssociatedExperience; BOOL mScriptRemoved; BOOL mSaveDialogShown; + LLUUID mAssetID; LLTextBox* mLineCol; // Advanced Script Editor @@ -328,7 +331,7 @@ protected: static void onSave(void* userdata, BOOL close_after_save, bool sync); // - static void onLoadComplete(LLVFS *vfs, const LLUUID& uuid, + static void onLoadComplete(const LLUUID& uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); @@ -396,13 +399,13 @@ private: static void onSave(void* userdata, BOOL close_after_save, bool sync); // - static void onLoadComplete(LLVFS *vfs, const LLUUID& asset_uuid, + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); static void onRunningCheckboxClicked(LLUICtrl*, void* userdata); static void onReset(void* userdata); - void loadScriptText(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); + void loadScriptText(const LLUUID &uuid, LLAssetType::EType type); static void onErrorList(LLUICtrl*, void* user_data); diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index 1e5b893cbc..58cbe0e20a 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -57,7 +57,7 @@ #include "llinventorymodel.h" #include "llassetstorage.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "lldrawpoolwater.h" #include @@ -292,18 +292,18 @@ void LLSettingsVOBase::onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, L void LLSettingsVOBase::getSettingsAsset(const LLUUID &assetId, LLSettingsVOBase::asset_download_fn callback) { gAssetStorage->getAssetData(assetId, LLAssetType::AT_SETTINGS, - [callback](LLVFS *vfs, const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) - { onAssetDownloadComplete(vfs, asset_id, status, ext_status, callback); }, + [callback](const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) + { onAssetDownloadComplete(asset_id, status, ext_status, callback); }, nullptr, true); } -void LLSettingsVOBase::onAssetDownloadComplete(LLVFS *vfs, const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) +void LLSettingsVOBase::onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) { LLSettingsBase::ptr_t settings; if (!status) { - LLVFile file(vfs, asset_id, LLAssetType::AT_SETTINGS, LLVFile::READ); + LLFileSystem file(asset_id, LLAssetType::AT_SETTINGS, LLFileSystem::READ); S32 size = file.getSize(); std::string buffer(size + 1, '\0'); diff --git a/indra/newview/llsettingsvo.h b/indra/newview/llsettingsvo.h index 65136ad2f5..a1baf02fe7 100644 --- a/indra/newview/llsettingsvo.h +++ b/indra/newview/llsettingsvo.h @@ -38,7 +38,6 @@ #include "llextendedstatus.h" #include -class LLVFS; class LLInventoryItem; class LLGLSLShader; @@ -81,7 +80,7 @@ private: static void onAgentAssetUploadComplete(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback); static void onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback); - static void onAssetDownloadComplete(LLVFS *vfs, const LLUUID &asset_id, S32 status, LLExtStat ext_status, asset_download_fn callback); + static void onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, asset_download_fn callback); }; //========================================================================= diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index f3df947a71..1100ac1bb2 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -31,6 +31,7 @@ #include "llagentbenefits.h" #include "llagentcamera.h" #include "llagentui.h" +#include "llfilesystem.h" #include "llcombobox.h" #include "llfloaterperms.h" #include "llfloaterreg.h" @@ -51,8 +52,6 @@ #include "llviewercontrol.h" #include "llviewermenufile.h" // upload_new_resource() #include "llviewerstats.h" -#include "llvfile.h" -#include "llvfs.h" #include "llwindow.h" #include "llworld.h" #include @@ -1119,7 +1118,8 @@ void LLSnapshotLivePreview::saveTexture(BOOL outfit_snapshot, std::string name) if (formatted->encode(scaled, 0.0f)) { - LLVFile::writeFile(formatted->getData(), formatted->getDataSize(), gVFS, new_asset_id, LLAssetType::AT_TEXTURE); + LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + fmt_file.write(formatted->getData(), formatted->getDataSize()); std::string pos_string; LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); std::string who_took_it; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 7a839d7b1b..5aba427bc7 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -82,7 +82,6 @@ #include "llversioninfo.h" #include "llviewercontrol.h" #include "llviewerhelp.h" -#include "llvfs.h" #include "llxorcipher.h" // saved password, MAC address #include "llwindow.h" #include "message.h" @@ -314,7 +313,7 @@ bool login_alert_status(const LLSD& notification, const LLSD& response); void login_packet_failed(void**, S32 result); void use_circuit_callback(void**, S32 result); void register_viewer_callbacks(LLMessageSystem* msg); -void asset_callback_nothing(LLVFS*, const LLUUID&, LLAssetType::EType, void*, S32); +void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); bool callback_choose_gender(const LLSD& notification, const LLSD& response); void init_start_screen(S32 location_id); void release_start_screen(); @@ -856,7 +855,7 @@ bool idle_startup() // start the xfer system. by default, choke the downloads // a lot... const S32 VIEWER_MAX_XFER = 3; - start_xfer_manager(gVFS); + start_xfer_manager(); gXferManager->setMaxIncomingXfers(VIEWER_MAX_XFER); F32 xfer_throttle_bps = gSavedSettings.getF32("XferThrottle"); if (xfer_throttle_bps > 1.f) @@ -864,7 +863,7 @@ bool idle_startup() gXferManager->setUseAckThrottling(TRUE); gXferManager->setAckThrottleBPS(xfer_throttle_bps); } - gAssetStorage = new LLViewerAssetStorage(msg, gXferManager, gVFS, gStaticVFS); + gAssetStorage = new LLViewerAssetStorage(msg, gXferManager); F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); @@ -1575,13 +1574,6 @@ bool idle_startup() // and "[CURRENT_GRID] Help" menu entries update_grid_help(); - // Poke the VFS, which could potentially block for a while if - // Windows XP is acting up - set_startup_status(0.07f, LLTrans::getString("LoginVerifyingCache"), LLStringUtil::null); - display_startup(); - - gVFS->pokeFiles(); - // fsdata agents support //LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); LLStartUp::setStartupState(STATE_AGENTS_WAIT); @@ -3512,7 +3504,7 @@ void register_viewer_callbacks(LLMessageSystem* msg) msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message); } -void asset_callback_nothing(LLVFS*, const LLUUID&, LLAssetType::EType, void*, S32) +void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32) { // nothing } diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 806750b989..a4fa362d7d 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -55,6 +55,7 @@ const S32 TEXTURE_FAST_CACHE_ENTRY_OVERHEAD = sizeof(S32) * 4; //w, h, c, level const S32 TEXTURE_FAST_CACHE_DATA_SIZE = 16 * 16 * 4; const S32 TEXTURE_FAST_CACHE_ENTRY_SIZE = TEXTURE_FAST_CACHE_DATA_SIZE + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD; const F32 TEXTURE_LAZY_PURGE_TIME_LIMIT = .004f; // 4ms. Would be better to autoadjust, but there is a major cache rework in progress. +const F32 TEXTURE_PRUNING_MAX_TIME = 15.f; class LLTextureCacheWorker : public LLWorkerClass { @@ -1556,7 +1557,6 @@ void LLTextureCache::readHeaderCache() if (num_entries - empty_entries > sCacheMaxEntries) { // Special case: cache size was reduced, need to remove entries - // Note: After we prune entries, we will call this again and create the LRU U32 entries_to_purge = (num_entries - empty_entries) - sCacheMaxEntries; LL_INFOS() << "Texture Cache Entries: " << num_entries << " Max: " << sCacheMaxEntries << " Empty: " << empty_entries << " Purging: " << entries_to_purge << LL_ENDL; // We can exit the following loop with the given condition, since if we'd reach the end of the lru set we'd have: @@ -1569,7 +1569,7 @@ void LLTextureCache::readHeaderCache() ++iter; } } - else + { S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE); for (std::set::iterator iter = lru.begin(); iter != lru.end(); ++iter) @@ -1583,30 +1583,19 @@ void LLTextureCache::readHeaderCache() if (purge_list.size() > 0) { + LLTimer timer; for (std::set::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter) { std::string tex_filename = getTextureFileName(entries[*iter].mID); removeEntry((S32)*iter, entries[*iter], tex_filename); - } - // If we removed any entries, we need to rebuild the entries list, - // write the header, and call this again - std::vector new_entries; - for (U32 i=0; i 0) + + //make sure that pruning entries doesn't take too much time + if (timer.getElapsedTimeF32() > TEXTURE_PRUNING_MAX_TIME) { - new_entries.push_back(entry); + break; } } - mFreeList.clear(); // recreating list, no longer valid. - llassert_always(new_entries.size() <= sCacheMaxEntries); - mHeaderEntriesInfo.mEntries = new_entries.size(); - writeEntriesHeader(); - writeEntriesAndClose(new_entries); - mHeaderMutex.unlock(); // unlock the mutex before calling again - readHeaderCache(); // repeat with new entries file - mHeaderMutex.lock(); + writeEntriesAndClose(entries); } else { @@ -1756,7 +1745,7 @@ void LLTextureCache::purgeTexturesLazy(F32 time_limit_sec) } S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (sCacheMaxTexturesSize * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; + S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; for (time_idx_set_t::iterator iter = time_idx_set.begin(); iter != time_idx_set.end(); ++iter) { @@ -1852,21 +1841,26 @@ void LLTextureCache::purgeTextures(bool validate) } S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (sCacheMaxTexturesSize * (S64)((1.f-TEXTURE_CACHE_PURGE_AMOUNT)*100)) / 100; + S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; S32 purge_count = 0; for (time_idx_set_t::iterator iter = time_idx_set.begin(); iter != time_idx_set.end(); ++iter) { S32 idx = iter->second; bool purge_entry = false; - if (validate) + + if (cache_size >= purged_cache_size) + { + purge_entry = true; + } + else if (validate) { // make sure file exists and is the correct size U32 uuididx = entries[idx].mID.mData[0]; if (uuididx == validate_idx) { - std::string filename = getTextureFileName(entries[idx].mID); - LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; + std::string filename = getTextureFileName(entries[idx].mID); + LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; // mHeaderAPRFilePoolp because this is under header mutex in main thread S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); if (bodysize != entries[idx].mBodySize) @@ -1876,10 +1870,6 @@ void LLTextureCache::purgeTextures(bool validate) } } } - else if (cache_size >= purged_cache_size) - { - purge_entry = true; - } else { break; diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 9d1e5c9c1f..5f6f281e30 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -28,8 +28,7 @@ #include "llviewerassetstorage.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "message.h" #include "llagent.h" @@ -106,10 +105,8 @@ public: ///---------------------------------------------------------------------------- // Unused? -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, - const LLHost &upstream_host) - : LLAssetStorage(msg, xfer, vfs, static_vfs, upstream_host), +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) + : LLAssetStorage(msg, xfer, upstream_host), mAssetCoroCount(0), mCountRequests(0), mCountStarted(0), @@ -119,10 +116,8 @@ LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager * { } - -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs) - : LLAssetStorage(msg, xfer, vfs, static_vfs), +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) + : LLAssetStorage(msg, xfer), mAssetCoroCount(0), mCountRequests(0), mCountStarted(0), @@ -159,13 +154,13 @@ void LLViewerAssetStorage::storeAssetData( if (mUpstreamHost.isOk()) { - if (mVFS->getExists(asset_id, asset_type)) + if (LLFileSystem::getExists(asset_id, asset_type)) { // Pack data into this packet if we can fit it. U8 buffer[MTUBYTES]; buffer[0] = 0; - LLVFile vfile(mVFS, asset_id, asset_type, LLVFile::READ); + LLFileSystem vfile(asset_id, asset_type, LLFileSystem::READ); S32 asset_size = vfile.getSize(); LLAssetRequest *req = new LLAssetRequest(asset_id, asset_type); @@ -174,22 +169,20 @@ void LLViewerAssetStorage::storeAssetData( if (asset_size < 1) { - // This can happen if there's a bug in our code or if the VFS has been corrupted. - LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the VFS, but it's not! " << asset_id << LL_ENDL; - // LLAssetStorage metric: Zero size VFS - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (VFS - can't tell which)" ); + // This can happen if there's a bug in our code or if the cache has been corrupted. + LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the cache, but it's not! " << asset_id << LL_ENDL; delete req; if (callback) { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::VFS_CORRUPT); + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::CACHE_CORRUPT); } return; } else { // LLAssetStorage metric: Successful Request - S32 size = mVFS->getSize(asset_id, asset_type); + S32 size = LLFileSystem::getFileSize(asset_id, asset_type); const char *message = "Added to upload queue"; reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); @@ -203,7 +196,7 @@ void LLViewerAssetStorage::storeAssetData( } } - // Read the data from the VFS if it'll fit in this packet. + // Read the data from the cache if it'll fit in this packet. if (asset_size + 100 < MTUBYTES) { BOOL res = vfile.read(buffer, asset_size); /* Flawfinder: ignore */ @@ -216,14 +209,11 @@ void LLViewerAssetStorage::storeAssetData( } else { - LL_WARNS("AssetStorage") << "Probable corruption in VFS file, aborting store asset data" << LL_ENDL; - - // LLAssetStorage metric: VFS corrupt - bogus size - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, asset_size, MR_VFS_CORRUPTION, __FILE__, __LINE__, "VFS corruption" ); + LL_WARNS("AssetStorage") << "Probable corruption in cache file, aborting store asset data" << LL_ENDL; if (callback) { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::VFS_CORRUPT); + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::CACHE_CORRUPT); } return; } @@ -246,8 +236,7 @@ void LLViewerAssetStorage::storeAssetData( else { LL_WARNS("AssetStorage") << "AssetStorage: attempt to upload non-existent vfile " << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; - // LLAssetStorage metric: Zero size VFS - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (VFS - can't tell which)" ); + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (cache - can't tell which)" ); if (callback) { callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::NONEXISTENT_FILE); @@ -280,7 +269,6 @@ void LLViewerAssetStorage::storeAssetData( if(filename.empty()) { // LLAssetStorage metric: no filename - reportMetric( LLUUID::null, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_VFS_CORRUPTION, __FILE__, __LINE__, "Filename missing" ); LL_ERRS() << "No filename specified" << LL_ENDL; return; } @@ -305,9 +293,10 @@ void LLViewerAssetStorage::storeAssetData( legacy->mUpCallback = callback; legacy->mUserData = user_data; - LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE); - - file.setMaxSize(size); + // Fix broken asset upload + //LLFileSystem file(asset_id, asset_type, LLFileSystem::WRITE); + LLFileSystem file(asset_id, asset_type, LLFileSystem::APPEND); + // const S32 buf_size = 65536; U8 copy_buf[buf_size]; @@ -585,21 +574,20 @@ void LLViewerAssetStorage::assetRequestCoro( // case. LLUUID temp_id; temp_id.generate(); - LLVFile vf(gAssetStorage->mVFS, temp_id, atype, LLVFile::WRITE); - vf.setMaxSize(size); + LLFileSystem vf(temp_id, atype, LLFileSystem::WRITE); req->mBytesFetched = size; if (!vf.write(raw.data(),size)) { // TODO asset-http: handle error LL_WARNS("ViewerAsset") << "Failure in vf.write()" << LL_ENDL; result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::VFS_CORRUPT; + ext_status = LLExtStat::CACHE_CORRUPT; } else if (!vf.rename(uuid, atype)) { LL_WARNS("ViewerAsset") << "rename failed" << LL_ENDL; result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::VFS_CORRUPT; + ext_status = LLExtStat::CACHE_CORRUPT; } else { diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index ef01d179b7..972c89de34 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -30,18 +30,16 @@ #include "llassetstorage.h" #include "llcorehttputil.h" -class LLVFile; +class LLFileSystem; class LLViewerAssetRequest; class LLViewerAssetStorage : public LLAssetStorage { public: - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host); + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs, LLVFS *static_vfs); + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); ~LLViewerAssetStorage(); diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 1ad96660a5..57f1174701 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -45,7 +45,7 @@ #include "llviewerassetupload.h" #include "llappviewer.h" #include "llviewerstats.h" -#include "llvfile.h" +#include "llfilesystem.h" #include "llgesturemgr.h" #include "llpreviewnotecard.h" #include "llpreviewgesture.h" @@ -492,15 +492,16 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() setAssetType(assetType); - // copy this file into the vfs for upload + // copy this file into the cache for upload S32 file_size; LLAPRFile infile; infile.open(filename, LL_APR_RB, NULL, &file_size); if (infile.getFileHandle()) { - LLVFile file(gVFS, getAssetId(), assetType, LLVFile::WRITE); - - file.setMaxSize(file_size); + // Fix broken asset upload + //LLFileSystem file(getAssetId(), assetType, LLFileSystem::WRITE); + LLFileSystem file(getAssetId(), assetType, LLFileSystem::APPEND); + // const S32 buf_size = 65536; U8 copy_buf[buf_size]; @@ -532,7 +533,7 @@ LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType: mContents(buffer), mInvnFinishFn(finish), mTaskFinishFn(nullptr), - mStoredToVFS(false) + mStoredToCache(false) { setItemId(itemId); setAssetType(assetType); @@ -546,7 +547,7 @@ LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLPointerrenameFile(getAssetId(), assetType, newAssetId, assetType); + LLFileSystem::renameFile(getAssetId(), assetType, newAssetId, assetType); } if (mTaskUpload) diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h index d9eacf3167..e56ba7d8f7 100644 --- a/indra/newview/llviewerassetupload.h +++ b/indra/newview/llviewerassetupload.h @@ -197,7 +197,7 @@ private: std::string mContents; invnUploadFinish_f mInvnFinishFn; taskUploadFinish_f mTaskFinishFn; - bool mStoredToVFS; + bool mStoredToCache; }; //------------------------------------------------------------------------- diff --git a/indra/newview/llviewermedia_streamingaudio.cpp b/indra/newview/llviewermedia_streamingaudio.cpp index b76a34a254..ed3cd120e2 100644 --- a/indra/newview/llviewermedia_streamingaudio.cpp +++ b/indra/newview/llviewermedia_streamingaudio.cpp @@ -33,10 +33,8 @@ #include "llviewermedia_streamingaudio.h" #include "llmimetypes.h" -#include "llvfs.h" #include "lldir.h" - LLStreamingAudio_MediaPlugins::LLStreamingAudio_MediaPlugins() : mMediaPlugin(NULL), mGain(1.0) diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 6827e1bc16..5b8c6e5849 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -53,8 +53,6 @@ #include "llviewercontrol.h" // gSavedSettings #include "llviewertexturelist.h" #include "lluictrlfactory.h" -#include "llvfile.h" -#include "llvfs.h" #include "llviewerinventory.h" #include "llviewermenu.h" // gMenuHolder #include "llviewerparcelmgr.h" diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 39f25ae290..270e342ec3 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -44,8 +44,7 @@ #include "llteleportflags.h" #include "lltoastnotifypanel.h" #include "lltransactionflags.h" -#include "llvfile.h" -#include "llvfs.h" +#include "llfilesystem.h" #include "llxfermanager.h" #include "mean_collision_data.h" @@ -8607,16 +8606,15 @@ void process_covenant_reply(LLMessageSystem* msg, void**) } } -void onCovenantLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) +void onCovenantLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) { LL_DEBUGS("Messaging") << "onCovenantLoadComplete()" << LL_ENDL; std::string covenant_text; if(0 == status) { - LLVFile file(vfs, asset_uuid, type, LLVFile::READ); + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); S32 file_length = file.getSize(); diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h index 2d041055ce..acbd64be46 100644 --- a/indra/newview/llviewermessage.h +++ b/indra/newview/llviewermessage.h @@ -50,7 +50,6 @@ class LLInventoryObject; class LLInventoryItem; class LLMeanCollisionData; class LLMessageSystem; -class LLVFS; class LLViewerObject; class LLViewerRegion; @@ -206,8 +205,7 @@ void process_script_dialog(LLMessageSystem* msg, void**); void process_load_url(LLMessageSystem* msg, void**); void process_script_teleport_request(LLMessageSystem* msg, void**); void process_covenant_reply(LLMessageSystem* msg, void**); -void onCovenantLoadComplete(LLVFS *vfs, - const LLUUID& asset_uuid, +void onCovenantLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h index bbbacce8fa..e378c2448a 100644 --- a/indra/newview/llviewerprecompiledheaders.h +++ b/indra/newview/llviewerprecompiledheaders.h @@ -101,7 +101,6 @@ #include "v4math.h" #include "xform.h" -// Library includes from llvfs #include "lldir.h" // Library includes from llmessage project diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index e048947762..2f1eb06ecf 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -33,7 +33,6 @@ #include "llfloaterreg.h" #include "llmemory.h" #include "lltimer.h" -#include "llvfile.h" #include "llappviewer.h" @@ -151,7 +150,6 @@ LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"), VISIBLE_AVATARS("visibleavatars", "Visible Avatars"), SHADER_OBJECTS("shaderobjects", "Object Shaders"), DRAW_DISTANCE("drawdistance", "Draw Distance"), - PENDING_VFS_OPERATIONS("vfspendingoperations"), WINDOW_WIDTH("windowwidth", "Window width"), WINDOW_HEIGHT("windowheight", "Window height"); @@ -389,7 +387,6 @@ void update_statistics() F64Bits layer_bits = gVLManager.getLandBits() + gVLManager.getWindBits() + gVLManager.getCloudBits(); add(LLStatViewer::LAYERS_NETWORK_DATA_RECEIVED, layer_bits); add(LLStatViewer::OBJECT_NETWORK_DATA_RECEIVED, gObjectData); - sample(LLStatViewer::PENDING_VFS_OPERATIONS, LLVFile::getVFSThread()->getPending()); add(LLStatViewer::ASSET_UDP_DATA_RECEIVED, F64Bits(gTransferManager.getTransferBitsIn(LLTCT_ASSET))); gTransferManager.resetTransferBitsIn(LLTCT_ASSET); diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index efcb2fb566..b596c744c5 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -191,7 +191,6 @@ extern LLTrace::SampleStatHandle<> FPS_SAMPLE, VISIBLE_AVATARS, SHADER_OBJECTS, DRAW_DISTANCE, - PENDING_VFS_OPERATIONS, WINDOW_WIDTH, WINDOW_HEIGHT; diff --git a/indra/newview/llviewertexlayer.cpp b/indra/newview/llviewertexlayer.cpp index 6ad460c8ef..ecbc832cd0 100644 --- a/indra/newview/llviewertexlayer.cpp +++ b/indra/newview/llviewertexlayer.cpp @@ -31,8 +31,6 @@ #include "llagent.h" #include "llimagej2c.h" #include "llnotificationsutil.h" -#include "llvfile.h" -#include "llvfs.h" #include "llviewerregion.h" #include "llglslshader.h" #include "llvoavatarself.h" @@ -41,6 +39,7 @@ #include "llviewerassetupload.h" #include "llsdutil.h" +#include "llfilesystem.h" // [Legacy Bake] static const S32 BAKE_UPLOAD_ATTEMPTS = 7; static const F32 BAKE_UPLOAD_RETRY_DELAY = 2.f; // actual delay grows by power of 2 each attempt @@ -661,14 +660,14 @@ void LLViewerTexLayerSetBuffer::doUpload(LLRenderTarget* bound_target) LLTransactionID tid; tid.generate(); const LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - if (LLVFile::writeFile(compressedImage->getData(), compressedImage->getDataSize(), - gVFS, asset_id, LLAssetType::AT_TEXTURE)) + LLFileSystem up_file(asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + if (up_file.write(compressedImage->getData(), compressedImage->getDataSize())) { // Read back the file and validate. BOOL valid = FALSE; LLPointer integrity_test = new LLImageJ2C; S32 file_size = 0; - LLVFile file(gVFS, asset_id, LLAssetType::AT_TEXTURE); + LLFileSystem file(asset_id, LLAssetType::AT_TEXTURE); file_size = file.getSize(); U8* data = integrity_test->allocateData(file_size); std::string strAssetData; @@ -752,8 +751,7 @@ void LLViewerTexLayerSetBuffer::doUpload(LLRenderTarget* bound_target) { // The read back and validate operation failed. Remove the uploaded file. mUploadPending = FALSE; - LLVFile file(gVFS, asset_id, LLAssetType::AT_TEXTURE, LLVFile::WRITE); - file.remove(); + LLFileSystem::removeFile(asset_id, LLAssetType::AT_TEXTURE); LL_INFOS() << "Unable to create baked upload file (reason: corrupted)." << LL_ENDL; } } diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 16611734c2..e9aa0d5f48 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -39,8 +39,6 @@ #include "llimagej2c.h" #include "llimagetga.h" #include "llstl.h" -#include "llvfile.h" -#include "llvfs.h" #include "message.h" #include "lltimer.h" diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index feb6f5a56d..cc9e208957 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -60,7 +60,7 @@ class LLTexturePipelineTester ; typedef void (*loaded_callback_func)( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata ); -class LLVFile; +class LLFileSystem; class LLMessageSystem; class LLViewerMediaImpl ; class LLVOVolume ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index bb85c9054d..a46da8ef0d 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -41,9 +41,7 @@ #include "llsdserialize.h" #include "llsys.h" -#include "llvfs.h" -#include "llvfile.h" -#include "llvfsthread.h" +#include "llfilesystem.h" #include "llxmltree.h" #include "message.h" diff --git a/indra/newview/llviewerwearable.cpp b/indra/newview/llviewerwearable.cpp index a6768dff49..dfca234f28 100644 --- a/indra/newview/llviewerwearable.cpp +++ b/indra/newview/llviewerwearable.cpp @@ -113,7 +113,6 @@ LLWearable::EImportResult LLViewerWearable::importStream( std::istream& input_st // Shouldn't really log the asset id for security reasons, but // we need it in this case. LL_WARNS() << "Bad Wearable asset header: " << mAssetID << LL_ENDL; - //gVFS->dumpMap(); return result; } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index a7fc225458..bf3681804e 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1265,7 +1265,6 @@ void LLVOAvatar::initInstance() //------------------------------------------------------------------------- if (LLCharacter::sInstances.size() == 1) { - LLKeyframeMotion::setVFS(gStaticVFS); registerMotion( ANIM_AGENT_DO_NOT_DISTURB, LLNullMotion::create ); registerMotion( ANIM_AGENT_CROUCH, LLKeyframeStandMotion::create ); registerMotion( ANIM_AGENT_CROUCHWALK, LLKeyframeWalkMotion::create ); diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index 4fa7a062d2..4c68bbdd54 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -223,10 +223,9 @@ public: void updateSculptTexture(); void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;} void sculpt(); - static void rebuildMeshAssetCallback(LLVFS *vfs, - const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); + static void rebuildMeshAssetCallback(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); void updateRelativeXform(bool force_identity = false); /*virtual*/ BOOL updateGeometry(LLDrawable *drawable); diff --git a/indra/newview/skins/default/xui/da/floater_stats.xml b/indra/newview/skins/default/xui/da/floater_stats.xml index fe3fa9626e..d07f9e48ca 100644 --- a/indra/newview/skins/default/xui/da/floater_stats.xml +++ b/indra/newview/skins/default/xui/da/floater_stats.xml @@ -32,7 +32,6 @@ - diff --git a/indra/newview/skins/default/xui/da/strings.xml b/indra/newview/skins/default/xui/da/strings.xml index 18560432fc..fd39fb3e7b 100644 --- a/indra/newview/skins/default/xui/da/strings.xml +++ b/indra/newview/skins/default/xui/da/strings.xml @@ -19,9 +19,6 @@ Initialiserer tekstur cache... - - Initialiserer VFS... - Gendanner... diff --git a/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml index 94eb04554b..fb2a460030 100644 --- a/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/de/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/de/floater_stats.xml b/indra/newview/skins/default/xui/de/floater_stats.xml index 9e4cc6d0fa..fcb4e9f34d 100644 --- a/indra/newview/skins/default/xui/de/floater_stats.xml +++ b/indra/newview/skins/default/xui/de/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index 66f95431c5..2f9df6fae8 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -25,9 +25,6 @@ Textur-Cache wird initialisiert... - - VFS wird initialisiert... - Grafikinitialisierung fehlgeschlagen. Bitte aktualisieren Sie Ihren Grafiktreiber. @@ -86,8 +83,8 @@ Erweitertes Beleuchtungsmodell: [ALMSTATUS] Texturspeicher: Dynamisch ([TEXTUREMEMORYMIN] MB min. / [TEXTUREMEMORYCACHERESERVE]% Cache / [TEXTUREMEMORYGPURESERVE]% VRAM) - - VFS (Cache) Erstellungszeit: [VFS_DATE] + + Disk-Cache: [DISK_CACHE_INFO] HiDPI-Anzeigemodus: [HIDPI] diff --git a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml index bf988a75fa..f72791c9fb 100644 --- a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml @@ -205,12 +205,6 @@ bar_max="1024.f" tick_spacing="128.f" precision="1" - show_bar="false"/> - diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml index bfe970d99b..ca5b944abb 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -244,11 +244,6 @@ decimal_digits="1" show_history="false" setting="DebugStatModeActualOut"/> - diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 0b45a2f64a..32e647e166 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -23,7 +23,6 @@ Clearing cache... Clearing texture cache... Initializing texture cache... - Initializing VFS... Graphics initialization failed. Please update your graphics driver! @@ -78,8 +77,8 @@ Texture memory: [TEXTUREMEMORY] MB ([TEXTUREMEMORYMULTIPLIER]) Texture memory: Dynamic ([TEXTUREMEMORYMIN] MB min / [TEXTUREMEMORYCACHERESERVE]% Cache / [TEXTUREMEMORYGPURESERVE]% VRAM) - -VFS (cache) creation time (UTC): [VFS_DATE] + +Disk cache: [DISK_CACHE_INFO] HiDPI display mode: [HIDPI] diff --git a/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml index f625d5257c..cfc5e524b5 100644 --- a/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/es/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/es/floater_stats.xml b/indra/newview/skins/default/xui/es/floater_stats.xml index 1208f0798f..011306b08c 100644 --- a/indra/newview/skins/default/xui/es/floater_stats.xml +++ b/indra/newview/skins/default/xui/es/floater_stats.xml @@ -36,7 +36,6 @@ - diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 8be3cc5166..489ced7b00 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -27,9 +27,6 @@ Iniciando la caché de texturas... - - Iniciando VFS... - Error de inicialización de gráficos. Actualiza tu controlador de gráficos. diff --git a/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml index 62830054bf..3889b13f0c 100644 --- a/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/fr/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/fr/floater_stats.xml b/indra/newview/skins/default/xui/fr/floater_stats.xml index 9f3ced976b..137086cedd 100644 --- a/indra/newview/skins/default/xui/fr/floater_stats.xml +++ b/indra/newview/skins/default/xui/fr/floater_stats.xml @@ -55,7 +55,6 @@ - diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 71a70298dd..39a35ff473 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -22,9 +22,6 @@ Initialisation du cache des textures... - - Initialisation VFS... - Échec d'initialisation des graphiques. Veuillez mettre votre pilote graphique à jour. diff --git a/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml index 4fd52a07c5..c86a8b115a 100644 --- a/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/it/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/it/floater_stats.xml b/indra/newview/skins/default/xui/it/floater_stats.xml index 4c2d0ef493..c05db6e025 100644 --- a/indra/newview/skins/default/xui/it/floater_stats.xml +++ b/indra/newview/skins/default/xui/it/floater_stats.xml @@ -55,7 +55,6 @@ - diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index c34d0e16da..86a9c6c6a0 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -18,9 +18,6 @@ Inizializzazione della cache texture... - - Inizializzazione VFS... - Inizializzazione grafica non riuscita. Aggiornare il driver della scheda grafica. @@ -72,9 +69,6 @@ Stato illuminazione (Advanced Lighting Model): [ALMSTATUS] Memoria texture (Texture memory): [TEXTUREMEMORY] MB ([TEXTUREMEMORYMULTIPLIER]) - - VFS (cache) creation time (UTC): [VFS_DATE] - Modalità display HiDPI: [HIDPI] diff --git a/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml index f6edce026f..f348ce3c4d 100644 --- a/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/ja/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/ja/floater_stats.xml b/indra/newview/skins/default/xui/ja/floater_stats.xml index 6ddda8b149..006748cb0b 100644 --- a/indra/newview/skins/default/xui/ja/floater_stats.xml +++ b/indra/newview/skins/default/xui/ja/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index df2e36388f..95ac48f0ac 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -22,9 +22,6 @@ テクスチャキャッシュを初期化中です... - - VFS を初期化中です... - グラフィックを初期化できませんでした。グラフィックドライバを更新してください。 diff --git a/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml index e079c97a69..b42cb6327f 100644 --- a/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/pl/floater_scene_load_stats.xml @@ -28,7 +28,6 @@ - diff --git a/indra/newview/skins/default/xui/pl/floater_stats.xml b/indra/newview/skins/default/xui/pl/floater_stats.xml index 695dfb780a..a6914d0d4d 100644 --- a/indra/newview/skins/default/xui/pl/floater_stats.xml +++ b/indra/newview/skins/default/xui/pl/floater_stats.xml @@ -55,7 +55,6 @@ - diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index c2ddb24c68..623c1aac44 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -18,9 +18,6 @@ Inicjowanie bufora danych tekstur... - - Inicjowanie wirtualnego systemu plików... - Nie można zainicjować grafiki. Zaktualizuj sterowniki! @@ -75,8 +72,8 @@ Zaawansowane oświetlenie (Advanced Lighting Model): [ALMSTATUS] Pamięć tekstur (Texture memory): Dynamiczna ([TEXTUREMEMORYMIN] MB min / [TEXTUREMEMORYCACHERESERVE]% Cache / [TEXTUREMEMORYGPURESERVE]% VRAM) - - Czas utworzenia VFS (cache) w UTC: [VFS_DATE] + + Pamięć podręczna dysku (disk cache): [DISK_CACHE_INFO] Tryb obrazu HiDPI: [HIDPI] diff --git a/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml index 027e1ef311..dbaab1d782 100644 --- a/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/pt/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/pt/floater_stats.xml b/indra/newview/skins/default/xui/pt/floater_stats.xml index f41fe17778..3253984268 100644 --- a/indra/newview/skins/default/xui/pt/floater_stats.xml +++ b/indra/newview/skins/default/xui/pt/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index 6912012ef1..10ae9fad15 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -19,9 +19,6 @@ Iniciando cache de texturas... - - Iniciando VFS... - Falha na inicialização dos gráficos. Atualize seu driver gráfico! diff --git a/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml index f14cb9b232..e14ca4ee18 100644 --- a/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/ru/floater_scene_load_stats.xml @@ -32,7 +32,6 @@ - diff --git a/indra/newview/skins/default/xui/ru/floater_stats.xml b/indra/newview/skins/default/xui/ru/floater_stats.xml index 6584b376c1..c98ca14882 100644 --- a/indra/newview/skins/default/xui/ru/floater_stats.xml +++ b/indra/newview/skins/default/xui/ru/floater_stats.xml @@ -35,7 +35,6 @@ - diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index d6023c63dd..b66ec18c51 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -34,9 +34,6 @@ Инициализация кэша текстур... - - Инициализация виртуальной файловой системы (VFS)... - Инициализация графики не удалась. Пожалуйста, обновите драйвер видеокарты! @@ -95,8 +92,8 @@ SLURL: <nolink>[SLURL]</nolink> Память текстур: динамическая ([TEXTUREMEMORYMIN] МБ мин. / [TEXTUREMEMORYCACHERESERVE]% Кэш / [TEXTUREMEMORYGPURESERVE]% VRAM) - - Время создания VFS (кэш) (UTC): [VFS_DATE] + + Кэш диска: [DISK_CACHE_INFO] Режим отображения HiDPI: [HIDPI] diff --git a/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml index ae0a94595d..7d5f4adb02 100644 --- a/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/tr/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/tr/floater_stats.xml b/indra/newview/skins/default/xui/tr/floater_stats.xml index 1ae42ad382..bd36d4916f 100644 --- a/indra/newview/skins/default/xui/tr/floater_stats.xml +++ b/indra/newview/skins/default/xui/tr/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index 0355dc2089..571e7335ac 100644 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -31,9 +31,6 @@ Doku önbelleği başlatılıyor... - - VFS Başlatılıyor... - Grafik başlatma başarılamadı. Lütfen grafik sürücünüzü güncelleştirin! diff --git a/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml index 1a5c20abeb..20344e299f 100644 --- a/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml +++ b/indra/newview/skins/default/xui/zh/floater_scene_load_stats.xml @@ -29,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/zh/floater_stats.xml b/indra/newview/skins/default/xui/zh/floater_stats.xml index f06eb5e78f..e233ece527 100644 --- a/indra/newview/skins/default/xui/zh/floater_stats.xml +++ b/indra/newview/skins/default/xui/zh/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml index ff9e8a74af..8532adb490 100644 --- a/indra/newview/skins/default/xui/zh/strings.xml +++ b/indra/newview/skins/default/xui/zh/strings.xml @@ -31,9 +31,6 @@ 正在初始化材質快取... - - VFS 初始化中... - 顯像初始化失敗。 請更新你的顯像卡驅動程式! diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 8fc854ffc9..e9fd0e3d35 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -87,7 +87,6 @@ class ViewerManifest(LLManifest,FSViewerManifest): self.exclude("logcontrol-dev.xml") self.path("*.ini") self.path("*.xml") - self.path("*.db2") # include the entire shaders directory recursively self.path("shaders") diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index ed32e38fda..95b4d99c0d 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -8,7 +8,7 @@ include(LLCoreHttp) include(LLInventory) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include(Linking) include(Tut) @@ -24,7 +24,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} # Removed -> ${LSCRIPT_INCLUDE_DIRS} ${GOOGLEMOCK_INCLUDE_DIRS} @@ -90,7 +90,7 @@ target_link_libraries(lltest ${LLINVENTORY_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLMATH_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLXML_LIBRARIES} ${LSCRIPT_LIBRARIES} ${LLCOMMON_LIBRARIES} diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt index 2e4446da9e..5a7dea948a 100644 --- a/indra/win_crash_logger/CMakeLists.txt +++ b/indra/win_crash_logger/CMakeLists.txt @@ -8,7 +8,7 @@ include(LLCoreHttp) include(LLCrashLogger) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLWindow) include(LLXML) include(Linking) @@ -23,7 +23,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLWINDOW_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${BREAKPAD_INCLUDE_DIRECTORIES} ) include_directories(SYSTEM @@ -81,7 +81,7 @@ target_link_libraries(windows-crash-logger ${BREAKPAD_EXCEPTION_HANDLER_LIBRARIES} ${LLCRASHLOGGER_LIBRARIES} ${LLWINDOW_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLXML_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLMATH_LIBRARIES}

iUO{A+_>y^r$H<>b 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�sEslb`{_o+p3MRGmyKk7StqW&>O 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