From 925feaaf95b43f2380a4f375ac9b6419d05732f6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 4 Mar 2021 12:06:12 +0200 Subject: [PATCH 01/52] SL-14941 FIXED Cannot upload large images on the Simplified Cache Viewer. --- doc/contributions.txt | 1 + indra/newview/llviewerassetstorage.cpp | 2 +- indra/newview/llviewerassetupload.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/contributions.txt b/doc/contributions.txt index bbdfaf655d..120c737d7a 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -226,6 +226,7 @@ Ansariel Hiller SL-13364 SL-13858 SL-13697 + SL-14941 Aralara Rajal Arare Chantilly CHUIBUG-191 diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 5b76d57196..0f2901406a 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -291,7 +291,7 @@ void LLViewerAssetStorage::storeAssetData( legacy->mUpCallback = callback; legacy->mUserData = user_data; - 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]; diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 1923e7d6ff..7b5229d312 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -473,7 +473,7 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() infile.open(filename, LL_APR_RB, NULL, &file_size); if (infile.getFileHandle()) { - LLFileSystem file(getAssetId(), assetType, LLFileSystem::WRITE); + LLFileSystem file(getAssetId(), assetType, LLFileSystem::APPEND); const S32 buf_size = 65536; U8 copy_buf[buf_size]; From 87908bab6d7a0283e7195566dd99926a4c8401b1 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 4 Mar 2021 12:23:57 +0200 Subject: [PATCH 02/52] SL-14939 Fixed the log spam --- doc/contributions.txt | 1 + indra/llfilesystem/llfilesystem.cpp | 6 +++--- indra/llfilesystem/llfilesystem.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/contributions.txt b/doc/contributions.txt index 120c737d7a..877d38c2a1 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -226,6 +226,7 @@ Ansariel Hiller SL-13364 SL-13858 SL-13697 + SL-14939 SL-14941 Aralara Rajal Arare Chantilly diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index 64e0b9f193..053b52014e 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -72,14 +72,14 @@ bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType fil } // static -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); - LLFile::remove(filename.c_str()); + LLFile::remove(filename.c_str(), suppress_error); return true; } @@ -98,7 +98,7 @@ bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::ETyp 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); + LLFileSystem::removeFile(new_file_id, new_file_type, ENOENT); if (LLFile::rename(old_filename, new_filename) != 0) { diff --git a/indra/llfilesystem/llfilesystem.h b/indra/llfilesystem/llfilesystem.h index 89bfff5798..d934a408c2 100644 --- a/indra/llfilesystem/llfilesystem.h +++ b/indra/llfilesystem/llfilesystem.h @@ -54,7 +54,7 @@ class LLFileSystem 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 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); From ada8ad1bc21665631f030c3194a567ea6f6a2a72 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 4 Mar 2021 12:49:48 +0200 Subject: [PATCH 03/52] SL-14940 Fixed ignoring custom cache path --- doc/contributions.txt | 1 + indra/newview/llappviewer.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/contributions.txt b/doc/contributions.txt index 877d38c2a1..bf95989f19 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -227,6 +227,7 @@ Ansariel Hiller SL-13858 SL-13697 SL-14939 + SL-14940 SL-14941 Aralara Rajal Arare Chantilly diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ea0b950e62..6ede9ec0e7 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4132,8 +4132,6 @@ bool LLAppViewer::initCache() 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,6 +4179,9 @@ bool LLAppViewer::initCache() gSavedSettings.setString("CacheLocationTopFolder", ""); } + 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) { if (mPurgeCache) From 168d177197bd7558bbe0ca13d01c984ad8638da7 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 9 Mar 2021 14:39:51 -0800 Subject: [PATCH 04/52] This set of changes reverts the merge with master (git revert c83e740) and results in a version of the DRTVWR-519 that matches what was presemt before it was deployed as a release viewer *plus* 3 small fixes from Maxim (See commits). This branch can now be used for additional fixes before eventually being used to release D-519 as normal --- 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/{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 | 283 +++ indra/llfilesystem/llfilesystem.h | 78 + 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 | 2220 ----------------- indra/llvfs/llvfs.h | 183 -- 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/app_settings/settings.xml | 81 +- indra/newview/app_settings/static_data.db2 | Bin 576578 -> 0 bytes indra/newview/app_settings/static_index.db2 | Bin 9894 -> 0 bytes indra/newview/llappviewer.cpp | 294 +-- 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 | 19 +- 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, 1455 insertions(+), 4789 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/README.md b/README.md index e4078770f3..729c0ae368 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,4 @@ 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 53e5d7b6a5..4b39bfe332 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}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 8aea50e02b..f4071793d5 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -66,11 +66,10 @@ 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 a17e37cd32..352dfc0641 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 - 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 1cec660eb0..d7706e73b2 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 e5039141de..a4600069ce 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 e7db84f6ab..ff0aa6e76e 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(); } } 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 1d447f32ae..d35f249973 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" @@ -684,13 +684,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; @@ -1018,13 +1014,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 "<getNumJointMotions(); jm++) { if (!mJointStates[jm]->getJoint()) diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index cde38c8091..fe9de30f0a 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -39,14 +39,13 @@ #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; //----------------------------------------------------------------------------- @@ -515,7 +514,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; } @@ -559,13 +558,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; @@ -2296,10 +2290,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; @@ -2331,7 +2324,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 f876b8ee4a..6e8b9efaf7 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -194,23 +194,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) { @@ -229,11 +270,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 da23b46b7b..d70a1e0fb0 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 87dc1b9795..3bc4ee844e 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..053b52014e --- /dev/null +++ b/indra/llfilesystem/llfilesystem.cpp @@ -0,0 +1,283 @@ +/** + * @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, 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); + + 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. + 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); + + 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..d934a408c2 --- /dev/null +++ b/indra/llfilesystem/llfilesystem.h @@ -0,0 +1,78 @@ +/** + * @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, 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 aed8943439..350a8eb120 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -2219,20 +2219,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 e829788c91..04975940aa 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( @@ -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} ${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 2f99ca069e..f0a1dfe940 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -9,7 +9,7 @@ include(LLCommon) include(LLCoreHttp) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLAddBuildTest) include(Python) include(Tut) @@ -23,7 +23,7 @@ include_directories( ${LLCOREHTTP_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIR} ) @@ -209,7 +209,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -227,7 +227,7 @@ target_link_libraries( llmessage ${CURL_LIBRARIES} ${LLCOMMON_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${JSONCPP_LIBRARIES} ${OPENSSL_LIBRARIES} @@ -257,7 +257,7 @@ if (LL_TESTS) if (LINUX) set(test_libs ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${CURL_LIBRARIES} ${NGHTTP2_LIBRARIES} @@ -273,7 +273,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 0eae6d9826..7031f1aa8c 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 "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 cce618487b..7401e6130a 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 ) @@ -283,7 +283,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 617056d94d..0000000000 --- a/indra/llvfs/llvfs.cpp +++ /dev/null @@ -1,2220 +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); - } - 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 deleted file mode 100644 index 42feafe20b..0000000000 --- a/indra/llvfs/llvfs.h +++ /dev/null @@ -1,183 +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; -}; - -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 8bfb23ed64..70eb99c86c 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} ${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 3439951e80..82e6cda193 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) @@ -84,7 +84,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} @@ -2016,7 +2016,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLRENDER_LIBRARIES} ${FREETYPE_LIBRARIES} ${LLUI_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLWINDOW_LIBRARIES} ${LLXML_LIBRARIES} ${LLMATH_LIBRARIES} @@ -2492,7 +2492,7 @@ if (LL_TESTS) set(test_libs ${LLMESSAGE_LIBRARIES} ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${GOOGLEMOCK_LIBRARIES} @@ -2507,7 +2507,7 @@ if (LL_TESTS) set(test_libs ${WINDOWS_LIBRARIES} - ${LLVFS_LIBRARIES} + ${LLFILESYSTEM_LIBRARIES} ${LLMATH_LIBRARIES} ${LLCOMMON_LIBRARIES} ${LLMESSAGE_LIBRARIES} diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c0166f158e..393a38fb9c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1362,6 +1362,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 @@ -2835,17 +2868,6 @@ Value -1 - DebugStatModeVFSPendingOps - - Comment - Mode of stat in Statistics floater - Persist - 1 - Type - S32 - Value - -1 - DebugStatModeTimeDialation Comment @@ -3638,17 +3660,6 @@ Value 4 - DumpVFSCaches - - Comment - Dump VFS caches on startup. - Persist - 1 - Type - Boolean - Value - 0 - DynamicCameraStrength Comment @@ -11580,7 +11591,7 @@ Boolean Value 0 - + NearbyListShowMap Comment @@ -14258,28 +14269,6 @@ 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 @@ -16621,7 +16610,7 @@ Type Boolean Value - 1 + 1 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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

iUO{A+_>y^r$H<>b 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�sEslb`{_o+p3MRGmyKk7StqW&>O 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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/llappviewer.cpp b/indra/newview/llappviewer.cpp index f518704e06..6ede9ec0e7 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" #include "llweb.h" #include "llfloatertexturefetchdebugger.h" @@ -115,8 +116,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" @@ -340,9 +339,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 @@ -431,12 +427,6 @@ 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; @@ -971,10 +961,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; @@ -1369,7 +1355,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"); @@ -1603,10 +1588,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); @@ -1614,7 +1595,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 ; @@ -1631,7 +1612,6 @@ bool LLAppViewer::doFrame() } if(!total_io_pending) //pause file threads if nothing to process. { - LLVFSThread::sLocal->pause(); LLLFSThread::sLocal->pause(); } @@ -1693,12 +1673,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; @@ -1786,7 +1765,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 @@ -1853,8 +1832,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 @@ -1938,15 +1917,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 @@ -2033,7 +2003,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) @@ -2108,28 +2077,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(); @@ -2211,7 +2163,6 @@ bool LLAppViewer::initThreads() LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); - LLVFSThread::initClass(enable_threads && false); LLLFSThread::initClass(enable_threads && false); // Image decoding @@ -3213,10 +3164,6 @@ 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 @@ -3308,6 +3255,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(); + return info; } @@ -3957,7 +3907,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 @@ -4146,39 +4096,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() { @@ -4205,6 +4122,17 @@ 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"); + bool texture_cache_mismatch = false; if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion()) { @@ -4251,10 +4179,24 @@ bool LLAppViewer::initCache() gSavedSettings.setString("CacheLocationTopFolder", ""); } - if (mPurgeCache && !read_only) + 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(); + } } LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); @@ -4264,172 +4206,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 5332fe2deb..902b94d495 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; @@ -82,7 +83,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. @@ -381,12 +382,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 9b1c0d1f8b..84ffb3551d 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -502,7 +502,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 3aaaaf52f5..5d010a6f1e 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" @@ -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 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 { @@ -134,8 +134,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)); @@ -283,11 +283,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()); diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index adb854875a..a9bac345b5 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(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 2fc496a144..04ba4416d7 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -136,7 +136,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 957b2e1e8e..9813156bf2 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" @@ -202,7 +201,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); @@ -210,7 +211,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 131d9b077b..687d820a18 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" @@ -997,10 +997,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 b9c03f66a3..481a7dab66 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -59,6 +59,7 @@ #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 526214a617..f99d0e6150 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, vfs, and VO cache on next startup + void onClickClearCache(); // Clear viewer texture cache, file 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 ec1909d02a..0375c15467 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" @@ -2229,10 +2229,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; @@ -2240,7 +2239,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 75d0c3ea5c..c34dbb62e8 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; class LLPanelRegionDebugInfo; @@ -357,8 +356,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 7bfba2a6d7..5d0e2bbc55 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" @@ -899,12 +898,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 bd403f68d7..1aeb727172 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 "llvfile.h" +#include "llfilesystem.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 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 950a6cfaef..9f2119281d 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 @@ -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 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))) @@ -1038,10 +1038,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; @@ -1056,7 +1055,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); @@ -1159,8 +1158,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) { @@ -1172,7 +1170,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); @@ -1180,7 +1178,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 b4236c406b..2966ca1f10 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 @@ -118,7 +118,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, @@ -127,7 +126,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 3e8731dfe6..8e5bdc0225 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" @@ -294,8 +294,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 @@ -1336,8 +1334,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]; @@ -1370,7 +1368,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; constructUrl(mesh_id, &http_url); @@ -1432,8 +1430,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]; @@ -1467,7 +1465,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; constructUrl(mesh_id, &http_url); @@ -1529,8 +1527,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; @@ -1563,7 +1561,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; constructUrl(mesh_id, &http_url); @@ -1634,8 +1632,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(); @@ -1649,7 +1647,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; } } @@ -1713,8 +1711,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]; @@ -1749,7 +1747,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; constructUrl(mesh_id, &http_url); @@ -3208,7 +3206,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; @@ -3247,31 +3245,16 @@ 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)) + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::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(); - if (remaining > 0) - { - file.write(block, remaining); - } } } else @@ -3323,8 +3306,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 VFS for caching - LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3387,8 +3370,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 VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3435,8 +3418,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 VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; @@ -3482,8 +3465,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 VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to cache for caching + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 81e49cb1d8..441264d42f 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -48,7 +48,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 272e7ae351..90f6d23a61 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 d5775042c1..071fc31d27 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 70ce275734..4318a55704 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" @@ -841,10 +841,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; @@ -853,7 +852,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); @@ -1138,10 +1137,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 1b60610668..a7bb5c8236 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" @@ -327,8 +327,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) { @@ -339,7 +338,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(); @@ -446,7 +445,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 @@ -454,7 +453,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()) { @@ -479,7 +478,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(); @@ -558,14 +557,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 d9c14815c1..063a01ca47 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -83,8 +83,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 5e81fa6402..379bc9871c 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 "llvfile.h" +#include "llfilesystem.h" #include "llagent.h" #include "llmenugl.h" @@ -1685,8 +1685,11 @@ 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, - [](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); })); @@ -1696,8 +1699,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; @@ -1707,7 +1710,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); @@ -1734,6 +1737,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; } else @@ -1970,7 +1974,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) { @@ -1984,9 +1988,10 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, { if( LL_ERR_NOERR == status ) { - 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); } else { @@ -2010,9 +2015,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); @@ -2166,6 +2171,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"]) @@ -2236,12 +2242,14 @@ 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](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 3cf22a0e6e..18c10ab365 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -48,7 +48,6 @@ struct LLEntryAndEdCore; class LLMenuBarGL; class LLFloaterScriptSearch; class LLKeywordToken; -class LLVFS; class LLViewerInventoryItem; class LLScriptEdContainer; class LLFloaterGotoLine; @@ -143,6 +142,9 @@ 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(); @@ -188,6 +190,7 @@ private: LLUUID mAssociatedExperience; BOOL mScriptRemoved; BOOL mSaveDialogShown; + LLUUID mAssetID; LLScriptEdContainer* mContainer; // parent view @@ -234,7 +237,7 @@ protected: static void onLoad(void* userdata); static void onSave(void* userdata, BOOL close_after_save); - 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); @@ -295,13 +298,13 @@ private: static void onLoad(void* userdata); static void onSave(void* userdata, BOOL close_after_save); - 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 8369def968..8134187c21 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" @@ -50,8 +51,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 @@ -1006,7 +1005,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 17777c3ceb..f281b30a8e 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" @@ -268,7 +267,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(); @@ -580,7 +579,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) @@ -588,7 +587,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"); @@ -1004,13 +1003,6 @@ 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; @@ -2589,7 +2581,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 a9f19dc32d..d4fc6f3de2 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 { @@ -1551,7 +1552,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: @@ -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,30 +1578,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 { @@ -1724,7 +1713,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) { @@ -1820,21 +1809,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) @@ -1844,10 +1838,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 54f80a2995..0f2901406a 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" @@ -104,10 +103,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), @@ -117,10 +114,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), @@ -157,13 +152,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); @@ -172,22 +167,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 ); @@ -201,7 +194,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 */ @@ -214,14 +207,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; } @@ -244,8 +234,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); @@ -278,7 +267,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; } @@ -303,9 +291,7 @@ void LLViewerAssetStorage::storeAssetData( legacy->mUpCallback = callback; legacy->mUserData = user_data; - LLVFile file(mVFS, asset_id, asset_type, LLVFile::WRITE); - - file.setMaxSize(size); + LLFileSystem file(asset_id, asset_type, LLFileSystem::APPEND); const S32 buf_size = 65536; U8 copy_buf[buf_size]; @@ -539,21 +525,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 f2e887a678..7b5229d312 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" @@ -467,7 +467,7 @@ 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); @@ -505,7 +505,7 @@ LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType: mContents(buffer), mInvnFinishFn(finish), mTaskFinishFn(nullptr), - mStoredToVFS(false) + mStoredToCache(false) { setItemId(itemId); setAssetType(assetType); @@ -519,7 +519,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 3ccf3070ab..d3e24aece5 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 4b231c7067..e59a263adc 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 458fc3b13d..6ef452bd92 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" @@ -6844,16 +6843,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 78829a6a56..1e5a69ae13 100644 --- a/indra/newview/llviewermessage.h +++ b/indra/newview/llviewermessage.h @@ -47,7 +47,6 @@ class LLInventoryObject; class LLInventoryItem; class LLMeanCollisionData; class LLMessageSystem; -class LLVFS; class LLViewerObject; class LLViewerRegion; @@ -189,8 +188,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 05f88b0a75..35fb4de5a9 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" @@ -145,7 +144,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"); @@ -383,7 +381,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 04870e0c26..72ce336664 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -186,7 +186,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 c501dd0035..4c2fbcf837 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" diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 20a22ba45e..d69ab1b110 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 69568cc825..07997e02a5 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 LLVFile; +class LLFileSystem; class LLMessageSystem; class LLViewerMediaImpl ; class LLVOVolume ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 561319ca5d..38fccba169 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 9c4dfd1ca2..9a4607bb60 100644 --- a/indra/newview/llviewerwearable.cpp +++ b/indra/newview/llviewerwearable.cpp @@ -107,7 +107,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 f69b9b3861..414a1da4fe 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1140,7 +1140,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 ce400a3498..73eeec813c 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -218,10 +218,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 ec6ba4800d..814305c1bc 100644 --- a/indra/newview/skins/default/xui/da/strings.xml +++ b/indra/newview/skins/default/xui/da/strings.xml @@ -22,9 +22,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 dff462a594..a3749f1cfa 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 4e6f56cd94..1838c2081a 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 43327c132d..f021e03dc7 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -31,9 +31,6 @@ Textur-Cache wird initialisiert... - - VFS wird initialisiert... - Grafikinitialisierung fehlgeschlagen. Bitte aktualisieren Sie Ihren Grafiktreiber. @@ -73,7 +70,6 @@ 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 62cce3a1e3..35d4385487 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,12 +206,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 e4f735740b..f9d44b2c69 100644 --- a/indra/newview/skins/default/xui/en/floater_stats.xml +++ b/indra/newview/skins/default/xui/en/floater_stats.xml @@ -198,10 +198,6 @@ 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 f9f12e7f5c..619c869a3d 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -17,7 +17,6 @@ Loading [APP_NAME]... Clearing cache... Initializing texture cache... - Initializing VFS... Graphics initialization failed. Please update your graphics driver! @@ -56,9 +55,9 @@ LOD factor: [LOD_FACTOR] Render quality: [RENDER_QUALITY] Advanced Lighting Model: [GPU_SHADERS] Texture memory: [TEXTURE_MEMORY]MB -VFS (cache) creation time: [VFS_TIME] +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 d1c5e867db..0aec786f25 100644 --- a/indra/newview/skins/default/xui/es/floater_stats.xml +++ b/indra/newview/skins/default/xui/es/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index f5e7d0bf4e..ebb4ceaa7e 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -22,9 +22,6 @@ Iniciando la caché de las texturas... - - Iniciando VFS... - Error de inicialización de gráficos. Actualiza tu controlador de gráficos. @@ -65,7 +62,6 @@ 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 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 fae17e3608..d0f7f42939 100644 --- a/indra/newview/skins/default/xui/fr/floater_stats.xml +++ b/indra/newview/skins/default/xui/fr/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index f26eac545a..9fde703d6c 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -31,9 +31,6 @@ Initialisation du cache des textures... - - Initialisation VFS... - Échec d'initialisation des graphiques. Veuillez mettre votre pilote graphique à jour. @@ -74,7 +71,6 @@ 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 ca18812eb7..efceb05298 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 7ef13aa37f..6434c3b66b 100644 --- a/indra/newview/skins/default/xui/it/floater_stats.xml +++ b/indra/newview/skins/default/xui/it/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index f0466cea81..3049828f46 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -28,9 +28,6 @@ Inizializzazione della cache texture... - - Inizializzazione VFS... - Inizializzazione grafica non riuscita. Aggiorna il driver della scheda grafica! @@ -71,7 +68,6 @@ 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 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 3bc343639b..1da0e5ebc9 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 52d6fb0c2b..dcd6e65d34 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -31,9 +31,6 @@ テクスチャキャッシュを初期化中です... - - VFS を初期化中です... - グラフィックを初期化できませんでした。グラフィックドライバを更新してください。 @@ -74,7 +71,6 @@ 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 6fdc7e19f6..8f5d0c5c70 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,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/pl/floater_stats.xml b/indra/newview/skins/default/xui/pl/floater_stats.xml index 5dd7d19bab..21e37717c2 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 91fea234d2..cf033df3c9 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -15,9 +15,6 @@ 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 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 ee982b5b22..c72a41fd3a 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -22,9 +22,6 @@ Iniciando cache de texturas... - - Iniciando VFS... - Falha na inicialização dos gráficos. Atualize seu driver gráfico! @@ -65,7 +62,6 @@ 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 a101e62627..c4f432023c 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,7 +29,6 @@ - diff --git a/indra/newview/skins/default/xui/ru/floater_stats.xml b/indra/newview/skins/default/xui/ru/floater_stats.xml index 10e9f5a7f4..a7d26029c2 100644 --- a/indra/newview/skins/default/xui/ru/floater_stats.xml +++ b/indra/newview/skins/default/xui/ru/floater_stats.xml @@ -56,7 +56,6 @@ - diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index e9592a0476..43a87b2b18 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -31,9 +31,6 @@ Инициализация кэша текстур... - - Инициализация виртуальной файловой системы... - Ошибка инициализации графики. Обновите графический драйвер! @@ -74,7 +71,6 @@ 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 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 56fad978f5..982de76a5b 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! @@ -74,7 +71,6 @@ 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 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 e6c61a5d94..3221cde3b7 100644 --- a/indra/newview/skins/default/xui/zh/strings.xml +++ b/indra/newview/skins/default/xui/zh/strings.xml @@ -31,9 +31,6 @@ 正在初始化材質快取... - - VFS 初始化中... - 顯像初始化失敗。 請更新你的顯像卡驅動程式! @@ -74,7 +71,6 @@ 呈像品質:[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 6d231040f7..8eee583a48 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -69,7 +69,6 @@ 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 87536e146b..b3bff3611a 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -8,12 +8,11 @@ include(LLCoreHttp) include(LLInventory) include(LLMath) include(LLMessage) -include(LLVFS) +include(LLFileSystem) include(LLXML) include(Linking) include(Tut) include(LLAddBuildTest) - include(GoogleMock) include_directories( @@ -23,7 +22,7 @@ include_directories( ${LLMATH_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} - ${LLVFS_INCLUDE_DIRS} + ${LLFILESYSTEM_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} ${LSCRIPT_INCLUDE_DIRS} ${GOOGLEMOCK_INCLUDE_DIRS} @@ -89,7 +88,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 86aa655f03..1b187d624a 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 @@ -76,7 +76,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} From d7518c7b4f5eca731d0ea143bab34b279bd4ee13 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 9 Mar 2021 18:33:35 -0800 Subject: [PATCH 05/52] Ansariel kindly offered their patch to help mitigate this round of file system issues - taken from https://vcs.firestormviewer.org/phoenix-firestorm/changeset/104a8600946be01e2de44d10ad069ba854272d1f --- indra/llfilesystem/lldiskcache.cpp | 4 ++-- indra/llfilesystem/llfilesystem.cpp | 27 +++++++++++++++++++++++ indra/newview/llmeshrepository.cpp | 34 ++++++++++++++++++++++++----- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index c9f7684b5a..61eb654693 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -192,8 +192,8 @@ const std::string LLDiskCache::metaDataToFilepath(const std::string id, 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 + 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, diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index 053b52014e..da44e8d98c 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -200,9 +200,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 8e5bdc0225..c2404a7e67 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -3248,13 +3248,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 @@ -3307,7 +3323,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; @@ -3371,7 +3389,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; @@ -3419,7 +3439,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; @@ -3466,7 +3488,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 ad2edc1d8336c745fc897197e2e85debf3654c17 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 9 Mar 2021 18:35:30 -0800 Subject: [PATCH 06/52] Remove debugging tags --- indra/llfilesystem/lldiskcache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 61eb654693..c9f7684b5a 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -192,8 +192,8 @@ const std::string LLDiskCache::metaDataToFilepath(const std::string id, 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 + //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, From 5f5e52b5219d6f8f9ad284c90e66199a29531371 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 10 Mar 2021 17:03:56 -0800 Subject: [PATCH 07/52] Fix for SL-14985 - bump the texture cache version so the texture cache is purged on startup after running an older version of the Viewer --- indra/newview/llappviewer.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 6ede9ec0e7..a1e9ac2f87 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4099,8 +4099,11 @@ void LLAppViewer::migrateCacheDirectory() //static U32 LLAppViewer::getTextureCacheVersion() { - //viewer texture cache version, change if the texture cache format changes. - const U32 TEXTURE_CACHE_VERSION = 8; + // Viewer texture cache version, change if the texture cache format changes. + // 2021-03-10 Bumping up by one to help obviate texture cache issues with + // Simple Cache Viewer - see SL-14985 for more information + //const U32 TEXTURE_CACHE_VERSION = 8; + const U32 TEXTURE_CACHE_VERSION = 9; return TEXTURE_CACHE_VERSION ; } From d039580d0736004af642f4620b5f40e1f762a24a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Mon, 22 Mar 2021 23:45:25 +0200 Subject: [PATCH 08/52] SL-14993 Crash accessing mInvBindMatrix --- indra/llprimitive/llmodel.cpp | 30 +++++++++++++++++----------- indra/newview/llskinningutil.cpp | 34 +++----------------------------- indra/newview/llskinningutil.h | 1 - 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index a2d9b4cd9b..702a1b5238 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -434,7 +434,7 @@ void LLModel::generateNormals(F32 angle_cutoff) if (vol_face.mNumIndices > 65535) { - LL_WARNS() << "Too many vertices for normal generation to work." << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Too many vertices for normal generation to work." << LL_ENDL; continue; } @@ -1100,7 +1100,7 @@ bool LLModel::loadModel(std::istream& is) { if (!LLSDSerialize::fromBinary(header, is, 1024*1024*1024)) { - LL_WARNS() << "Mesh header parse error. Not a valid mesh asset!" << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Mesh header parse error. Not a valid mesh asset!" << LL_ENDL; return false; } } @@ -1132,7 +1132,7 @@ bool LLModel::loadModel(std::istream& is) if (header[lod_name[lod]]["offset"].asInteger() == -1 || header[lod_name[lod]]["size"].asInteger() == 0 ) { //cannot load requested LOD - LL_WARNS() << "LoD data is invalid!" << LL_ENDL; + LL_WARNS("MESHSKININFO") << "LoD data is invalid!" << LL_ENDL; return false; } @@ -1195,7 +1195,7 @@ bool LLModel::loadModel(std::istream& is) } else { - LL_WARNS() << "unpackVolumeFaces failed!" << LL_ENDL; + LL_WARNS("MESHSKININFO") << "unpackVolumeFaces failed!" << LL_ENDL; } return false; @@ -1223,7 +1223,7 @@ bool LLModel::isMaterialListSubset( LLModel* ref ) if (!foundRef) { - LL_INFOS() << "Could not find material " << mMaterialList[src] << " in reference model " << ref->mLabel << LL_ENDL; + LL_INFOS("MESHSKININFO") << "Could not find material " << mMaterialList[src] << " in reference model " << ref->mLabel << LL_ENDL; return false; } } @@ -1259,7 +1259,7 @@ bool LLModel::matchMaterialOrder(LLModel* ref, int& refFaceCnt, int& modelFaceCn bool isASubset = isMaterialListSubset( ref ); if ( !isASubset ) { - LL_INFOS()<<"Material of model is not a subset of reference."<= face.mNumVertices) { - LL_WARNS() << "Face has invalid index." << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Face has invalid index." << LL_ENDL; return false; } } if (face.mNumIndices % 3 != 0 || face.mNumIndices == 0) { - LL_WARNS() << "Face has invalid number of indices." << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Face has invalid number of indices." << LL_ENDL; return false; } @@ -1879,7 +1887,7 @@ bool validate_model(const LLModel* mdl) { if (mdl->getNumVolumeFaces() == 0) { - LL_WARNS() << "Model has no faces!" << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Model has no faces!" << LL_ENDL; return false; } @@ -1887,13 +1895,13 @@ bool validate_model(const LLModel* mdl) { if (mdl->getVolumeFace(i).mNumVertices == 0) { - LL_WARNS() << "Face has no vertices." << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Face has no vertices." << LL_ENDL; return false; } if (mdl->getVolumeFace(i).mNumIndices == 0) { - LL_WARNS() << "Face has no indices." << LL_ENDL; + LL_WARNS("MESHSKININFO") << "Face has no indices." << LL_ENDL; return false; } diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index 1fb63c7444..f325315933 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -309,7 +309,8 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a if (vol_face.mJointRiggingInfoTab.needsUpdate()) { S32 num_verts = vol_face.mNumVertices; - if (num_verts>0 && vol_face.mWeights && (skin->mJointNames.size()>0)) + S32 num_joints = skin->mJointNames.size(); + if (num_verts > 0 && vol_face.mWeights && num_joints > 0) { initJointNums(const_cast(skin), avatar); if (vol_face.mJointRiggingInfoTab.size()==0) @@ -343,7 +344,7 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a for (U32 k=0; k<4; ++k) { S32 joint_index = idx[k]; - if (wght[k] > 0.0f) + if (wght[k] > 0.0f && num_joints > joint_index) { S32 joint_num = skin->mJointNums[joint_index]; if (joint_num >= 0 && joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS) @@ -394,35 +395,6 @@ void LLSkinningUtil::updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *a } } -void LLSkinningUtil::updateRiggingInfo_(LLMeshSkinInfo* skin, LLVOAvatar *avatar, S32 num_verts, LLVector4a* weights, LLVector4a* positions, U8* joint_indices, LLJointRiggingInfoTab &rig_info_tab) -{ - LL_RECORD_BLOCK_TIME(FTM_FACE_RIGGING_INFO); - for (S32 i=0; i < num_verts; i++) - { - LLVector4a& pos = positions[i]; - LLVector4a& wght = weights[i]; - for (U32 k=0; k<4; ++k) - { - S32 joint_num = skin->mJointNums[joint_indices[k]]; - llassert(joint_num >= 0 && joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS); - { - rig_info_tab[joint_num].setIsRiggedTo(true); - LLMatrix4a bind_shape; - bind_shape.loadu(skin->mBindShapeMatrix); - LLMatrix4a inv_bind; - inv_bind.loadu(skin->mInvBindMatrix[joint_indices[k]]); - LLMatrix4a mat; - matMul(bind_shape, inv_bind, mat); - LLVector4a pos_joint_space; - mat.affineTransform(pos, pos_joint_space); - pos_joint_space.mul(wght[k]); - LLVector4a *extents = rig_info_tab[joint_num].getRiggedExtents(); - update_min_max(extents[0], extents[1], pos_joint_space); - } - } - } -} - // This is used for extracting rotation from a bind shape matrix that // already has scales baked in LLQuaternion LLSkinningUtil::getUnscaledQuaternion(const LLMatrix4& mat4) diff --git a/indra/newview/llskinningutil.h b/indra/newview/llskinningutil.h index 549aa6a29f..efe7c85997 100644 --- a/indra/newview/llskinningutil.h +++ b/indra/newview/llskinningutil.h @@ -67,7 +67,6 @@ namespace LLSkinningUtil void initJointNums(LLMeshSkinInfo* skin, LLVOAvatar *avatar); void updateRiggingInfo(const LLMeshSkinInfo* skin, LLVOAvatar *avatar, LLVolumeFace& vol_face); - void updateRiggingInfo_(LLMeshSkinInfo* skin, LLVOAvatar *avatar, S32 num_verts, LLVector4a* weights, LLVector4a* positions, U8* joint_indices, LLJointRiggingInfoTab &rig_info_tab); LLQuaternion getUnscaledQuaternion(const LLMatrix4& mat4); }; From 9f95e619c9802bd405db43afa3fbf6b5ad82799d Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 26 Mar 2021 11:47:41 -0700 Subject: [PATCH 09/52] Add some logging for QA purposes (based on INFO/Mesh tag) to display when the mesh header and the mesh body is retrieved from the simulator or the local cache. --- indra/newview/llmeshrepository.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index c2404a7e67..a93b3b4bfd 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1647,6 +1647,10 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + // Found mesh in cache return true; } @@ -1658,8 +1662,13 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c std::string http_url; constructUrl(mesh_params.getSculptID(), &http_url); + if (!http_url.empty()) { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits //within the first 4KB //NOTE -- this will break of headers ever exceed 4KB @@ -1740,6 +1749,11 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) { delete[] buffer; + + std::string mid; + mesh_id.toString(mid); + LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + return true; } } @@ -1750,9 +1764,13 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, //reading from cache failed for whatever reason, fetch from sim std::string http_url; constructUrl(mesh_id, &http_url); - + if (!http_url.empty()) { + std::string mid; + mesh_id.toString(mid); + LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) From bf4256fff0dc13dcb83c0fb1ff22cbd9618575fc Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 5 Apr 2021 15:35:19 +0300 Subject: [PATCH 10/52] SL-15070 FIXED Moving the cache does not remove the "cache" folder from the old location --- indra/newview/llappviewer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 934a3313be..76c38ea024 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4158,6 +4158,7 @@ bool LLAppViewer::initCache() LL_INFOS("AppCache") << "Cache location changed, cache needs purging" << LL_ENDL; gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation")); purgeCache(); // purge old cache + gDirUtilp->deleteDirAndContents(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name)); gSavedSettings.setString("CacheLocation", new_cache_location); gSavedSettings.setString("CacheLocationTopFolder", gDirUtilp->getBaseFileName(new_cache_location)); } From 0df59ff9e6e50237d36a976731e81c6e37e73933 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Mon, 12 Apr 2021 15:03:26 -0700 Subject: [PATCH 11/52] Fix for SL-15105 (Mesh/Cache messages are spamming the log) - I don't know why the log spam is present when the 'Mesh' tag is missing from logcontrol.xml for LL_INFOS(..) messages so I changed it to LL_DEBUGS(..) --- indra/newview/llmeshrepository.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index a93b3b4bfd..157689f282 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1649,7 +1649,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c { std::string mid; mesh_params.getSculptID().toString(mid); - LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; // Found mesh in cache return true; @@ -1667,7 +1667,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c { std::string mid; mesh_params.getSculptID().toString(mid); - LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits //within the first 4KB @@ -1752,7 +1752,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, std::string mid; mesh_id.toString(mid); - LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; return true; } @@ -1769,7 +1769,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, { std::string mid; mesh_id.toString(mid); - LL_INFOS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); From 8633ba9c8bd8e24f9b6d8a74ed7bc4d50072a728 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Tue, 11 May 2021 20:27:07 +0200 Subject: [PATCH 12/52] BUG-230697: Do not crash viewer during cache cleanup --- indra/llfilesystem/lldiskcache.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index c9f7684b5a..75255ef230 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -102,7 +102,12 @@ void LLDiskCache::purge() if (file_size_total > mMaxSizeBytes) { action = "DELETE:"; - boost::filesystem::remove(entry.second.second); + boost::system::error_code ec; + boost::filesystem::remove(entry.second.second, ec); + if (ec.failed()) + { + LL_WARNS() << "Failed to delete cache file " << entry.second.second << ": " << ec.message() << LL_ENDL; + } } else { @@ -284,7 +289,12 @@ void LLDiskCache::clearCache() { if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) { - boost::filesystem::remove(entry); + boost::system::error_code ec; + boost::filesystem::remove(entry, ec); + if (ec.failed()) + { + LL_WARNS() << "Failed to delete cache file " << entry << ": " << ec.message() << LL_ENDL; + } } } } From 3898609ae24e7787d6f6ae71c2424b43102326bf Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 11 May 2021 12:58:05 -0700 Subject: [PATCH 13/52] Fix for SL-15226 Simple cache viewer: Integer overflow in cache size - via FS:Ansariel --- indra/llfilesystem/lldiskcache.cpp | 2 +- indra/llfilesystem/lldiskcache.h | 2 +- indra/newview/llappviewer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index c9f7684b5a..241c46dc73 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -40,7 +40,7 @@ #include "lldiskcache.h" LLDiskCache::LLDiskCache(const std::string cache_dir, - const int max_size_bytes, + const uintmax_t max_size_bytes, const bool enable_cache_debug_info) : mCacheDir(cache_dir), mMaxSizeBytes(max_size_bytes), diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h index 997884da31..c19714434a 100644 --- a/indra/llfilesystem/lldiskcache.h +++ b/indra/llfilesystem/lldiskcache.h @@ -86,7 +86,7 @@ class LLDiskCache : * The maximum size of the cache in bytes - Based on the * setting at 'CacheSize' and 'DiskCachePercentOfTotal' */ - const int max_size_bytes, + const uintmax_t max_size_bytes, /** * A flag that enables extra cache debugging so that * if there are bugs, we can ask uses to enable this diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index a3d20a8e2f..c519e55fc3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4141,7 +4141,7 @@ bool LLAppViewer::initCache() 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 uintmax_t disk_cache_bytes = disk_cache_mb * 1024 * 1024; const bool enable_cache_debug_info = gSavedSettings.getBOOL("EnableDiskCacheDebugInfo"); bool texture_cache_mismatch = false; From 0e253cb9098cb58b2f3528860a5fd9f00ec5af96 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 12 May 2021 10:45:23 +0200 Subject: [PATCH 14/52] BUG-230673: Trim asset disk cache regularly --- indra/llfilesystem/lldiskcache.cpp | 22 ++++++++++++++++++++++ indra/llfilesystem/lldiskcache.h | 11 +++++++++++ indra/newview/app_settings/settings.xml | 2 +- indra/newview/llappviewer.cpp | 6 ++++++ indra/newview/llappviewer.h | 3 +++ 5 files changed, 43 insertions(+), 1 deletion(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 02678864b9..68423cc4f8 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -335,3 +335,25 @@ uintmax_t LLDiskCache::dirFileSize(const std::string dir) return total_file_size; } + +LLPurgeDiskCacheThread::LLPurgeDiskCacheThread() : + LLThread("PurgeDiskCacheThread", nullptr) +{ +} + +void LLPurgeDiskCacheThread::run() +{ + constexpr F64 CHECK_INTERVAL = 60; + mTimer.setTimerExpirySec(CHECK_INTERVAL); + mTimer.start(); + + do + { + if (mTimer.checkExpirationAndReset(CHECK_INTERVAL)) + { + LLDiskCache::instance().purge(); + } + + ms_sleep(100); + } while (!isQuitting()); +} \ No newline at end of file diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h index c19714434a..867a80f332 100644 --- a/indra/llfilesystem/lldiskcache.h +++ b/indra/llfilesystem/lldiskcache.h @@ -180,4 +180,15 @@ class LLDiskCache : bool mEnableCacheDebugInfo; }; +class LLPurgeDiskCacheThread : public LLThread +{ +public: + LLPurgeDiskCacheThread(); + +protected: + void run() override; + +private: + LLTimer mTimer; +}; #endif // _LLDISKCACHE diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index cbd33e9244..0cae9cd9cc 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1362,7 +1362,7 @@ Value 23 - EnableCacheDebugInfo + EnableDiskCacheDebugInfo Comment When set, display additional cache debugging information diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index c519e55fc3..fd094b12d7 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -655,6 +655,7 @@ LLAppViewer* LLAppViewer::sInstance = NULL; LLTextureCache* LLAppViewer::sTextureCache = NULL; LLImageDecodeThread* LLAppViewer::sImageDecodeThread = NULL; LLTextureFetch* LLAppViewer::sTextureFetch = NULL; +LLPurgeDiskCacheThread* LLAppViewer::sPurgeDiskCacheThread = NULL; std::string getRuntime() { @@ -2032,6 +2033,7 @@ bool LLAppViewer::cleanup() sTextureFetch->shutdown(); sTextureCache->shutdown(); sImageDecodeThread->shutdown(); + sPurgeDiskCacheThread->shutdown(); sTextureFetch->shutDownTextureCacheThread() ; sTextureFetch->shutDownImageDecodeThread() ; @@ -2054,6 +2056,8 @@ bool LLAppViewer::cleanup() sImageDecodeThread = NULL; delete mFastTimerLogThread; mFastTimerLogThread = NULL; + delete sPurgeDiskCacheThread; + sPurgeDiskCacheThread = NULL; if (LLFastTimerView::sAnalyzePerformance) { @@ -2174,6 +2178,7 @@ bool LLAppViewer::initThreads() sImageDecodeThread, enable_threads && true, app_metrics_qa_mode); + LLAppViewer::sPurgeDiskCacheThread = new LLPurgeDiskCacheThread(); if (LLTrace::BlockTimer::sLog || LLTrace::BlockTimer::sMetricLog) { @@ -4210,6 +4215,7 @@ bool LLAppViewer::initCache() LLDiskCache::getInstance()->purge(); } } + LLAppViewer::getPurgeDiskCacheThread()->start(); LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 5e24398caa..00d6943047 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -58,6 +58,7 @@ class LLImageDecodeThread; class LLTextureFetch; class LLWatchdogTimeout; class LLViewerJoystick; +class LLPurgeDiskCacheThread; extern LLTrace::BlockTimerStatHandle FTM_FRAME; @@ -117,6 +118,7 @@ public: static LLTextureCache* getTextureCache() { return sTextureCache; } static LLImageDecodeThread* getImageDecodeThread() { return sImageDecodeThread; } static LLTextureFetch* getTextureFetch() { return sTextureFetch; } + static LLPurgeDiskCacheThread* getPurgeDiskCacheThread() { return sPurgeDiskCacheThread; } static U32 getTextureCacheVersion() ; static U32 getObjectCacheVersion() ; @@ -284,6 +286,7 @@ private: static LLTextureCache* sTextureCache; static LLImageDecodeThread* sImageDecodeThread; static LLTextureFetch* sTextureFetch; + static LLPurgeDiskCacheThread* sPurgeDiskCacheThread; S32 mNumSessions; From 89cf988aaf0de402641cd945e7e9ad5292bc78d6 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 17 May 2021 09:49:32 +0200 Subject: [PATCH 15/52] BUG-230673: Add warning that LLDiskCache::purge() is also called from outside the main thread --- indra/llfilesystem/lldiskcache.cpp | 2 ++ indra/llfilesystem/lldiskcache.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 68423cc4f8..17906ce369 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -51,6 +51,8 @@ LLDiskCache::LLDiskCache(const std::string cache_dir, LLFile::mkdir(cache_dir); } +// WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must +// NOT touch any LLDiskCache data without introducing and locking a mutex! void LLDiskCache::purge() { if (mEnableCacheDebugInfo) diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h index 867a80f332..7c5b798f7e 100644 --- a/indra/llfilesystem/lldiskcache.h +++ b/indra/llfilesystem/lldiskcache.h @@ -118,6 +118,9 @@ class LLDiskCache : /** * Purge the oldest items in the cache so that the combined size of all files * is no bigger than mMaxSizeBytes. + * + * WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must + * NOT touch any LLDiskCache data without introducing and locking a mutex! */ void purge(); From 87faf258911f5d23416500ff632050ce05b30e3e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 May 2021 10:24:27 -0400 Subject: [PATCH 16/52] SL-15200: Explain why purge() is called on another thread. Also add Ansariel's explanation for why interaction through the filesystem itself should be safe. --- indra/llfilesystem/lldiskcache.cpp | 28 +++++++++++++++++++++++++++- indra/llfilesystem/lldiskcache.h | 4 ++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 17906ce369..c0f10ac620 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -53,6 +53,32 @@ LLDiskCache::LLDiskCache(const std::string cache_dir, // WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must // NOT touch any LLDiskCache data without introducing and locking a mutex! + +// Interaction through the filesystem itself should be safe. Let’s say thread +// A is accessing the cache file for reading/writing and thread B is trimming +// the cache. Let’s also assume using llifstream to open a file and +// boost::filesystem::remove are not atomic (which will be pretty much the +// case). + +// Now, A is trying to open the file using llifstream ctor. It does some +// checks if the file exists and whatever else it might be doing, but has not +// issued the call to the OS to actually open the file yet. Now B tries to +// delete the file: If the file has been already marked as in use by the OS, +// deleting the file will fail and B will continue with the next file. A can +// safely continue opening the file. If the file has not yet been marked as in +// use, B will delete the file. Now A actually wants to open it, operation +// will fail, subsequent check via llifstream.is_open will fail, asset will +// have to be re-requested. (Assuming here the viewer will actually handle +// this situation properly, that can also happen if there is a file containing +// garbage.) + +// Other situation: B is trimming the cache and A wants to read a file that is +// about to get deleted. boost::filesystem::remove does whatever it is doing +// before actually deleting the file. If A opens the file before the file is +// actually gone, the OS call from B to delete the file will fail since the OS +// will prevent this. B continues with the next file. If the file is already +// gone before A finally gets to open it, this operation will fail and the +// asset will have to be re-requested. void LLDiskCache::purge() { if (mEnableCacheDebugInfo) @@ -358,4 +384,4 @@ void LLPurgeDiskCacheThread::run() ms_sleep(100); } while (!isQuitting()); -} \ No newline at end of file +} diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h index 7c5b798f7e..268fe92bcc 100644 --- a/indra/llfilesystem/lldiskcache.h +++ b/indra/llfilesystem/lldiskcache.h @@ -121,6 +121,10 @@ class LLDiskCache : * * WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must * NOT touch any LLDiskCache data without introducing and locking a mutex! + * + * Purging the disk cache involves nontrivial work on the viewer's + * filesystem. If called on the main thread, this causes a noticeable + * freeze. */ void purge(); From d313d7021ff551f2a5ef7ec54ff2b3234d96017b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 May 2021 13:37:13 -0400 Subject: [PATCH 17/52] SL-15200: Add LLApp::sleep(duration) methods. Two sleep() methods: one accepting F32Milliseconds, or in general any LLUnits time class; the other accepting any std::chrono::duration. The significant thing about each of these sleep() methods, as opposed to any freestanding sleep() function, is that it only sleeps until the app starts shutdown. Moreover, it returns true if it slept for the whole specified duration, false if it woke for app shutdown. This is accomplished by making LLApp::sStatus be an LLScalarCond instead of a plain EAppStatus enum, and by making setStatus() call set_all() each time the value changes. Then each new sleep() method can call wait_for_unequal(duration, APP_STATUS_RUNNING). Introducing llcond.h into llapp.h triggered an #include circularity because llthread.h #included llapp.h even though it didn't reference anything from it. Removed. This, in turn, necessitated adding #include "llapp.h" to several .cpp files that reference LLApp but had been depending on other header files to drag in llapp.h. --- indra/llcommon/llapp.cpp | 20 +++++++++---- indra/llcommon/llapp.h | 38 ++++++++++++++++++++++-- indra/llcommon/llthread.h | 1 - indra/llcommon/lluuid.cpp | 1 + indra/llcrashlogger/llcrashlogger.cpp | 2 +- indra/llmessage/message.cpp | 1 + indra/llplugin/llpluginprocessparent.cpp | 1 + 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index a90b294550..34f6ba140a 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -113,7 +113,8 @@ BOOL LLApp::sDisableCrashlogger = FALSE; BOOL LLApp::sLogInSignal = FALSE; // static -LLApp::EAppStatus LLApp::sStatus = LLApp::APP_STATUS_STOPPED; // Keeps track of application status +// Keeps track of application status +LLScalarCond LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; LLAppErrorHandler LLApp::sErrorHandler = NULL; BOOL LLApp::sErrorThreadRunning = FALSE; @@ -579,7 +580,8 @@ static std::map statusDesc // static void LLApp::setStatus(EAppStatus status) { - sStatus = status; + // notify everyone waiting on sStatus any time its value changes + sStatus.set_all(status); // This can also happen very late in the application lifecycle -- don't // resurrect a deleted LLSingleton @@ -609,6 +611,12 @@ void LLApp::setError() setStatus(APP_STATUS_ERROR); } +// static +bool LLApp::sleep(F32Milliseconds duration) +{ + return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); +} + void LLApp::setMiniDumpDir(const std::string &path) { if (path.empty()) @@ -668,28 +676,28 @@ void LLApp::setStopped() // static bool LLApp::isStopped() { - return (APP_STATUS_STOPPED == sStatus); + return (APP_STATUS_STOPPED == sStatus.get()); } // static bool LLApp::isRunning() { - return (APP_STATUS_RUNNING == sStatus); + return (APP_STATUS_RUNNING == sStatus.get()); } // static bool LLApp::isError() { - return (APP_STATUS_ERROR == sStatus); + return (APP_STATUS_ERROR == sStatus.get()); } // static bool LLApp::isQuitting() { - return (APP_STATUS_QUITTING == sStatus); + return (APP_STATUS_QUITTING == sStatus.get()); } // static diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 245c73e3a2..e95929b865 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -28,9 +28,11 @@ #define LL_LLAPP_H #include +#include "llcond.h" #include "llrun.h" #include "llsd.h" #include +#include // Forward declarations class LLErrorThread; class LLLiveFile; @@ -211,6 +213,36 @@ public: static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) static int getPid(); + // + // Sleep for specified time while still running + // + // For use by a coroutine or thread that performs some maintenance on a + // periodic basis. (See also LLEventTimer.) This method supports the + // pattern of an "infinite" loop that sleeps for some time, performs some + // action, then sleeps again. The trouble with literally sleeping a worker + // thread is that it could potentially sleep right through attempted + // application shutdown. This method avoids that by returning false as + // soon as the application status changes away from APP_STATUS_RUNNING + // (isRunning()). + // + // sleep() returns true if it sleeps undisturbed for the entire specified + // duration. The idea is that you can code 'while sleep(duration) ...', + // which will break the loop once shutdown begins. + // + // Since any time-based LLUnit should be implicitly convertible to + // F32Milliseconds, accept that specific type as a proxy. + static bool sleep(F32Milliseconds duration); + // Allow any duration defined in terms of . + // One can imagine a wonderfully general bidirectional conversion system + // between any type derived from LLUnits::LLUnit and + // any std::chrono::duration -- but that doesn't yet exist. + template + bool sleep(const std::chrono::duration& duration) + { + // wait_for_unequal() has the opposite bool return convention + return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); + } + /** @name Error handling methods */ //@{ /** @@ -241,8 +273,8 @@ public: // Return the Google Breakpad minidump filename after a crash. char *getMiniDumpFilename() { return mMinidumpPath; } - std::string* getStaticDebugFile() { return &mStaticDebugFileName; } - std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } + std::string* getStaticDebugFile() { return &mStaticDebugFileName; } + std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } // Write out a Google Breakpad minidump file. void writeMiniDump(); @@ -266,7 +298,7 @@ public: protected: static void setStatus(EAppStatus status); // Use this to change the application status. - static EAppStatus sStatus; // Reflects current application status + static LLScalarCond sStatus; // Reflects current application status static BOOL sErrorThreadRunning; // Set while the error thread is running static BOOL sDisableCrashlogger; // Let the OS handle crashes for us. std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 5cd0731f6c..50202631e7 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -27,7 +27,6 @@ #ifndef LL_LLTHREAD_H #define LL_LLTHREAD_H -#include "llapp.h" #include "llapr.h" #include "boost/intrusive_ptr.hpp" #include "llrefcount.h" diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index b05630c6b5..b4879c2489 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -33,6 +33,7 @@ #include #endif +#include "llapp.h" #include "lldefs.h" #include "llerror.h" diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp index 62fcdaf545..24bf91b595 100644 --- a/indra/llcrashlogger/llcrashlogger.cpp +++ b/indra/llcrashlogger/llcrashlogger.cpp @@ -595,7 +595,7 @@ bool LLCrashLogger::init() #if LL_WINDOWS Sleep(1000); #else - sleep(1); + ::sleep(1); #endif locked = mKeyMaster.checkMaster(); } diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index da62bb12e8..19146c64f4 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -46,6 +46,7 @@ #include "apr_poll.h" // linden library headers +#include "llapp.h" #include "indra_constants.h" #include "lldir.h" #include "llerror.h" diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 7d18bae947..a89561fce0 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -28,6 +28,7 @@ #include "linden_common.h" +#include "llapp.h" #include "llpluginprocessparent.h" #include "llpluginmessagepipe.h" #include "llpluginmessageclasses.h" From b3708ac238d51eaf808cb77a4493e518c1593e33 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 17 May 2021 15:10:06 -0400 Subject: [PATCH 18/52] SL-15200: Use new LLApp::sleep() in LLPurgeDiskCacheThread::run(). --- indra/llfilesystem/lldiskcache.cpp | 16 +++++----------- indra/llfilesystem/lldiskcache.h | 3 --- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index c0f10ac620..6b0bbaeb48 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -31,6 +31,7 @@ */ #include "linden_common.h" +#include "llapp.h" #include "llassettype.h" #include "lldir.h" #include @@ -371,17 +372,10 @@ LLPurgeDiskCacheThread::LLPurgeDiskCacheThread() : void LLPurgeDiskCacheThread::run() { - constexpr F64 CHECK_INTERVAL = 60; - mTimer.setTimerExpirySec(CHECK_INTERVAL); - mTimer.start(); + constexpr long CHECK_INTERVAL = 60; - do + while (LLApp::instance()->sleep(std::chrono::seconds(CHECK_INTERVAL))) { - if (mTimer.checkExpirationAndReset(CHECK_INTERVAL)) - { - LLDiskCache::instance().purge(); - } - - ms_sleep(100); - } while (!isQuitting()); + LLDiskCache::instance().purge(); + } } diff --git a/indra/llfilesystem/lldiskcache.h b/indra/llfilesystem/lldiskcache.h index 268fe92bcc..1cbd2c58aa 100644 --- a/indra/llfilesystem/lldiskcache.h +++ b/indra/llfilesystem/lldiskcache.h @@ -194,8 +194,5 @@ public: protected: void run() override; - -private: - LLTimer mTimer; }; #endif // _LLDISKCACHE From ac8640d338997020ca0650001ff004e1103ac5cb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 18 May 2021 09:51:45 -0400 Subject: [PATCH 19/52] SL-15200: LLPurgeDiskCacheThread's CHECK_INTERVAL is secs. --- indra/llfilesystem/lldiskcache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 6b0bbaeb48..905351886f 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -372,9 +372,9 @@ LLPurgeDiskCacheThread::LLPurgeDiskCacheThread() : void LLPurgeDiskCacheThread::run() { - constexpr long CHECK_INTERVAL = 60; + constexpr std::chrono::seconds CHECK_INTERVAL{60}; - while (LLApp::instance()->sleep(std::chrono::seconds(CHECK_INTERVAL))) + while (LLApp::instance()->sleep(CHECK_INTERVAL)) { LLDiskCache::instance().purge(); } From 4c558e85bd90d77696e9201b8996272176d7adc5 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Thu, 10 Jun 2021 01:09:31 +0200 Subject: [PATCH 20/52] Fix more crashes in disk cache due to boost error handling --- indra/llfilesystem/lldiskcache.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index 905351886f..f4d20d0e61 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -253,9 +253,15 @@ void LLDiskCache::updateFileAccessTime(const std::string file_path) // current time const std::time_t cur_time = std::time(nullptr); + boost::system::error_code ec; #if LL_WINDOWS // file last write time - const std::time_t last_write_time = boost::filesystem::last_write_time(utf8str_to_utf16str(file_path)); + const std::time_t last_write_time = boost::filesystem::last_write_time(utf8str_to_utf16str(file_path), ec); + if (ec.failed()) + { + LL_WARNS() << "Failed to read last write time for cache file " << file_path << ": " << ec.message() << LL_ENDL; + return; + } // delta between cur time and last time the file was written const std::time_t delta_time = cur_time - last_write_time; @@ -264,11 +270,16 @@ void LLDiskCache::updateFileAccessTime(const std::string file_path) // before the last one if (delta_time > time_threshold) { - boost::filesystem::last_write_time(utf8str_to_utf16str(file_path), cur_time); + boost::filesystem::last_write_time(utf8str_to_utf16str(file_path), cur_time, ec); } #else // file last write time - const std::time_t last_write_time = boost::filesystem::last_write_time(file_path); + const std::time_t last_write_time = boost::filesystem::last_write_time(file_path, ec); + if (ec.failed()) + { + LL_WARNS() << "Failed to read last write time for cache file " << file_path << ": " << ec.message() << LL_ENDL; + return; + } // delta between cur time and last time the file was written const std::time_t delta_time = cur_time - last_write_time; @@ -277,9 +288,14 @@ void LLDiskCache::updateFileAccessTime(const std::string file_path) // before the last one if (delta_time > time_threshold) { - boost::filesystem::last_write_time(file_path, cur_time); + boost::filesystem::last_write_time(file_path, cur_time, ec); } #endif + + if (ec.failed()) + { + LL_WARNS() << "Failed to update last write time for cache file " << file_path << ": " << ec.message() << LL_ENDL; + } } const std::string LLDiskCache::getCacheInfo() From e7cbd00d64b4056a8b2ea452d7d33f46ea0a3646 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Thu, 10 Jun 2021 16:51:23 -0700 Subject: [PATCH 21/52] Fix for SL-15382 Update contributions text file in Viewer --- doc/contributions.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/contributions.txt b/doc/contributions.txt index ccc9600d48..276d37836d 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -230,6 +230,9 @@ Ansariel Hiller SL-14940 SL-14941 SL-3136 + SL-15200 + SL-15226 + SL-15227 Aralara Rajal Arare Chantilly CHUIBUG-191 From d0f2a2c2008006b57f181e8dfa5e20940687a941 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 11 Jun 2021 11:01:07 -0700 Subject: [PATCH 22/52] Fix for SL-15389 -- Pull in the patch to add the Akamai cert fix specified in SL-15370 --- indra/newview/llsechandler_basic.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index 737ef30ada..19db020a31 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -915,11 +915,19 @@ void _validateCert(int validation_policy, } if (validation_policy & VALIDATION_POLICY_SSL_KU) { + // This stanza of code was changed 2021-06-09 as per details in SL-15370. + // Brief summary: a renewed certificate from Akamai only contains the + // 'Digital Signature' field and not the 'Key Encipherment' one. This code + // used to look for both and throw an exception at startup (ignored) and + // (for example) when buying L$ in the Viewer (fails with a UI message + // and an entry in the Viewer log). This modified code removes the second + // check for the 'Key Encipherment' field. If Akamai can provide a + // replacement certificate that has both fields, then this modified code + // will not be required. if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && - (!(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], - LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) || !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], - LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT))))) + LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) + ) { LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); } From 564a7acb321c54ad5f9fc6f5242efbe4c7638dbc Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 11 Jun 2021 22:50:39 +0200 Subject: [PATCH 23/52] Change all remaining boost::filesystem methods to their non-throwing overloads --- indra/llfilesystem/lldiskcache.cpp | 35 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index f4d20d0e61..dd8916dccb 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -87,6 +87,7 @@ void LLDiskCache::purge() LL_INFOS() << "Total dir size before purge is " << dirFileSize(mCacheDir) << LL_ENDL; } + boost::system::error_code ec; auto start_time = std::chrono::high_resolution_clock::now(); typedef std::pair> file_info_t; @@ -97,17 +98,25 @@ void LLDiskCache::purge() #else std::string cache_path(mCacheDir); #endif - if (boost::filesystem::is_directory(cache_path)) + if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) { for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) { - if (boost::filesystem::is_regular_file(entry)) + if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) { - uintmax_t file_size = boost::filesystem::file_size(entry); + uintmax_t file_size = boost::filesystem::file_size(entry, ec); + if (ec.failed()) + { + continue; + } const std::string file_path = entry.path().string(); - const std::time_t file_time = boost::filesystem::last_write_time(entry); + const std::time_t file_time = boost::filesystem::last_write_time(entry, ec); + if (ec.failed()) + { + continue; + } file_info.push_back(file_info_t(file_time, { file_size, file_path })); } @@ -131,7 +140,6 @@ void LLDiskCache::purge() if (file_size_total > mMaxSizeBytes) { action = "DELETE:"; - boost::system::error_code ec; boost::filesystem::remove(entry.second.second, ec); if (ec.failed()) { @@ -321,20 +329,20 @@ void LLDiskCache::clearCache() * the component files but it's called infrequently so it's * likely just fine */ + boost::system::error_code ec; #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)) + if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) { for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) { - if (boost::filesystem::is_regular_file(entry)) + if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) { - boost::system::error_code ec; boost::filesystem::remove(entry, ec); if (ec.failed()) { @@ -359,20 +367,25 @@ uintmax_t LLDiskCache::dirFileSize(const std::string dir) * so if performance is ever an issue, optimizing this or removing it altogether, * is an easy win. */ + boost::system::error_code ec; #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)) + if (boost::filesystem::is_directory(dir_path, ec) && !ec.failed()) { for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path), {})) { - if (boost::filesystem::is_regular_file(entry)) + if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { if (entry.path().string().find(mCacheFilenamePrefix) != std::string::npos) { - total_file_size += boost::filesystem::file_size(entry); + uintmax_t file_size = boost::filesystem::file_size(entry, ec); + if (!ec.failed()) + { + total_file_size += file_size; + } } } } From fedae88be7b7174956f549194b13883ff2cb4161 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 11 Jun 2021 22:59:55 +0200 Subject: [PATCH 24/52] boost::filesystem::directory_iterator uses throw-behavior by default as well --- indra/llfilesystem/lldiskcache.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index dd8916dccb..ee43a599f7 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -100,7 +100,7 @@ void LLDiskCache::purge() #endif if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path, ec), {})) { if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { @@ -337,7 +337,7 @@ void LLDiskCache::clearCache() #endif if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed()) { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path), {})) + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(cache_path, ec), {})) { if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { @@ -375,7 +375,7 @@ uintmax_t LLDiskCache::dirFileSize(const std::string dir) #endif if (boost::filesystem::is_directory(dir_path, ec) && !ec.failed()) { - for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path), {})) + for (auto& entry : boost::make_iterator_range(boost::filesystem::directory_iterator(dir_path, ec), {})) { if (boost::filesystem::is_regular_file(entry, ec) && !ec.failed()) { From 6baaef4ee20b62b310ea738e416feeb417ed1792 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Mon, 14 Jun 2021 11:10:22 -0700 Subject: [PATCH 25/52] Add in the JIRA (SL-15398) describing the contribution from FS:Ansariel to the contributions.txt file --- doc/contributions.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/contributions.txt b/doc/contributions.txt index 276d37836d..a29d4e6137 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -233,6 +233,7 @@ Ansariel Hiller SL-15200 SL-15226 SL-15227 + SL-15398 Aralara Rajal Arare Chantilly CHUIBUG-191 From e28c1b46e9944f0215a13cab8ee7dded88d7fc90 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Tue, 20 Jul 2021 15:03:22 -0700 Subject: [PATCH 26/52] Speculative fix for SL-15547: Viewer hung while looking for a file in the cache - since I am unable to repro, this might be enough --- indra/llfilesystem/llfilesystem.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index da44e8d98c..b029723e11 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -165,11 +165,14 @@ BOOL LLFileSystem::read(U8* buffer, S32 bytes) } } - // 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); + if (success == TRUE) + { + // 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; } From 484aa963af598a96eca6d9a1715ff1d8ed7ef7b4 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 21 Jul 2021 09:09:02 -0700 Subject: [PATCH 27/52] Additional speculative fix for SL-15547: Viewer hung while looking for a file in the cache. I am still unable to reproduce this problem, although others have so I am trying a more aggressive approach for them to test. I tried this as part of last speculative fix but saw what I thought were scary side effects in the log/console. Turns out, those are there in the release viewer so probably unrelated to this change. --- indra/llfilesystem/llfilesystem.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index b029723e11..af5e8bf8b7 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -164,6 +164,10 @@ BOOL LLFileSystem::read(U8* buffer, S32 bytes) success = FALSE; } } + else + { + success = FALSE; + } if (success == TRUE) { From 0c3b78105d49185f37eff60d9c167d17e6072fc8 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 21 Jul 2021 17:42:08 -0700 Subject: [PATCH 28/52] Another tweak for 'SL-15547: Viewer hung while looking for a file in cache' - this time based on Henri's suggestion in this discussion: https://bitbucket.org/lindenlab/viewer/commits/e28c1b46e9944f0215a13cab8ee7dded88d7fc90#comment-10537114 --- indra/llfilesystem/llfilesystem.cpp | 41 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index af5e8bf8b7..4c836c8838 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -48,6 +48,28 @@ LLFileSystem::LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_ mPosition = 0; mBytesRead = 0; mMode = mode; + + // This block of code was originally called in the read() method but after comments here: + // https://bitbucket.org/lindenlab/viewer/commits/e28c1b46e9944f0215a13cab8ee7dded88d7fc90#comment-10537114 + // we decided to follow Henri's suggestion and move the code to update the last access time here. + if (mode == LLFileSystem::READ) + { + // build the filename (TODO: we do this in a few places - perhaps we should factor into a single function) + std::string id; + mFileID.toString(id); + const std::string extra_info = ""; + const std::string filename = LLDiskCache::getInstance()->metaDataToFilepath(id, mFileType, extra_info); + + // update the last access time for the file if it exists - 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 + bool exists = gDirUtilp->fileExists(filename); + if (exists) + { + LLDiskCache::getInstance()->updateFileAccessTime(filename); + } + } } LLFileSystem::~LLFileSystem() @@ -133,7 +155,7 @@ S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType fi BOOL LLFileSystem::read(U8* buffer, S32 bytes) { - BOOL success = TRUE; + BOOL success = FALSE; std::string id; mFileID.toString(id); @@ -159,24 +181,11 @@ BOOL LLFileSystem::read(U8* buffer, S32 bytes) file.close(); mPosition += mBytesRead; - if (!mBytesRead) + if (mBytesRead) { - success = FALSE; + success = TRUE; } } - else - { - success = FALSE; - } - - if (success == TRUE) - { - // 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; } From 0c89e67eb5bf2d82596d0c697831787cbbdf1e7f Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 20 Aug 2021 08:28:48 -0700 Subject: [PATCH 29/52] DRTVWR-534: Batch of modifications to 360 capture project after moving from internal repo to public one. --- .gitignore | 1 + autobuild.xml | 156 +++ indra/cmake/CubemapToEquirectangularJS.cmake | 5 + indra/cmake/JPEGEncoderBasic.cmake | 5 + indra/cmake/ThreeJS.cmake | 8 + indra/llplugin/llpluginclassmedia.cpp | 22 + indra/llplugin/llpluginclassmedia.h | 4 + indra/media_plugins/cef/media_plugin_cef.cpp | 41 +- indra/newview/CMakeLists.txt | 17 +- indra/newview/app_settings/commands.xml | 11 + indra/newview/app_settings/settings.xml | 129 ++- indra/newview/llfloater360capture.cpp | 904 ++++++++++++++++++ indra/newview/llfloater360capture.h | 97 ++ indra/newview/llfloatersnapshot.cpp | 14 + indra/newview/llfloatersnapshot.h | 1 + indra/newview/llviewerfloaterreg.cpp | 2 + indra/newview/llviewermedia.cpp | 36 +- indra/newview/llviewermedia.h | 1 + indra/newview/llviewermenu.cpp | 49 +- indra/newview/llviewermessage.cpp | 1 + indra/newview/llviewerobject.cpp | 1 + indra/newview/llviewerregion.cpp | 2 + indra/newview/llviewerwindow.cpp | 77 ++ indra/newview/llviewerwindow.h | 5 +- .../html/common/equirectangular/default.html | 22 + .../html/common/equirectangular/eqr_gen.html | 149 +++ .../skins/default/textures/textures.xml | 3 +- .../textures/toolbar_icons/360_capture.png | Bin 0 -> 793 bytes .../default/xui/en/floater_360capture.xml | 124 +++ .../skins/default/xui/en/floater_snapshot.xml | 22 +- .../skins/default/xui/en/floater_toybox.xml | 12 +- .../skins/default/xui/en/menu_viewer.xml | 17 + .../newview/skins/default/xui/en/strings.xml | 4 + indra/newview/viewer_manifest.py | 18 +- 34 files changed, 1892 insertions(+), 68 deletions(-) create mode 100644 indra/cmake/CubemapToEquirectangularJS.cmake create mode 100644 indra/cmake/JPEGEncoderBasic.cmake create mode 100644 indra/cmake/ThreeJS.cmake create mode 100644 indra/newview/llfloater360capture.cpp create mode 100644 indra/newview/llfloater360capture.h create mode 100644 indra/newview/skins/default/html/common/equirectangular/default.html create mode 100644 indra/newview/skins/default/html/common/equirectangular/eqr_gen.html create mode 100644 indra/newview/skins/default/textures/toolbar_icons/360_capture.png create mode 100644 indra/newview/skins/default/xui/en/floater_360capture.xml diff --git a/.gitignore b/.gitignore index 4af34870cf..355c39de70 100755 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ indra/newview/search_history.txt indra/newview/teleport_history.txt indra/newview/typed_locations.txt indra/newview/vivox-runtime +indra/newview/skins/default/html/common/equirectangular/js indra/server-linux-* indra/temp indra/test/linden_file.dat diff --git a/autobuild.xml b/autobuild.xml index 4768bd25c6..d44dc2ac7c 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -367,6 +367,58 @@ version 2.3.545362 + cubemaptoequirectangular + + copyright + Copyright (c) 2017 Jaume Sanchez Elias, http://www.clicktorelease.com + license + MIT + license_file + LICENSES/CUBEMAPTOEQUIRECTANGULAR_LICENSE.txt + name + cubemaptoequirectangular + platforms + + darwin64 + + archive + + hash + e6da43ea831b2416a5a8b388a511e70b + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85446/792080/cubemaptoequirectangular-1.1.0-darwin64-562268.tar.bz2 + + name + darwin64 + + windows + + archive + + hash + f26636666a056cacea0618bc2648d935 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85451/792109/cubemaptoequirectangular-1.1.0-windows-562268.tar.bz2 + + name + windows + + windows64 + + archive + + hash + a59ba299bc94e915a8b55e9eef681253 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85449/792108/cubemaptoequirectangular-1.1.0-windows64-562268.tar.bz2 + + name + windows64 + + + version + 1.1.0 + curl copyright @@ -1491,6 +1543,58 @@ version 2012.1-2 + jpegencoderbasic + + copyright + Andreas Ritter, www.bytestrom.eu, 11/2009 + license + NONE + license_file + LICENSES/JPEG_ENCODER_BASIC_LICENSE.txt + name + jpegencoderbasic + platforms + + darwin64 + + archive + + hash + d07453bf10bae71013209874477640bb + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85447/792085/jpegencoderbasic-1.0-darwin64-562269.tar.bz2 + + name + darwin64 + + windows + + archive + + hash + e422fee0c82507ec218597654f8cadbf + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85450/792107/jpegencoderbasic-1.0-windows-562269.tar.bz2 + + name + windows + + windows64 + + archive + + hash + fbe07040cecc4e8760acc2cdf2436468 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/85448/792106/jpegencoderbasic-1.0-windows64-562269.tar.bz2 + + name + windows64 + + + version + 1.0 + jpeglib copyright @@ -3067,6 +3171,58 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors version 4.10.0000.32327.5fc3fe7c.539691 + threejs + + copyright + Copyright © 2010-2021 three.js authors + license + MIT + license_file + LICENSES/THREEJS_LICENSE.txt + name + threejs + platforms + + darwin64 + + archive + + hash + 313a852ddc87be24235319987a42f0f2 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86208/797009/threejs-0.131.3-darwin64-562835.tar.bz2 + + name + darwin64 + + windows + + archive + + hash + 850b2400f91e7704653fe3fd4171ce94 + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86210/797031/threejs-0.131.3-windows-562835.tar.bz2 + + name + windows + + windows64 + + archive + + hash + 821f398169c1743987ac1b99376f767f + url + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/86209/797030/threejs-0.131.3-windows64-562835.tar.bz2 + + name + windows64 + + + version + 0.131.3 + tut copyright diff --git a/indra/cmake/CubemapToEquirectangularJS.cmake b/indra/cmake/CubemapToEquirectangularJS.cmake new file mode 100644 index 0000000000..bfe2926005 --- /dev/null +++ b/indra/cmake/CubemapToEquirectangularJS.cmake @@ -0,0 +1,5 @@ +# -*- cmake -*- +use_prebuilt_binary(cubemaptoequirectangular) + +# Main JS file +configure_file("${AUTOBUILD_INSTALL_DIR}/js/CubemapToEquirectangular.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/CubemapToEquirectangular.js" COPYONLY) diff --git a/indra/cmake/JPEGEncoderBasic.cmake b/indra/cmake/JPEGEncoderBasic.cmake new file mode 100644 index 0000000000..0d2a3231bb --- /dev/null +++ b/indra/cmake/JPEGEncoderBasic.cmake @@ -0,0 +1,5 @@ +# -*- cmake -*- +use_prebuilt_binary(jpegencoderbasic) + +# Main JS file +configure_file("${AUTOBUILD_INSTALL_DIR}/js/jpeg_encoder_basic.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/jpeg_encoder_basic.js" COPYONLY) diff --git a/indra/cmake/ThreeJS.cmake b/indra/cmake/ThreeJS.cmake new file mode 100644 index 0000000000..528adcbb25 --- /dev/null +++ b/indra/cmake/ThreeJS.cmake @@ -0,0 +1,8 @@ +# -*- cmake -*- +use_prebuilt_binary(threejs) + +# Main three.js file +configure_file("${AUTOBUILD_INSTALL_DIR}/js/three.min.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/three.min.js" COPYONLY) + +# Controls to move around the scene using mouse or keyboard +configure_file("${AUTOBUILD_INSTALL_DIR}/js/OrbitControls.js" "${CMAKE_SOURCE_DIR}/newview/skins/default/html/common/equirectangular/js/OrbitControls.js" COPYONLY) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 6d51adc685..da352e8dd4 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -714,6 +714,15 @@ void LLPluginClassMedia::loadURI(const std::string &uri) sendMessage(message); } +void LLPluginClassMedia::executeJavaScript(const std::string &code) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "execute_javascript"); + + message.setValue("code", code); + + sendMessage(message); +} + const char* LLPluginClassMedia::priorityToString(EPriority priority) { const char* result = "UNKNOWN"; @@ -891,6 +900,19 @@ void LLPluginClassMedia::setJavascriptEnabled(const bool enabled) sendMessage(message); } +void LLPluginClassMedia::setWebSecurityDisabled(const bool disabled) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "web_security_disabled"); + message.setValueBoolean("disabled", disabled); + sendMessage(message); +} + +void LLPluginClassMedia::setFileAccessFromFileUrlsEnabled(const bool enabled) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "file_access_from_file_urls"); + message.setValueBoolean("enabled", enabled); + sendMessage(message); +} void LLPluginClassMedia::enableMediaPluginDebugging( bool enable ) { diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index 382f891e0c..69622c62db 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -139,6 +139,8 @@ public: void loadURI(const std::string &uri); + void executeJavaScript(const std::string &code); + // "Loading" means uninitialized or any state prior to fully running (processing commands) bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; }; @@ -199,6 +201,8 @@ public: void setLanguageCode(const std::string &language_code); void setPluginsEnabled(const bool enabled); void setJavascriptEnabled(const bool enabled); + void setWebSecurityDisabled(const bool disabled); + void setFileAccessFromFileUrlsEnabled(const bool enabled); void setTarget(const std::string &target); /////////////////////////////////// diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 8465285d2b..2f2f25b612 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -65,7 +65,7 @@ private: void onTooltipCallback(std::string text); void onLoadStartCallback(); void onRequestExitCallback(); - void onLoadEndCallback(int httpStatusCode); + void onLoadEndCallback(int httpStatusCode, std::string url); void onLoadError(int status, const std::string error_text); void onAddressChangeCallback(std::string url); void onOpenPopupCallback(std::string url, std::string target); @@ -92,6 +92,8 @@ private: bool mDisableGPU; bool mDisableNetworkService; bool mUseMockKeyChain; + bool mDisableWebSecurity; + bool mFileAccessFromFileUrls; std::string mUserAgentSubtring; std::string mAuthUsername; std::string mAuthPassword; @@ -127,6 +129,8 @@ MediaPluginBase(host_send_func, host_user_data) mDisableGPU = false; mDisableNetworkService = true; mUseMockKeyChain = true; + mDisableWebSecurity = false; + mFileAccessFromFileUrls = false; mUserAgentSubtring = ""; mAuthUsername = ""; mAuthPassword = ""; @@ -260,13 +264,14 @@ void MediaPluginCEF::onRequestExitCallback() //////////////////////////////////////////////////////////////////////////////// // -void MediaPluginCEF::onLoadEndCallback(int httpStatusCode) +void MediaPluginCEF::onLoadEndCallback(int httpStatusCode, std::string url) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete"); //message.setValue("uri", event.getEventUri()); // not easily available here in CEF - needed? message.setValueS32("result_code", httpStatusCode); message.setValueBoolean("history_back_available", mCEFLib->canGoBack()); message.setValueBoolean("history_forward_available", mCEFLib->canGoForward()); + message.setValue("uri", url); sendMessage(message); } @@ -355,14 +360,16 @@ const std::vector MediaPluginCEF::onFileDialog(dullahan::EFileDialo } else if (dialog_type == dullahan::FD_SAVE_FILE) { + mPickedFiles.clear(); mAuthOK = false; LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "file_download"); + message.setValueBoolean("blocking_request", true); message.setValue("filename", default_file); sendMessage(message); - return std::vector(); + return mPickedFiles; } return std::vector(); @@ -523,7 +530,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) mCEFLib->setOnTitleChangeCallback(std::bind(&MediaPluginCEF::onTitleChangeCallback, this, std::placeholders::_1)); mCEFLib->setOnTooltipCallback(std::bind(&MediaPluginCEF::onTooltipCallback, this, std::placeholders::_1)); mCEFLib->setOnLoadStartCallback(std::bind(&MediaPluginCEF::onLoadStartCallback, this)); - mCEFLib->setOnLoadEndCallback(std::bind(&MediaPluginCEF::onLoadEndCallback, this, std::placeholders::_1)); + mCEFLib->setOnLoadEndCallback(std::bind(&MediaPluginCEF::onLoadEndCallback, this, std::placeholders::_1, std::placeholders::_2)); mCEFLib->setOnLoadErrorCallback(std::bind(&MediaPluginCEF::onLoadError, this, std::placeholders::_1, std::placeholders::_2)); mCEFLib->setOnAddressChangeCallback(std::bind(&MediaPluginCEF::onAddressChangeCallback, this, std::placeholders::_1)); mCEFLib->setOnOpenPopupCallback(std::bind(&MediaPluginCEF::onOpenPopupCallback, this, std::placeholders::_1, std::placeholders::_2)); @@ -562,6 +569,19 @@ void MediaPluginCEF::receiveMessage(const char* message_string) settings.disable_network_service = mDisableNetworkService; settings.use_mock_keychain = mUseMockKeyChain; #endif + // these were added to facilitate loading images directly into a local + // web page for the prototype 360 project in 2017 - something that is + // disallowed normally by the browser security model. Now the the source + // (cubemap) images are stores as JavaScript, we can avoid opening up + // this security hole (it was only set for the 360 floater but still + // a concern). Leaving them here, explicitly turn off vs removing + // entirely from this source file so that others are aware of them + // in the future. + settings.disable_web_security = false; + settings.file_access_from_file_urls = false; + + settings.flash_enabled = mPluginsEnabled; + // This setting applies to all plugins, not just Flash // Regarding, SL-15559 PDF files do not load in CEF v91, // it turns out that on Windows, PDF support is treated @@ -688,6 +708,11 @@ void MediaPluginCEF::receiveMessage(const char* message_string) std::string uri = message_in.getValue("uri"); mCEFLib->navigate(uri); } + else if (message_name == "execute_javascript") + { + std::string code = message_in.getValue("code"); + mCEFLib->executeJavaScript(code); + } else if (message_name == "set_cookie") { std::string uri = message_in.getValue("uri"); @@ -883,6 +908,14 @@ void MediaPluginCEF::receiveMessage(const char* message_string) { mDisableGPU = message_in.getValueBoolean("disable"); } + else if (message_name == "web_security_disabled") + { + mDisableWebSecurity = message_in.getValueBoolean("disabled"); + } + else if (message_name == "file_access_from_file_urls") + { + mFileAccessFromFileUrls = message_in.getValueBoolean("enabled"); + } } else if (message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) { diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 87caca56af..08462d044b 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -12,12 +12,14 @@ include(bugsplat) include(BuildPackagesInfo) include(BuildVersion) include(CMakeCopyIfDifferent) +include(CubemapToEquirectangularJS) include(DBusGlib) include(DragDrop) include(EXPAT) include(FMODSTUDIO) include(GLOD) include(Hunspell) +include(JPEGEncoderBasic) include(JsonCpp) include(LLAppearance) include(LLAudio) @@ -47,6 +49,7 @@ include(OpenGL) include(OpenSSL) include(PNG) include(TemplateCheck) +include(ThreeJS) include(UI) include(UnixInstall) include(ViewerMiscLibs) @@ -205,6 +208,7 @@ set(viewer_SOURCE_FILES llfilteredwearablelist.cpp llfirstuse.cpp llflexibleobject.cpp + llfloater360capture.cpp llfloaterabout.cpp llfloaterbvhpreview.cpp llfloateraddpaymentmethod.cpp @@ -278,7 +282,7 @@ set(viewer_SOURCE_FILES llfloaternamedesc.cpp llfloaternotificationsconsole.cpp llfloaternotificationstabbed.cpp - llfloateroutfitphotopreview.cpp + llfloateroutfitphotopreview.cpp llfloateroutfitsnapshot.cpp llfloaterobjectweights.cpp llfloateropenobject.cpp @@ -844,6 +848,7 @@ set(viewer_HEADER_FILES llfilteredwearablelist.h llfirstuse.h llflexibleobject.h + llfloater360capture.h llfloaterabout.h llfloaterbvhpreview.h llfloateraddpaymentmethod.h @@ -1381,7 +1386,7 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}\n") set_source_files_properties( - llversioninfo.cpp tests/llversioninfo_test.cpp + llversioninfo.cpp tests/llversioninfo_test.cpp PROPERTIES COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake ) @@ -2004,7 +2009,7 @@ endif (WINDOWS) # one of these being libz where you can find four or more versions in play # at once. On Linux, libz can be found at link and run time via a number # of paths: -# +# # => -lfreetype # => libz.so.1 (on install machine, not build) # => -lSDL @@ -2175,7 +2180,7 @@ if (DARWIN) # https://blog.kitware.com/upcoming-in-cmake-2-8-12-osx-rpath-support/ set(CMAKE_MACOSX_RPATH 1) - + set_target_properties( ${VIEWER_BINARY_NAME} PROPERTIES @@ -2436,7 +2441,7 @@ if (LL_TESTS) llworldmap.cpp llworldmipmap.cpp PROPERTIES - LL_TEST_ADDITIONAL_SOURCE_FILES + LL_TEST_ADDITIONAL_SOURCE_FILES tests/llviewertexture_stub.cpp #llviewertexturelist.cpp ) @@ -2470,7 +2475,7 @@ if (LL_TESTS) llworldmap.cpp llworldmipmap.cpp PROPERTIES - LL_TEST_ADDITIONAL_SOURCE_FILES + LL_TEST_ADDITIONAL_SOURCE_FILES tests/llviewertexture_stub.cpp #llviewertexturelist.cpp LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_SYSTEM_LIBRARY}" diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index d0480ca47e..3dfe3f6634 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -276,4 +276,15 @@ is_running_function="Floater.IsOpen" is_running_parameters="my_environments" /> + diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b1120c18b2..5c140fb724 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -2076,6 +2076,28 @@ Value 1 + BrowserFileAccessFromFileUrls + + Comment + Allow access to local files via file urls in the embedded browser + Persist + 1 + Type + Boolean + Value + 1 + + BrowserPluginsEnabled + + Comment + Enable Web plugins in the built-in Web browser? + Persist + 1 + Type + Boolean + Value + 1 + ChatBarCustomWidth Comment @@ -3539,7 +3561,7 @@ Value 0 - DoubleClickTeleport + DoubleClickTeleport Comment Enable double-click to teleport where allowed (afects minimap and people panel) @@ -8858,7 +8880,7 @@ Value 0 - RenderHiDPI + RenderHiDPI Comment Enable support for HiDPI displays, like Retina (MacOS X ONLY, requires restart) @@ -11580,7 +11602,7 @@ Boolean Value 0 - + NearbyListShowMap Comment @@ -16632,30 +16654,83 @@ Boolean Value 1 - - CefVerboseLog - - Comment - Enable/disable CEF verbose loggingk - Persist - 1 - Type - Boolean - Value - 0 - - ResetUIScaleOnFirstRun - - Comment - Resets the UI scale factor on first run due to changed display scaling behavior - Persist - 1 - Type - Boolean - Value - 1 + + 360CaptureUseInterestListCap + + Comment + Flag if set, uses the new InterestList cap to ask the simulator for full content + Persist + 1 + Type + Boolean + Value + 1 + + 360CaptureJPEGEncodeQuality + + Comment + Quality value to use in the JPEG encoder (0..100) + Persist + 1 + Type + U32 + Value + 95 + + 360CaptureDebugSaveImage + + Comment + Flag if set, saves off each cube map as an image, as well as the JavaScript data URL, for debugging purposes + Persist + 1 + Type + Boolean + Value + 0 + + 360CaptureOutputImageWidth + + Comment + Width of the output 360 equirectangular image + Persist + 1 + Type + U32 + Value + 4096 + + 360CaptureHideAvatars + + Comment + Flag if set, removes all the avatars from the 360 snapshot + Persist + 1 + Type + Boolean + Value + 0 + 360CaptureCameraFOV + + Comment + Field of view of the WebGL camera that converts the cubemap to an equirectangular image + Persist + 1 + Type + U32 + Value + 75 + + ResetUIScaleOnFirstRun + + Comment + Resets the UI scale factor on first run due to changed display scaling behavior + Persist + 1 + Type + Boolean + Value + 1 + - - diff --git a/indra/newview/llfloater360capture.cpp b/indra/newview/llfloater360capture.cpp new file mode 100644 index 0000000000..aa226981aa --- /dev/null +++ b/indra/newview/llfloater360capture.cpp @@ -0,0 +1,904 @@ +/** + * @file llfloater360capture.cpp + * @author Callum Prentice (callum@lindenlab.com) + * @brief Floater code for the 360 Capture feature + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloater360capture.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llbase64.h" +#include "llcallbacklist.h" +#include "llenvironment.h" +#include "llimagejpeg.h" +#include "llmediactrl.h" +#include "llradiogroup.h" +#include "llslurl.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llversioninfo.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerpartsim.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "pipeline.h" + +#include + +LLFloater360Capture::LLFloater360Capture(const LLSD& key) + : LLFloater(key) +{ + // The handle to embedded browser that we use to + // render the WebGL preview as we as host the + // Cube Map to Equirectangular image code + mWebBrowser = nullptr; + + // Ask the simulator to send us everything (and not just + // what it thinks the connected Viewer can see) until + // such time as we ask it not to (the dtor). If we crash or + // otherwise, exit before this is turned off, the Simulator + // will take care of cleaning up for us. + if (gSavedSettings.getBOOL("360CaptureUseInterestListCap")) + { + // send everything to us for as long as this floater is open + const bool send_everything = true; + changeInterestListMode(send_everything); + } +} + +LLFloater360Capture::~LLFloater360Capture() +{ + if (mWebBrowser) + { + mWebBrowser->navigateStop(); + mWebBrowser->clearCache(); + mWebBrowser->unloadMediaSource(); + } + + // Tell the Simulator not to send us everything anymore + // and revert to the regular "keyhole" frustum of interest + // list updates. + if (gSavedSettings.getBOOL("360CaptureUseInterestListCap")) + { + const bool send_everything = false; + changeInterestListMode(send_everything); + } +} + +BOOL LLFloater360Capture::postBuild() +{ + mCaptureBtn = getChild("capture_button"); + mCaptureBtn->setCommitCallback(boost::bind(&LLFloater360Capture::onCapture360ImagesBtn, this)); + + mSaveLocalBtn = getChild("save_local_button"); + mSaveLocalBtn->setCommitCallback(boost::bind(&LLFloater360Capture::onSaveLocalBtn, this)); + mSaveLocalBtn->setEnabled(false); + + mWebBrowser = getChild("360capture_contents"); + mWebBrowser->addObserver(this); + + // There is a group of radio buttons that define the quality + // by each having a 'value' that is returns equal to the pixel + // size (width == height) + mQualityRadioGroup = getChild("360_quality_selection"); + mQualityRadioGroup->setCommitCallback(boost::bind(&LLFloater360Capture::onChooseQualityRadioGroup, this)); + + // UX/UI called for preview mode (always the first index/option) + // by default each time vs restoring the last value + mQualityRadioGroup->setSelectedIndex(0); + + // Construct a URL pointing to the first page to load. Although + // we do not use this page for anything (after some significant + // design changes), we retain the code to load the start page + // in case that changes again one day. It also makes sure the + // embedded browser is active and ready to go for when the real + // page with the 360 preview is navigated to. + std::string url = STRINGIZE( + "file:///" << + getHTMLBaseFolder() << + mDefaultHTML + ); + mWebBrowser->navigateTo(url); + + // initial pass at determining what size (width == height since + // the cube map images are square) we should capture at. + setSourceImageSize(); + + // the size of the output equirectangular image. The height of an EQR image + // is always 1/2 of the width so we should not store it but rather, + // calculate it from the width directly + mOutputImageWidth = gSavedSettings.getU32("360CaptureOutputImageWidth"); + mOutputImageHeight = mOutputImageWidth / 2; + + // enable resizing and enable for width and for height + enableResizeCtrls(true, true, true); + + // initial heading that consumers of the equirectangular image + // (such as Facebook or Flickr) use to position initial view - + // we set during capture - stored as degrees (0..359) + mInitialHeadingDeg = 0.0; + + // save directory in which to store the images (must obliviously be + // writable by the viewer). Also create it for users who haven't + // used the 360 feature before. + mImageSaveDir = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + "eqrimg"; + LLFile::mkdir(mImageSaveDir); + + // We do an initial capture when the floater is opened, albeit at a 'preview' + // quality level (really low resolution, but really fast) + onCapture360ImagesBtn(); + + return true; +} + +// called when the user choose a quality level using +// the buttons in the radio group +void LLFloater360Capture::onChooseQualityRadioGroup() +{ + // set the size of the captured cube map images based + // on the quality level chosen + setSourceImageSize(); +} + +// Using a new capability, tell the simulator that we want it to send everything +// it knows about and not just what is in front of the camera, in its view +// frustum. We need this feature so that the contents of the region that appears +// in the 6 snapshots which we cannot see and is normally not "considered", is +// also rendered. Typically, this is turned on when the 360 capture floater is +// opened and turned off when it is closed. +// Note: for this version, we do not have a way to determine when "everything" +// has arrived and has been rendered so for now, the proposal is that users +// will need to experiment with the low resolution version and wait for some +// (hopefully) small period of time while the full contents resolves. +// Pass in a flag to ask the simulator/interest list to "send everything" or +// not (the default mode) +void LLFloater360Capture::changeInterestListMode(bool send_everything) +{ + LLSD body; + + if (send_everything) + { + body["mode"] = LLSD::String("360"); + } + else + { + body["mode"] = LLSD::String("default"); + } + + if (gAgent.requestPostCapability("InterestList", body, [](const LLSD & response) + { + LL_INFOS("360Capture") << + "InterestList capability responded: \n" << + ll_pretty_print_sd(response) << + LL_ENDL; + })) + { + LL_INFOS("360Capture") << + "Successfully posted an InterestList capability request with payload: \n" << + ll_pretty_print_sd(body) << + LL_ENDL; + } + else + { + LL_INFOS("360Capture") << + "Unable to post an InterestList capability request with payload: \n" << + ll_pretty_print_sd(body) << + LL_ENDL; + } +} + +// There is is a setting (360CaptureSourceImageSize) that holds the size +// (width == height since it's a square) of each of the 6 source snapshots. +// However there are some known (and I dare say, some more unknown conditions +// where the specified size is not possible and this function tries to figure it +// out and change that setting to the optimal value for the current conditions. +void LLFloater360Capture::setSourceImageSize() +{ + mSourceImageSize = mQualityRadioGroup->getSelectedValue().asInteger(); + + // If deferred rendering is off, we need to shrink the window we capture + // until it's smaller than the Viewer window dimensions. + if (!LLPipeline::sRenderDeferred) + { + LLRect window_rect = gViewerWindow->getWindowRectRaw(); + S32 window_width = window_rect.getWidth(); + S32 window_height = window_rect.getHeight(); + + while (mSourceImageSize > window_width || mSourceImageSize > window_height) + { + mSourceImageSize /= 2; + LL_INFOS("360Capture") << "Deferred rendering is forcing a smaller capture size: " << mSourceImageSize << LL_ENDL; + } + + // there has to be an easier way than this to get the value + // from the radio group item at index 0. Why doesn't + // LLRadioGroup::getSelectedValue(int index) exist? + int index = mQualityRadioGroup->getSelectedIndex(); + mQualityRadioGroup->setSelectedIndex(0); + int min_size = mQualityRadioGroup->getSelectedValue().asInteger(); + mQualityRadioGroup->setSelectedIndex(index); + + // If the maximum size we can support falls below a threshold then + // we should display a message in the log so we can try to debug + // why this is happening + if (mSourceImageSize < min_size) + { + LL_INFOS("360Capture") << "Small snapshot size due to deferred rendering and small app window" << LL_ENDL; + } + } +} + +// This function shouldn't exist! We use the tooltip text from +// the corresponding XUI file (floater_360capture.xml) as the +// overlay text for the final web page to inform the user +// about the quality level in play. There ouught to be a +// UI function like LLView* getSelectedItemView() or similar +// but as far as I can tell, there isn't so we have to resort +// to finding it ourselves with this icky code.. +const std::string LLFloater360Capture::getSelectedQualityTooltip() +{ + // safey (or bravery?) + if (mQualityRadioGroup != nullptr) + { + // for all the child widgets for the radio group + // (just the radio buttons themselves I think) + for (child_list_const_reverse_iter_t iter = mQualityRadioGroup->getChildList()->rbegin(); + iter != mQualityRadioGroup->getChildList()->rend(); + ++iter) + { + // if we match the selected index (which we can get easily) + // with our position in the list of children + if (mQualityRadioGroup->getSelectedIndex() == + std::distance(mQualityRadioGroup->getChildList()->rend(), iter) - 1) + { + // return the plain old tooltip text + return (*iter)->getToolTip(); + } + } + } + + // if it's not found or not available, return an empty string + return std::string(); +} + +// Some of the 'magic' happens via a web page in an HTML directory +// and this code provides a single point of reference for its' location +const std::string LLFloater360Capture::getHTMLBaseFolder() +{ + std::string folder_name = gDirUtilp->getSkinDir(); + folder_name += gDirUtilp->getDirDelimiter(); + folder_name += "html"; + folder_name += gDirUtilp->getDirDelimiter(); + folder_name += "common"; + folder_name += gDirUtilp->getDirDelimiter(); + folder_name += "equirectangular"; + folder_name += gDirUtilp->getDirDelimiter(); + + return folder_name; +} + +// triggered when the 'capture' button in the UI is pressed +void LLFloater360Capture::onCapture360ImagesBtn() +{ + // launch the main capture code in a coroutine so we can + // yield/suspend at some points to give the main UI + // thread a look-in occasionally. + LLCoros::instance().launch("capture360cap", [this]() + { + capture360Images(); + }); +} + +// Gets the full path name for a given JavaScript file in the HTML folder. We +// use this ultimately as a parameter to the main web app so it knows where to find +// the JavaScript array containing the 6 cube map images, stored as data URLs +const std::string LLFloater360Capture::makeFullPathToJS(const std::string filename) +{ + std::string full_js_path = mImageSaveDir; + full_js_path += gDirUtilp->getDirDelimiter(); + full_js_path += filename; + + return full_js_path; +} + +// Write the header/prequel portion of the JavaScript array of data urls +// that we use to store the cube map images in (so the web code can load +// them without tweaking browser security - we'd have to do this if they +// we stored as plain old images) This deliberately overwrites the old +// one, if it exists +void LLFloater360Capture::writeDataURLHeader(const std::string filename) +{ + std::ofstream file_handle(filename.c_str()); + if (file_handle.is_open()) + { + file_handle << "// cube map images for Second Life Viewer panorama 360 images" << std::endl; + file_handle.close(); + } +} + +// Write the footer/sequel portion of the JavaScript image code. When this is +// called, the current file on disk will contain the header and the 6 data +// URLs, each with a well specified name. This final piece of JavaScript code +// creates an array from those data URLs that the main application can +// reference and read. +void LLFloater360Capture::writeDataURLFooter(const std::string filename) +{ + std::ofstream file_handle(filename.c_str(), std::ios_base::app); + if (file_handle.is_open()) + { + file_handle << "var cubemap_img_js = [" << std::endl; + file_handle << " img_posx, img_negx," << std::endl; + file_handle << " img_posy, img_negy," << std::endl; + file_handle << " img_posz, img_negz," << std::endl; + file_handle << "];" << std::endl; + + file_handle.close(); + } +} + +// Given a filename, a chunk of data (representing an image file) and the size +// of the buffer, we create a BASE64 encoded string and use it to build a JavaScript +// data URL that represents the image in a web browser environment +bool LLFloater360Capture::writeDataURL(const std::string filename, const std::string prefix, U8* data, unsigned int data_len) +{ + LL_INFOS("360Capture") << "Writing data URL for " << prefix << " to " << filename << LL_ENDL; + + const std::string data_url = LLBase64::encode(data, data_len); + + std::ofstream file_handle(filename.c_str(), std::ios_base::app); + if (file_handle.is_open()) + { + file_handle << "var img_"; + file_handle << prefix; + file_handle << " = '"; + file_handle << "data:image/jpeg; base64,"; + file_handle << data_url; + file_handle << "'"; + file_handle << std::endl; + file_handle.close(); + + return true; + } + + return false; +} + +// Encode the image from each of the 6 snapshots and save it out to +// the JavaScript array of data URLs +void LLFloater360Capture::encodeAndSave(LLPointer raw_image, const std::string filename, const std::string prefix) +{ + // the default quality for the JPEG encoding is set quite high + // but this still seems to be a reasonable compromise for + // quality/size and is still much smaller than equivalent PNGs + int jpeg_encode_quality = gSavedSettings.getU32("360CaptureJPEGEncodeQuality"); + LLPointer jpeg_image = new LLImageJPEG(jpeg_encode_quality); + + // Actually encode the JPEG image. This is where a lot of time + // is spent now that the snapshot capture process has been + // optimized. The encode_time parameter doesn't appear to be + // used anymore. + const int encode_time = 0; + bool resultjpeg = jpeg_image->encode(raw_image, encode_time); + + if (resultjpeg) + { + // save individual cube map images as real JPEG files + // for debugging or curiosity) based on debug settings + if (gSavedSettings.getBOOL("360CaptureDebugSaveImage")) + { + const std::string jpeg_filename = STRINGIZE( + gDirUtilp->getLindenUserDir() << + gDirUtilp->getDirDelimiter() << + "eqrimg" << + gDirUtilp->getDirDelimiter() << + prefix << + "." << + jpeg_image->getExtension() + ); + + LL_INFOS("360Capture") << "Saving debug JPEG image as " << jpeg_filename << LL_ENDL; + jpeg_image->save(jpeg_filename); + } + + // actually write the JPEG image to disk as a data URL + writeDataURL(filename, prefix, jpeg_image->getData(), jpeg_image->getDataSize()); + } +} + +// Defer back to the main loop for a single rendered frame to give +// the renderer a chance to update the UI if it is needed +void LLFloater360Capture::suspendForAFrame() +{ + const U32 frame_count_delta = 1; + U32 curr_frame_count = LLFrameTimer::getFrameCount(); + while (LLFrameTimer::getFrameCount() <= curr_frame_count + frame_count_delta) + { + llcoro::suspend(); + } +} + +// A debug version of the snapshot code that simply fills the +// buffer with a pattern that can be used to investigate +// issues with encoding and saving off each RAW image. +// Probably not needed anymore but saving here just in case. +void LLFloater360Capture::mockSnapShot(LLImageRaw* raw) +{ + unsigned int width = raw->getWidth(); + unsigned int height = raw->getHeight(); + unsigned int depth = raw->getComponents(); + unsigned char* pixels = raw->getData(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + unsigned long offset = y * width * depth + x * depth; + unsigned char red = x * 256 / width; + unsigned char green = y * 256 / height; + unsigned char blue = ((x + y) / 2) * 256 / (width + height) / 2; + pixels[offset + 0] = red; + pixels[offset + 1] = green; + pixels[offset + 2] = blue; + } + } +} + +// The main code that actually captures all 6 images and then saves them out to +// disk before navigating the embedded web browser to the page with the WebGL +// application that consumes them and creates an EQR image. This code runs as a +// coroutine so it can be suspended at certain points. +void LLFloater360Capture::capture360Images() +{ + // recheck the size of the cube map source images in case it changed + // since it was set when we opened the floater + setSourceImageSize(); + + // disable buttons while we are capturing + mCaptureBtn->setEnabled(false); + mSaveLocalBtn->setEnabled(false); + + // determine whether or not to include avatar in the scene as we capture the 360 panorama + if (gSavedSettings.getBOOL("360CaptureHideAvatars")) + { + // Turn off the avatar if UI tells us to hide it. + // Note: the original call to gAvatar.hide(FALSE) did *not* hide + // attachments and so for most residents, there would be some debris + // left behind in the snapshot. + // Note: this toggles so if it set to on, this will turn it off and + // the subsequent call to the same thing after capture is finished + // will turn it back on again. Similarly, for the case where it + // was set to off - I think this is what we need + LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR); + } + + // these are the 6 directions we will point the camera - essentially, + // North, South, East, West, Up, Down + LLVector3 look_dirs[6] = { LLVector3(1, 0, 0), LLVector3(0, 1, 0), LLVector3(0, 0, 1), LLVector3(-1, 0, 0), LLVector3(0, -1, 0), LLVector3(0, 0, -1) }; + LLVector3 look_upvecs[6] = { LLVector3(0, 0, 1), LLVector3(0, 0, 1), LLVector3(0, -1, 0), LLVector3(0, 0, 1), LLVector3(0, 0, 1), LLVector3(0, 1, 0) }; + + // save current view/camera settings so we can restore them afterwards + S32 old_occlusion = LLPipeline::sUseOcclusion; + LLPipeline::sUseOcclusion = 0; + LLViewerCamera* camera = LLViewerCamera::getInstance(); + F32 old_fov = camera->getView(); + F32 old_aspect = camera->getAspect(); + F32 old_yaw = camera->getYaw(); + + // stop the motion of as much of the world moving as much as we can + freezeWorld(true); + + // Save the direction (in degrees) the camera is looking when we + // take the shot since that is what we write to image metadata + // 'GPano:InitialViewHeadingDegrees' field. + // We need to convert from the angle getYaw() gives us into something + // the XMP data field wants (N=0, E=90, S=180, W= 270 etc.) + mInitialHeadingDeg = (360 + 90 - (int)(camera->getYaw() * RAD_TO_DEG)) % 360; + LL_INFOS("360Capture") << "Recording a heading of " << (int)(mInitialHeadingDeg) << LL_ENDL; + + // camera constants for the square, cube map capture image + camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV + camera->setView(F_PI_BY_TWO); + camera->yaw(0.0); + + // record how many times we changed camera to try to understand the "all shots are the same issue" + unsigned int camera_changed_times = 0; + + // the name of the JavaScript file written out that contains the 6 cube map images + // stored as a JavaScript array of data URLs. If you change this filename, you must + // also change the corresponding entry in the HTML file that uses it - + // (newview/skins/default/html/common/equirectangular/display_eqr.html) + const std::string cumemap_js_filename("cubemap_img.js"); + + // construct the full path to this file - typically stored in the users' + // Second Life settings / username / eqrimg folder. + const std::string cubemap_js_full_path = makeFullPathToJS(cumemap_js_filename); + + // Write the JavaScript file header (the top of the file before the + // declarations of the actual data URLs array). In practice, all this writes + // is a comment - it's main purpose is to reset the file from the last time + // it was written + writeDataURLHeader(cubemap_js_full_path); + + // the names of the prefixes we assign as the name to each data URL and are then + // consumed by the WebGL application. Nominally, they stand for positive and + // negative in the X/Y/Z directions. + static const std::string prefixes[6] = + { + "posx", "posz", "posy", + "negx", "negz", "negy", + }; + + // time the encode process for later optimization + auto encode_time_total = 0.0; + + // for each of the 6 directions we shoot... + for (int i = 0; i < 6; i++) + { + // these buffers are where the raw, captured pixels are stored and + // the first time we use them, we have to make a new one + if (mRawImages[i] == nullptr) + { + mRawImages[i] = new LLImageRaw(mSourceImageSize, mSourceImageSize, 3); + } + else + // subsequent capture with floater open so we resize the buffer from + // the previous run + { + // LLImageRaw deletes the old one via operator= but just to be + // sure, we delete its' large data member first... + mRawImages[i]->deleteData(); + mRawImages[i] = new LLImageRaw(mSourceImageSize, mSourceImageSize, 3); + } + + // set up camera to look in each direction + camera->lookDir(look_dirs[i], look_upvecs[i]); + + // record if camera changed to try to understand the "all shots are the same issue" + if (camera->isChanged()) + { + ++camera_changed_times; + } + + // call the (very) simplified snapshot code that simply deals + // with a single image, no sub-images etc. but is very fast + gViewerWindow->simpleSnapshot(mRawImages[i], mSourceImageSize, mSourceImageSize); + + // encode each image and write to disk while saving how long it took to do so + auto t_start = std::chrono::high_resolution_clock::now(); + encodeAndSave(mRawImages[i], cubemap_js_full_path, prefixes[i]); + auto t_end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast>(t_end - t_start); + encode_time_total += duration.count(); + + // ping the main loop in case the snapshot process takes a really long + // time and we get disconnected + LLAppViewer::instance()->pingMainloopTimeout("LLFloater360Capture::capture360Images"); + } + + // display time to encode all 6 images. It tends to be a fairly linear + // time for each so we don't need to worry about displaying the time + // for each - this gives us plenty to use for optimizing + LL_INFOS("360Capture") << + "Time to encode and save 6 images was " << + encode_time_total << + " seconds" << + LL_ENDL; + + // Write the JavaScript file footer (the bottom of the file after the + // declarations of the actual data URLs array). The footer comprises of + // a JavaScript array declaration that references the 6 data URLs generated + // previously and is what is referred to in the display HTML file + // (newview/skins/default/html/common/equirectangular/display_eqr.html) + writeDataURLFooter(cubemap_js_full_path); + + // unfreeze the world now we have our shots + freezeWorld(false); + + // restore original view/camera/avatar settings settings + camera->setAspect(old_aspect); + camera->setView(old_fov); + camera->yaw(old_yaw); + LLPipeline::sUseOcclusion = old_occlusion; + + // if we toggled off the avatar because the Hide check box was ticked, + // we should toggle it back to where it was before we started the capture + if (gSavedSettings.getBOOL("360CaptureHideAvatars")) + { + LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR); + } + + // record that we missed some shots in the log for later debugging + // note: we use 5 and not 6 because the first shot isn't regarded + // as a change - only the subsequent 5 are + if (camera_changed_times < 5) + { + LL_INFOS("360Capture") << "Warning: we only captured " << camera_changed_times << " images." << LL_ENDL; + } + + // now we have the 6 shots saved in a well specified location, + // we can load the web content that uses them + std::string url = "file:///" + getHTMLBaseFolder() + mEqrGenHTML; + mWebBrowser->navigateTo(url); + + // allow the UI to update by suspending and waiting for the + // main render loop to update the UI + suspendForAFrame(); + + // page is loaded and ready so we can turn on the buttons again + mCaptureBtn->setEnabled(true); + mSaveLocalBtn->setEnabled(true); +} + +// once the request is made to navigate to the web page containing the code +// to process the 6 images into an EQR one, we have to wait for it to finish +// loaded - we get a "navigate complete" event when that happens that we can act on +void LLFloater360Capture::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch (event) + { + // not used right now but retaining because this event might + // be useful for a feature I am hoping to add + case MEDIA_EVENT_LOCATION_CHANGED: + break; + + // navigation in the browser completed + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + // Confirm that the navigation event does indeed apply to the + // page we are looking for. At the moment, this is the only + // one we care about so the test is superfluous but that might change. + std::string navigate_url = self->getNavigateURI(); + if (navigate_url.find(mEqrGenHTML) != std::string::npos) + { + // this string is being passed across to the web so replace all the windows backslash + // characters with forward slashes or (I think) the backslashes are treated as escapes + std::replace(mImageSaveDir.begin(), mImageSaveDir.end(), '\\', '/'); + + // we store the camera FOV (field of view) in a saved setting since this feels + // like something it would be interesting to change and experiment with + int camera_fov = gSavedSettings.getU32("360CaptureCameraFOV"); + + // compose the overlay for the final web page that tells the user + // what level of quality the capture was taken with + std::string overlay_label = "'" + getSelectedQualityTooltip() + "'"; + + // so now our page is loaded and images are in place - call + // the JavaScript init script with some parameters to initialize + // the WebGL based preview + const std::string cmd = STRINGIZE( + "init(" + << mOutputImageWidth + << ", " + << mOutputImageHeight + << ", " + << "'" + << mImageSaveDir + << "'" + << ", " + << camera_fov + << ", " + << LLViewerCamera::getInstance()->getYaw() + << ", " + << overlay_label + << ")" + ); + + // execute the command on the page + mWebBrowser->getMediaPlugin()->executeJavaScript(cmd); + } + } + break; + + default: + break; + } +} + +// called when the user wants to save the cube maps off to the final EQR image +void LLFloater360Capture::onSaveLocalBtn() +{ + // region name and URL + std::string region_name; // no sensible default + std::string region_url("http://secondlife.com"); + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + // region names can (and do) contain characters that would make passing + // them into a JavaScript function problematic - single quotes for example + // so we must escape/encode both + region_name = region->getName(); + + // escaping/encoding is a minefield - let's just remove any offending characters from the region name + region_name.erase(std::remove(region_name.begin(), region_name.end(), '\''), region_name.end()); + region_name.erase(std::remove(region_name.begin(), region_name.end(), '\"'), region_name.end()); + + // fortunately there is already an escaping function built into the SLURL generation code + LLSLURL slurl; + bool is_escaped = true; + LLAgentUI::buildSLURL(slurl, is_escaped); + region_url = slurl.getSLURLString(); + } + + // build suggested filename (the one that appears as the default + // in the Save dialog box) + const std::string suggested_filename = generate_proposed_filename(); + + // This string (the name of the product plus a truncated version number (no build)) + // is used in the XMP block as the name of the generating and stitching software. + // We save the version number here and not in the more generic 'software' item + // because that might help us determine something about the image in the future. + const std::string client_version = STRINGIZE( + LLVersionInfo::instance().getChannel() << + " " << + LLVersionInfo::instance().getShortVersion() + ); + + // save the time the image was created. I don't know if this should be + // UTC/ZULU or the users' local time. It probably doesn't matter. + std::time_t result = std::time(nullptr); + std::string ctime_str = std::ctime(&result); + std::string time_str = ctime_str.substr(0, ctime_str.length() - 1); + + // build the JavaScript data structure that is used to pass all the + // variables into the JavaScript function on the web page loaded into + // the embedded browser component of the floater. + const std::string xmp_details = STRINGIZE( + "{ " << + "pano_version: '" << "2.2.1" << "', " << + "software: '" << LLVersionInfo::instance().getChannel() << "', " << + "capture_software: '" << client_version << "', " << + "stitching_software: '" << client_version << "', " << + "width: " << mOutputImageWidth << ", " << + "height: " << mOutputImageHeight << ", " << + "heading: " << mInitialHeadingDeg << ", " << + "actual_source_image_size: " << mQualityRadioGroup->getSelectedValue().asInteger() << ", " << + "scaled_source_image_size: " << mSourceImageSize << ", " << + "first_photo_date: '" << time_str << "', " << + "last_photo_date: '" << time_str << "', " << + "region_name: '" << region_name << "', " << + "region_url: '" << region_url << "', " << + " }" + ); + + // build the JavaScript command to send to the web browser + const std::string cmd = "saveAsEqrImage(\"" + suggested_filename + "\", " + xmp_details + ")"; + + // send it to the browser instance, triggering the equirectangular capture + // process and complimentary offer to save the image + mWebBrowser->getMediaPlugin()->executeJavaScript(cmd); +} + +// We capture all 6 images sequentially and if parts of the world are moving +// E.G. clouds, water, objects - then we may get seams or discontinuities +// when the images are combined to form the EQR image. This code tries to +// stop everything so we can shoot for seamless shots. There is probably more +// we can do here - e.g. waves in the water probably won't line up. +void LLFloater360Capture::freezeWorld(bool enable) +{ + static bool clouds_scroll_paused = false; + if (enable) + { + // record the cloud scroll current value so we can restore it + clouds_scroll_paused = LLEnvironment::instance().isCloudScrollPaused(); + + // stop the clouds moving + LLEnvironment::instance().pauseCloudScroll(); + + // freeze all avatars + LLCharacter* avatarp; + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + avatarp = *iter; + mAvatarPauseHandles.push_back(avatarp->requestPause()); + } + + // freeze everything else + gSavedSettings.setBOOL("FreezeTime", true); + + // disable particle system + LLViewerPartSim::getInstance()->enable(false); + } + else // turning off freeze world mode, either temporarily or not. + { + // restart the clouds moving if they were not paused before + // we starting using the 360 capture floater + if (clouds_scroll_paused == false) + { + LLEnvironment::instance().resumeCloudScroll(); + } + + // thaw all avatars + mAvatarPauseHandles.clear(); + + // thaw everything else + gSavedSettings.setBOOL("FreezeTime", false); + + //enable particle system + LLViewerPartSim::getInstance()->enable(true); + } +} + +// Build the default filename that appears in the Save dialog box. We try +// to encode some metadata about too (region name, EQR dimensions, capture +// time) but the user if free to replace this with anything else before +// the images is saved. +const std::string LLFloater360Capture::generate_proposed_filename() +{ + std::ostringstream filename(""); + + // base name + filename << "sl360_"; + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + // this looks complex but it's straightforward - removes all non-alpha chars from a string + // which in this case is the SL region name - we use it as a proposed filename but the user is free to change + std::string region_name = region->getName(); + std::replace_if(region_name.begin(), region_name.end(), std::not1(std::ptr_fun(isalnum)), '_'); + if (region_name.length() > 0) + { + filename << region_name; + filename << "_"; + } + } + + // add in resolution to make it easier to tell what you captured later + filename << mOutputImageWidth; + filename << "x"; + filename << mOutputImageHeight; + filename << "_"; + + // Add in the size of the source image (width == height since it was square) + // Might be useful later for quality comparisons + filename << mSourceImageSize; + filename << "_"; + + // add in the current HH-MM-SS (with leading 0's) so users can easily save many shots in same folder + std::time_t cur_epoch = std::time(nullptr); + std::tm* tm_time = std::localtime(&cur_epoch); + filename << std::setfill('0') << std::setw(4) << (tm_time->tm_year + 1900); + filename << std::setfill('0') << std::setw(2) << (tm_time->tm_mon + 1); + filename << std::setfill('0') << std::setw(2) << tm_time->tm_mday; + filename << "_"; + filename << std::setfill('0') << std::setw(2) << tm_time->tm_hour; + filename << std::setfill('0') << std::setw(2) << tm_time->tm_min; + filename << std::setfill('0') << std::setw(2) << tm_time->tm_sec; + + // the unusual way we save the output image (originates in the + // embedded browser and not the C++ code) means that the system + // appends ".jpeg" to the file automatically on macOS at least, + // so we only need to do it ourselves for windows. +#if LL_WINDOWS + filename << ".jpg"; +#endif + + return filename.str(); +} diff --git a/indra/newview/llfloater360capture.h b/indra/newview/llfloater360capture.h new file mode 100644 index 0000000000..6da7ee074a --- /dev/null +++ b/indra/newview/llfloater360capture.h @@ -0,0 +1,97 @@ +/** + * @file llfloater360capture.h + * @author Callum Prentice (callum@lindenlab.com) + * @brief Floater for the 360 capture feature + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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_FLOATER_360CAPTURE_H +#define LL_FLOATER_360CAPTURE_H + +#include "llfloater.h" +#include "llmediactrl.h" +#include "llcharacter.h" + +class LLImageRaw; +class LLTextBox; +class LLRadioGroup; + +class LLFloater360Capture: + public LLFloater, + public LLViewerMediaObserver +{ + friend class LLFloaterReg; + + private: + LLFloater360Capture(const LLSD& key); + + ~LLFloater360Capture(); + BOOL postBuild() override; + void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; + + void changeInterestListMode(bool send_everything); + + const std::string getHTMLBaseFolder(); + void capture360Images(); + + const std::string makeFullPathToJS(const std::string filename); + void writeDataURLHeader(const std::string filename); + void writeDataURLFooter(const std::string filename); + bool writeDataURL(const std::string filename, const std::string prefix, U8* data, unsigned int data_len); + void encodeAndSave(LLPointer raw_image, const std::string filename, const std::string prefix); + + std::vector mAvatarPauseHandles; + void freezeWorld(bool enable); + + void mockSnapShot(LLImageRaw* raw); + + void suspendForAFrame(); + + const std::string generate_proposed_filename(); + + void setSourceImageSize(); + + LLMediaCtrl* mWebBrowser; + const std::string mDefaultHTML = "default.html"; + const std::string mEqrGenHTML = "eqr_gen.html"; + + LLUICtrl* mCaptureBtn; + void onCapture360ImagesBtn(); + + void onSaveLocalBtn(); + LLUICtrl* mSaveLocalBtn; + + LLRadioGroup* mQualityRadioGroup; + void onChooseQualityRadioGroup(); + const std::string getSelectedQualityTooltip(); + + int mSourceImageSize; + float mInitialHeadingDeg; + int mOutputImageWidth; + int mOutputImageHeight; + std::string mImageSaveDir; + + LLPointer mRawImages[6]; +}; + +#endif // LL_FLOATER_360CAPTURE_H diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index ef7a9fd536..83212230e5 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -179,6 +179,10 @@ void LLFloaterSnapshotBase::ImplBase::updateLayout(LLFloaterSnapshotBase* floate thumbnail_placeholder->reshape(panel_width, thumbnail_placeholder->getRect().getHeight()); floaterp->getChild("image_res_text")->setVisible(mAdvanced); floaterp->getChild("file_size_label")->setVisible(mAdvanced); + if (floaterp->hasChild("360_label", TRUE)) + { + floaterp->getChild("360_label")->setVisible(mAdvanced); + } if(!floaterp->isMinimized()) { floaterp->reshape(floater_width, floaterp->getRect().getHeight()); @@ -992,6 +996,10 @@ BOOL LLFloaterSnapshot::postBuild() getChild("retract_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); getChild("extend_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); + getChild("360_label")->setSoundFlags(LLView::MOUSE_UP); + getChild("360_label")->setShowCursorHand(false); + getChild("360_label")->setClickedCallback(boost::bind(&LLFloaterSnapshot::on360Snapshot, this)); + // Filters LLComboBox* filterbox = getChild("filters_combobox"); std::vector filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); @@ -1118,6 +1126,12 @@ void LLFloaterSnapshot::onExtendFloater() impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); } +void LLFloaterSnapshot::on360Snapshot() +{ + LLFloaterReg::showInstance("360capture"); + closeFloater(); +} + //virtual void LLFloaterSnapshotBase::onClose(bool app_quitting) { diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h index 8221b0a637..7ec133ff45 100644 --- a/indra/newview/llfloatersnapshot.h +++ b/indra/newview/llfloatersnapshot.h @@ -153,6 +153,7 @@ public: static void update(); void onExtendFloater(); + void on360Snapshot(); static LLFloaterSnapshot* getInstance(); static LLFloaterSnapshot* findInstance(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 5a05f89758..62d73063aa 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -33,6 +33,7 @@ #include "llcommandhandler.h" #include "llcompilequeue.h" #include "llfasttimerview.h" +#include "llfloater360capture.h" #include "llfloaterabout.h" #include "llfloateraddpaymentmethod.h" #include "llfloaterauction.h" @@ -195,6 +196,7 @@ void LLViewerFloaterReg::registerFloaters() // *NOTE: Please keep these alphabetized for easier merges LLFloaterAboutUtil::registerFloater(); + LLFloaterReg::add("360capture", "floater_360capture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("block_timers", "floater_fast_timers.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("about_land", "floater_about_land.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("add_payment_method", "floater_add_payment_method.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index d35dbda907..c428e73a6e 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1746,8 +1746,16 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ media_source->cookies_enabled( cookies_enabled || clean_browser); // collect 'javascript enabled' setting from prefs and send to embedded browser - bool javascript_enabled = gSavedSettings.getBOOL( "BrowserJavascriptEnabled" ); - media_source->setJavascriptEnabled( javascript_enabled || clean_browser); + bool javascript_enabled = gSavedSettings.getBOOL("BrowserJavascriptEnabled"); + media_source->setJavascriptEnabled(javascript_enabled || clean_browser); + + // collect 'web security disabled' (see Chrome --web-security-disabled) setting from prefs and send to embedded browser + bool web_security_disabled = gSavedSettings.getBOOL("BrowserWebSecurityDisabled"); + media_source->setWebSecurityDisabled(web_security_disabled || clean_browser); + + // collect setting indicates if local file access from file URLs is allowed from prefs and send to embedded browser + bool file_access_from_file_urls = gSavedSettings.getBOOL("BrowserFileAccessFromFileUrls"); + media_source->setFileAccessFromFileUrlsEnabled(file_access_from_file_urls || clean_browser); // As of SL-15559 PDF files do not load in CEF v91 we enable plugins // but explicitly disable Flash (PDF support in CEF is now treated as a plugin) @@ -1907,6 +1915,15 @@ void LLViewerMediaImpl::loadURI() } } +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::executeJavaScript(const std::string& code) +{ + if (mMediaSource) + { + mMediaSource->executeJavaScript(code); + } +} + ////////////////////////////////////////////////////////////////////////////////////////// void LLViewerMediaImpl::setSize(int width, int height) { @@ -3212,8 +3229,19 @@ void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginCla case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: { - //llinfos << "Media event - file download requested - filename is " << self->getFileDownloadFilename() << llendl; - LLNotificationsUtil::add("MediaFileDownloadUnsupported"); + LL_DEBUGS("Media") << "Media event - file download requested - filename is " << plugin->getFileDownloadFilename() << LL_ENDL; + // pick a file from SAVE FILE dialog + + // need a better algorithm that this or else, pass in type of save type + // from event that initiated it - this is okay for now - only thing + // that saves is 360s + std::string suggested_filename = plugin->getFileDownloadFilename(); + LLFilePicker::ESaveFilter filter = LLFilePicker::FFSAVE_ALL; + if (suggested_filename.find(".jpg") != std::string::npos || suggested_filename.find(".jpeg") != std::string::npos) + filter = LLFilePicker::FFSAVE_JPEG; + if (suggested_filename.find(".png") != std::string::npos) + filter = LLFilePicker::FFSAVE_PNG; + init_threaded_picker_save_dialog(plugin, filter, suggested_filename); } break; diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index 8bf1ad2441..71cec5125d 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -207,6 +207,7 @@ public: bool initializeMedia(const std::string& mime_type); bool initializePlugin(const std::string& media_type); void loadURI(); + void executeJavaScript(const std::string& code); LLPluginClassMedia* getMediaPlugin() { return mMediaSource.get(); } void setSize(int width, int height); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index ad81cb07c1..0eda1523ff 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1263,12 +1263,55 @@ class LLAdvancedDumpScriptedCamera : public view_listener_t class LLAdvancedDumpRegionObjectCache : public view_listener_t { bool handleEvent(const LLSD& userdata) -{ + { handle_dump_region_object_cache(NULL); return true; } }; +class LLAdvancedInterestListFullUpdate : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLSD request; + LLSD body; + static bool using_360 = false; + + if (using_360) + { + body["mode"] = LLSD::String("default"); + } + else + { + body["mode"] = LLSD::String("360"); + } + using_360 = !using_360; + + if (gAgent.requestPostCapability("InterestList", body, [](const LLSD& response) + { + LL_INFOS("360Capture") << + "InterestList capability responded: \n" << + ll_pretty_print_sd(response) << + LL_ENDL; + })) + { + LL_INFOS("360Capture") << + "Successfully posted an InterestList capability request with payload: \n" << + ll_pretty_print_sd(body) << + LL_ENDL; + return true; + } + else + { + LL_INFOS("360Capture") << + "Unable to post an InterestList capability request with payload: \n" << + ll_pretty_print_sd(body) << + LL_ENDL; + return false; + } + } +}; + class LLAdvancedBuyCurrencyTest : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -2309,11 +2352,10 @@ class LLAdvancedLeaveAdminStatus : public view_listener_t // Advanced > Debugging // ////////////////////////// - class LLAdvancedForceErrorBreakpoint : public view_listener_t { bool handleEvent(const LLSD& userdata) - { + { force_error_breakpoint(NULL); return true; } @@ -9189,6 +9231,7 @@ void initialize_menus() // Advanced > World view_listener_t::addMenu(new LLAdvancedDumpScriptedCamera(), "Advanced.DumpScriptedCamera"); view_listener_t::addMenu(new LLAdvancedDumpRegionObjectCache(), "Advanced.DumpRegionObjectCache"); + view_listener_t::addMenu(new LLAdvancedInterestListFullUpdate(), "Advanced.InterestListFullUpdate"); // Advanced > UI commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 39c891c9c1..7737a2753f 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3745,6 +3745,7 @@ void process_kill_object(LLMessageSystem *mesgsys, void **user_data) { LLColor4 color(0.f,1.f,0.f,1.f); gPipeline.addDebugBlip(objectp->getPositionAgent(), color); + LL_DEBUGS("MessageBlip") << "Kill blip for local " << local_id << " at " << objectp->getPositionAgent() << LL_ENDL; } // Do the kill diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index b88baf6aa7..31e80eb865 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -2402,6 +2402,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, color.setVec(1.f, 0.f, 0.f, 1.f); } gPipeline.addDebugBlip(getPositionAgent(), color); + LL_DEBUGS("MessageBlip") << "Update type " << (S32)update_type << " blip for local " << mLocalID << " at " << getPositionAgent() << LL_ENDL; } const F32 MAG_CUTOFF = F_APPROXIMATELY_ZERO; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 7628a6c7ef..df84938eac 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -2956,6 +2956,8 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("IncrementCOFVersion"); AISAPI::getCapNames(capabilityNames); + capabilityNames.append("InterestList"); + capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetExperiences"); capabilityNames.append("AgentExperiences"); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 1d13a306ef..81e19e9fe2 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -5160,6 +5160,83 @@ BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei return ret; } +BOOL LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height) +{ + gDisplaySwapBuffers = FALSE; + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + setCursor(UI_CURSOR_WAIT); + + BOOL prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI) ? TRUE : FALSE; + if (prev_draw_ui != false) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + LLPipeline::sShowHUDAttachments = FALSE; + LLRect window_rect = getWorldViewRectRaw(); + + S32 original_width = LLPipeline::sRenderDeferred ? gPipeline.mDeferredScreen.getWidth() : gViewerWindow->getWorldViewWidthRaw(); + S32 original_height = LLPipeline::sRenderDeferred ? gPipeline.mDeferredScreen.getHeight() : gViewerWindow->getWorldViewHeightRaw(); + + LLRenderTarget scratch_space; + U32 color_fmt = GL_RGBA; + const bool use_depth_buffer = true; + const bool use_stencil_buffer = true; + if (scratch_space.allocate(image_width, image_height, color_fmt, use_depth_buffer, use_stencil_buffer)) + { + if (gPipeline.allocateScreenBuffer(image_width, image_height)) + { + mWorldViewRectRaw.set(0, image_height, image_width, 0); + + scratch_space.bindTarget(); + } + else + { + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + } + } + + const U32 subfield = 0; + const bool do_rebuild = true; + const F32 zoom = 1.0; + const bool for_snapshot = TRUE; + display(do_rebuild, zoom, subfield, for_snapshot); + + glReadPixels( + 0, 0, + image_width, + image_height, + GL_RGB, GL_UNSIGNED_BYTE, + raw->getData() + ); + stop_glerror(); + + gDisplaySwapBuffers = FALSE; + gDepthDirty = TRUE; + + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + if (prev_draw_ui != false) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + } + + LLPipeline::sShowHUDAttachments = TRUE; + + setCursor(UI_CURSOR_ARROW); + + gPipeline.resetDrawOrders(); + mWorldViewRectRaw = window_rect; + scratch_space.flush(); + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + + return true; +} + void LLViewerWindow::destroyWindow() { if (mWindow) diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 8a6df613dc..dd1a32edef 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -357,7 +357,10 @@ public: BOOL saveSnapshot(const std::string& filename, S32 image_width, S32 image_height, BOOL show_ui = TRUE, BOOL show_hud = TRUE, BOOL do_rebuild = FALSE, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, LLSnapshotModel::ESnapshotFormat format = LLSnapshotModel::SNAPSHOT_FORMAT_BMP); BOOL rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, BOOL keep_window_aspect = TRUE, BOOL is_texture = FALSE, BOOL show_ui = TRUE, BOOL show_hud = TRUE, BOOL do_rebuild = FALSE, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, S32 max_size = MAX_SNAPSHOT_IMAGE_SIZE); - BOOL thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, BOOL show_ui, BOOL show_hud, BOOL do_rebuild, LLSnapshotModel::ESnapshotLayerType type); + + BOOL simpleSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height); + + BOOL thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, BOOL show_ui, BOOL show_hud, BOOL do_rebuild, LLSnapshotModel::ESnapshotLayerType type); BOOL isSnapshotLocSet() const; void resetSnapshotLoc() const; diff --git a/indra/newview/skins/default/html/common/equirectangular/default.html b/indra/newview/skins/default/html/common/equirectangular/default.html new file mode 100644 index 0000000000..227b306590 --- /dev/null +++ b/indra/newview/skins/default/html/common/equirectangular/default.html @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/indra/newview/skins/default/html/common/equirectangular/eqr_gen.html b/indra/newview/skins/default/html/common/equirectangular/eqr_gen.html new file mode 100644 index 0000000000..855c26c651 --- /dev/null +++ b/indra/newview/skins/default/html/common/equirectangular/eqr_gen.html @@ -0,0 +1,149 @@ + + + + + + + + + + + + + +

+
Preview Quality
+ + \ No newline at end of file diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 03878d9fe7..a36b859b6c 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -131,7 +131,8 @@ with the same filename but different name - + + diff --git a/indra/newview/skins/default/textures/toolbar_icons/360_capture.png b/indra/newview/skins/default/textures/toolbar_icons/360_capture.png new file mode 100644 index 0000000000000000000000000000000000000000..163cebe29fc7ff3c510d78e5769aca0d38aec1bb GIT binary patch literal 793 zcmV+!1LpjRP)oNtFE4uRk9F9aH)Ya8ROG`@% znM|gfpP#4Y<>e43XEK>I#@_}92PvD)entL6ut2>+>`nrnak9hVpw-n?vRbXzs;a6E z*zI=9E>xLGh5B$vxYI-Rbesi~<`sZ^Gm z&E|7sV`KfLrKJ-K3kwvF$7LRmCl`ywNUPPRP&WmKWa6BB;r6_+m_Dyh4>#d=t*xy+ zDwV3n>-ADgOG|HCTicCjG+OwBBBId1jY{|wtgo*Zwz#>u*=95vk4sq^jYf;c5AY6? zGuyBM*9`_kBX*B+xm@7{6?$M@p1@;n($UeOPb3m6`3(Z=mXw~6JAvlgupwOE15f$k zBDkGSXZgs;$jkf|1NryH$H)1Ay5KS&NWNDNY>_5REkOO3?jv|2DG|cp2V^#{XQ}T5%EiLSBH14H~H7 zEKI?Bs4u>>QdGiKI013+LPUJq1qRpwFTf5foBbY1u?LR8K|XBa-5h*?cMvEp_$|Ny XdK6>x;TK{r00000NkvXXu0mjfgH?8S literal 0 HcmV?d00001 diff --git a/indra/newview/skins/default/xui/en/floater_360capture.xml b/indra/newview/skins/default/xui/en/floater_360capture.xml new file mode 100644 index 0000000000..c2959b9853 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_360capture.xml @@ -0,0 +1,124 @@ + + + + + Quality level + + + + + + + + + @@ -106,7 +106,7 @@ layout="topleft" left="335" name="btn_restore_defaults" - top="330" + top="385" width="130"> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 72cce2208f..52afe9164e 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -733,6 +733,15 @@ function="Floater.Show" parameter="snapshot" /> + + + + + + + + + 360 Capture About land Outfits Complete avatars @@ -4154,6 +4156,8 @@ Try enclosing path to the editor with double quotes. Camera controls Voice settings + Capture a 360 panorama image Information about the land you're visiting Change your avatar Choose a complete avatar diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 41da8fa328..7e24c1f800 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -158,18 +158,12 @@ class ViewerManifest(LLManifest): self.path("*/xui/*/widgets/*.xml") self.path("*/*.xml") - # Local HTML files (e.g. loading screen) - # The claim is that we never use local html files any - # longer. But rather than commenting out this block, let's - # rename every html subdirectory as html.old. That way, if - # we're wrong, a user actually does have the relevant - # files; s/he just needs to rename every html.old - # directory back to html to recover them. - with self.prefix(src="*/html", dst="*/html.old"): - self.path("*.png") - self.path("*/*/*.html") - self.path("*/*/*.gif") - + # Update: 2017-11-01 CP Now we store app code in the html folder + # Initially the HTML/JS code to render equirectangular + # images for the 360 capture feature but more to follow. + with self.prefix(src="*/html", dst="*/html"): + self.path("*/*/*/*.js") + self.path("*/*/*.html") #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing From f78cb1c4ff7608a05976d6be7b568fbbbf36202b Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 20 Aug 2021 11:19:31 -0700 Subject: [PATCH 30/52] DRTVWR-534: collection of minor UI tweaks and fixes via XUI changes --- .../default/xui/en/floater_360capture.xml | 53 +++++++++++-------- .../skins/default/xui/en/menu_viewer.xml | 2 +- .../newview/skins/default/xui/en/strings.xml | 4 +- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_360capture.xml b/indra/newview/skins/default/xui/en/floater_360capture.xml index c2959b9853..f24639d81a 100644 --- a/indra/newview/skins/default/xui/en/floater_360capture.xml +++ b/indra/newview/skins/default/xui/en/floater_360capture.xml @@ -7,7 +7,7 @@ name="360capture" help_topic="360capture" save_rect="true" - title="360 Capture" + title="360 CAPTURE" width="800"> + name="ui_panel_left">