Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+74
View File
@@ -0,0 +1,74 @@
# -*- cmake -*-
project(llfilesystem)
include(00-Common)
include(LLCommon)
set(llfilesystem_SOURCE_FILES
lldir.cpp
lldiriterator.cpp
lllfsthread.cpp
lldiskcache.cpp
llfilesystem.cpp
)
set(llfilesystem_HEADER_FILES
CMakeLists.txt
lldir.h
lldirguard.h
lldiriterator.h
lllfsthread.h
lldiskcache.h
llfilesystem.h
)
if (DARWIN)
LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.mm)
LIST(APPEND llfilesystem_SOURCE_FILES lldir_utils_objc.h)
LIST(APPEND llfilesystem_SOURCE_FILES lldir_mac.cpp)
LIST(APPEND llfilesystem_HEADER_FILES lldir_mac.h)
endif (DARWIN)
if (LINUX)
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
PROPERTIES COMPILE_FLAGS
"-DAPP_RO_DATA_DIR=\\\"${APP_SHARE_DIR}\\\""
)
endif (INSTALL)
endif (LINUX)
if (WINDOWS)
LIST(APPEND llfilesystem_SOURCE_FILES lldir_win32.cpp)
LIST(APPEND llfilesystem_HEADER_FILES lldir_win32.h)
endif (WINDOWS)
list(APPEND llfilesystem_SOURCE_FILES ${llfilesystem_HEADER_FILES})
add_library (llfilesystem ${llfilesystem_SOURCE_FILES})
target_link_libraries(llfilesystem
llcommon
)
target_include_directories( llfilesystem INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# Add tests
if (LL_TESTS)
include(LLAddBuildTest)
# UNIT TESTS
SET(llfilesystem_TEST_SOURCE_FILES
lldiriterator.cpp
)
LL_ADD_PROJECT_UNIT_TESTS(llfilesystem "${llfilesystem_TEST_SOURCE_FILES}")
# INTEGRATION TESTS
set(test_libs llmath llcommon llfilesystem )
# TODO: Some of these need refactoring to be proper Unit tests rather than Integration tests.
LL_ADD_INTEGRATION_TEST(lldir "" "${test_libs}")
endif (LL_TESTS)
File diff suppressed because it is too large Load Diff
+362
View File
@@ -0,0 +1,362 @@
/**
* @file lldir.h
* @brief Definition of directory utilities class
*
* $LicenseInfo:firstyear=2000&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_LLDIR_H
#define LL_LLDIR_H
// these numbers are read from settings_files.xml, so we need to be explicit
typedef enum ELLPath
{
LL_PATH_NONE = 0,
LL_PATH_USER_SETTINGS = 1,
LL_PATH_APP_SETTINGS = 2,
LL_PATH_PER_SL_ACCOUNT = 3, // returns/expands to blank string if we don't know the account name yet
LL_PATH_CACHE = 4,
LL_PATH_CHARACTER = 5,
LL_PATH_HELP = 6,
LL_PATH_LOGS = 7,
LL_PATH_TEMP = 8,
LL_PATH_SKINS = 9,
LL_PATH_TOP_SKIN = 10,
LL_PATH_CHAT_LOGS = 11,
LL_PATH_PER_ACCOUNT_CHAT_LOGS = 12,
LL_PATH_USER_SKIN = 14,
LL_PATH_LOCAL_ASSETS = 15,
LL_PATH_EXECUTABLE = 16,
LL_PATH_DEFAULT_SKIN = 17,
LL_PATH_FONTS = 18,
LL_PATH_DUMP = 19,
// [SL:KB] - Patch: Viewer-Skins | mS: 2010-10-19 (Catznip-2.4)
LL_PATH_TOP_SKINTHEME = 20,
// [/SL:KB]
// <FS:TT> Firestorm data
LL_PATH_FS_RESOURCES = 21,
// </FS:TT>
// <FS:Ansariel> Sound cache
LL_PATH_FS_SOUND_CACHE = 22,
// </FS:Ansariel>
LL_PATH_LAST
} ELLPath;
/// Directory operations
class LLDir
{
public:
LLDir();
virtual ~LLDir();
// app_name - Usually SecondLife, used for creating settings directories
// in OS-specific location, such as C:\Documents and Settings
// app_read_only_data_dir - Usually the source code directory, used
// for test applications to read newview data files.
virtual void initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir = "") = 0;
virtual S32 deleteFilesInDir(const std::string &dirname, const std::string &mask);
U32 deleteDirAndContents(const std::string& dir_name);
std::vector<std::string> getFilesInDir(const std::string &dirname);
// pure virtual functions
virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask) = 0;
/// Walk the files in a directory, with file pattern matching
// <AO> Used by LGG Selection beams, do not remove
virtual bool getNextFileInDir(const std::string& dirname, ///< directory path - must end in trailing slash!
const std::string& mask, ///< file pattern string (use "*" for all)
std::string& fname ///< output: found file name
) = 0;
// </AO>
/**<
* @returns true if a file was found, false if the entire directory has been scanned.
*
* @note that this function is NOT thread safe
*
* This function may not be used to scan part of a directory, then start a new search of a different
* directory, and then restart the first search where it left off; the entire search must run to
* completion or be abandoned - there is no restart.
*
* @bug: See http://jira.secondlife.com/browse/VWR-23697
* and/or the tests in test/lldir_test.cpp
* This is known to fail with patterns that have both:
* a wildcard left of a . and more than one sequential ? right of a .
* the pattern foo.??x appears to work
* but *.??x or foo?.??x do not
*
* @todo this really should be rewritten as an iterator object, and the
* filtering should be done in a platform-independent way.
*/
virtual std::string getCurPath() = 0;
virtual bool fileExists(const std::string &filename) const = 0;
const std::string findFile(const std::string& filename, const std::vector<std::string> filenames) const;
const std::string findFile(const std::string& filename, const std::string& searchPath1 = "", const std::string& searchPath2 = "", const std::string& searchPath3 = "") const;
virtual std::string getLLPluginLauncher() = 0; // full path and name for the plugin shell
virtual std::string getLLPluginFilename(std::string base_name) = 0; // full path and name to the plugin DSO for this base_name (i.e. 'FOO' -> '/bar/baz/libFOO.so')
const std::string &getExecutablePathAndName() const; // Full pathname of the executable
const std::string &getAppName() const; // install directory under progams/ ie "SecondLife"
const std::string &getExecutableDir() const; // Directory where the executable is located
const std::string &getExecutableFilename() const;// Filename of .exe
const std::string &getWorkingDir() const; // Current working directory
const std::string &getAppRODataDir() const; // Location of read-only data files
const std::string &getOSUserDir() const; // Location of the os-specific user dir
const std::string &getOSUserAppDir() const; // Location of the os-specific user app dir
const std::string &getLindenUserDir() const; // Location of the Linden user dir.
const std::string &getChatLogsDir() const; // Location of the chat logs dir.
const std::string &getDumpDir() const; // Location of the per-run dump dir.
bool dumpDirExists() const;
const std::string &getPerAccountChatLogsDir() const; // Location of the per account chat logs dir.
const std::string &getTempDir() const; // Common temporary directory
const std::string getCacheDir(bool get_default = false) const; // Location of the cache.
const std::string &getOSCacheDir() const; // location of OS-specific cache folder (may be empty string)
const std::string &getCAFile() const; // File containing TLS certificate authorities
const std::string &getDirDelimiter() const; // directory separator for platform (ie. '\' or '/' or ':')
const std::string &getDefaultSkinDir() const; // folder for default skin. e.g. c:\program files\second life\skins\default
const std::string &getSkinDir() const; // User-specified skin folder.
// [SL:KB] - Patch: Viewer-Skins | Checked: 2010-10-20 (Catznip-2.2)
const std::string &getSkinThemeDir() const; // User-specified skin theme override folder.
// [/SL:KB]
const std::string &getUserDefaultSkinDir() const; // dir with user modifications to default skin
const std::string &getUserSkinDir() const; // User-specified skin folder with user modifications. e.g. c:\documents and settings\username\application data\second life\skins\curskin
const std::string getSkinBaseDir() const; // folder that contains all installed skins (not user modifications). e.g. c:\program files\second life\skins
const std::string &getLLPluginDir() const; // Directory containing plugins and plugin shell
const std::string &getUserName() const;
// <FS:Ansariel> Sound cache
const std::string &getSoundCacheDir() const;
// </FS:Ansariel>
// Expanded filename
std::string getExpandedFilename(ELLPath location, const std::string &filename) const;
std::string getExpandedFilename(ELLPath location, const std::string &subdir, const std::string &filename) const;
std::string getExpandedFilename(ELLPath location, const std::string &subdir1, const std::string &subdir2, const std::string &filename) const;
// Base and Directory name extraction
std::string getBaseFileName(const std::string& filepath, bool strip_exten = false) const;
std::string getDirName(const std::string& filepath) const;
std::string getExtension(const std::string& filepath) const; // Excludes '.', e.g getExtension("foo.wav") == "wav"
// these methods search the various skin paths for the specified file in the following order:
// getUserSkinDir(), getUserDefaultSkinDir(), getSkinThemeDir(), getSkinDir(), getDefaultSkinDir()
/// param value for findSkinnedFilenames(), explained below
enum ESkinConstraint { CURRENT_SKIN, ALL_SKINS };
/**
* Given a filename within skin, return an ordered sequence of paths to
* search. Nonexistent files will be filtered out -- which means that the
* vector might be empty.
*
* @param subdir Identify top-level skin subdirectory by passing one of
* LLDir::XUI (file lives under "xui" subtree), LLDir::TEXTURES (file
* lives under "textures" subtree), LLDir::SKINBASE (file lives at top
* level of skin subdirectory).
* @param filename Desired filename within subdir within skin, e.g.
* "panel_login.xml". DO NOT prepend (e.g.) "xui" or the desired language.
* @param constraint Callers perform two different kinds of processing.
* When fetching a XUI file, for instance, the existence of @a filename in
* the specified skin completely supercedes any @a filename in the default
* skin. For that case, leave the default @a constraint=CURRENT_SKIN. The
* returned vector will contain only
* ".../<i>current_skin</i>/xui/en/<i>filename</i>",
* ".../<i>current_skin</i>/xui/<i>current_language</i>/<i>filename</i>".
* But for (e.g.) "strings.xml", we want a given skin to be able to
* override only specific entries from the default skin. Any string not
* defined in the specified skin will be sought in the default skin. For
* that case, pass @a constraint=ALL_SKINS. The returned vector will
* contain at least ".../default/xui/en/strings.xml",
* ".../default/xui/<i>current_language</i>/strings.xml",
* ".../<i>current_skin</i>/xui/en/strings.xml",
* ".../<i>current_skin</i>/xui/<i>current_language</i>/strings.xml".
*/
std::vector<std::string> findSkinnedFilenames(const std::string& subdir,
const std::string& filename,
ESkinConstraint constraint=CURRENT_SKIN) const;
/// Values for findSkinnedFilenames(subdir) parameter
static const char *XUI, *TEXTURES, *SKINBASE;
/**
* Return the base-language pathname from findSkinnedFilenames(), or
* the empty string if no such file exists. Parameters are identical to
* findSkinnedFilenames(). This is shorthand for capturing the vector
* returned by findSkinnedFilenames(), checking for empty() and then
* returning front().
*/
std::string findSkinnedFilenameBaseLang(const std::string &subdir,
const std::string &filename,
ESkinConstraint constraint=CURRENT_SKIN) const;
/**
* Return the "most localized" pathname from findSkinnedFilenames(), or
* the empty string if no such file exists. Parameters are identical to
* findSkinnedFilenames(). This is shorthand for capturing the vector
* returned by findSkinnedFilenames(), checking for empty() and then
* returning back().
*/
std::string findSkinnedFilename(const std::string &subdir,
const std::string &filename,
ESkinConstraint constraint=CURRENT_SKIN) const;
// random filename in common temporary directory
std::string getTempFilename() const;
static std::string getDumpLogsDirPath(const std::string &file_name = "");
// For producing safe download file names from potentially unsafe ones
static std::string getScrubbedFileName(std::string_view uncleanFileName);
static std::string getForbiddenFileChars();
void setDumpDir( const std::string& path );
virtual void setChatLogsDir(const std::string &path); // Set the chat logs dir to this user's dir
// <FS:CR> Seperate user directories per grid
//virtual void setPerAccountChatLogsDir(const std::string &username); // Set the per user chat log directory.
//virtual void setLindenUserDir(const std::string &username); // Set the linden user dir to this user's dir
virtual void setPerAccountChatLogsDir(const std::string &username, const std::string &gridname);
virtual void setLindenUserDir(const std::string &username, const std::string &gridname);
// </FS:CR>
// [SL:KB] - Patch: Viewer-Skins | Checked: 2010-10-20 (Catznip-3.4)
virtual void setSkinFolder(const std::string& skin_folder, const std::string& theme_folder, const std::string& language);
// [/SL:KB]
// virtual void setSkinFolder(const std::string &skin_folder, const std::string& language);
virtual std::string getSkinFolder() const;
// [SL:KB] - Patch: Viewer-Skins | Checked: 2012-12-26 (Catznip-3.4)
virtual std::string getSkinThemeFolder() const;
// [/SL:KB]
virtual std::string getLanguage() const;
virtual bool setCacheDir(const std::string &path);
virtual void updatePerAccountChatLogsDir();
// <FS:Ansariel> Sound cache
virtual bool setSoundCacheDir(const std::string& path);
// </FS:Ansariel>
virtual void dumpCurrentDirectories(LLError::ELevel level = LLError::LEVEL_DEBUG);
// Utility routine
std::string buildSLOSCacheDir() const;
/// Append specified @a name to @a destpath, separated by getDirDelimiter()
/// if both are non-empty.
void append(std::string& destpath, const std::string& name) const;
/// Variadic form: append @a name0 and @a name1 and arbitrary other @a
/// names to @a destpath, separated by getDirDelimiter() as needed.
template <typename... NAMES>
void append(std::string& destpath, const std::string& name0, const std::string& name1,
const NAMES& ... names) const
{
// In a typical recursion case, we'd accept (destpath, name0, names).
// We accept (destpath, name0, name1, names) because it's important to
// delegate the two-argument case to the non-template implementation.
append(destpath, name0);
append(destpath, name1, names...);
}
/// Append specified @a names to @a path, separated by getDirDelimiter()
/// as needed. Return result, leaving @a path unmodified.
template <typename... NAMES>
std::string add(const std::string& path, const NAMES& ... names) const
{
std::string destpath(path);
append(destpath, names...);
return destpath;
}
protected:
// Does an add() or append() call need a directory delimiter?
typedef std::pair<bool, unsigned short> SepOff;
SepOff needSep(const std::string& path, const std::string& name) const;
// build mSearchSkinDirs without adding duplicates
void addSearchSkinDir(const std::string& skindir);
// Internal to findSkinnedFilenames()
template <typename FUNCTION>
void walkSearchSkinDirs(const std::string& subdir,
const std::vector<std::string>& subsubdirs,
const std::string& filename,
const FUNCTION& function) const;
std::string mAppName; // install directory under progams/ ie "SecondLife"
std::string mExecutablePathAndName; // full path + Filename of .exe
std::string mExecutableFilename; // Filename of .exe
std::string mExecutableDir; // Location of executable
std::string mWorkingDir; // Current working directory
std::string mAppRODataDir; // Location for static app data
std::string mOSUserDir; // OS Specific user directory
std::string mOSUserAppDir; // OS Specific user app directory
std::string mLindenUserDir; // Location for Linden user-specific data
std::string mPerAccountChatLogsDir; // Location for chat logs.
std::string mChatLogsDir; // Location for chat logs.
std::string mCAFile; // Location of the TLS certificate authority PEM file.
std::string mTempDir;
std::string mCacheDir; // cache directory as set by user preference
std::string mDefaultCacheDir; // default cache diretory
std::string mOSCacheDir; // operating system cache dir
std::string mDirDelimiter;
std::string mSkinName; // caller-specified skin name
// [SL:KB] - Patch: Viewer-Skins | Checked: 2012-12-26 (Catznip-3.4)
std::string mSkinThemeName; // Location for current skin theme override
// [/SL:KB]
std::string mSkinBaseDir; // Base for skins paths.
std::string mDefaultSkinDir; // Location for default skin info.
std::string mSkinDir; // Location for current skin info.
// [SL:KB] - Patch: Viewer-Skins | Checked: 2010-10-20 (Catznip-2.2)
std::string mSkinThemeDir; // Location for current skin theme override
// [/SL:KB]
std::string mUserDefaultSkinDir; // Location for default skin info.
std::string mUserSkinDir; // Location for user-modified skin info.
// Skin directories to search, most general to most specific. This order
// works well for composing fine-grained files, in which an individual item
// in a specific file overrides the corresponding item in more general
// files. Of course, for a file-level search, iterate backwards.
std::vector<std::string> mSearchSkinDirs;
std::string mLanguage; // Current viewer language
std::string mLLPluginDir; // Location for plugins and plugin shell
static std::string sDumpDir; // Per-run crash report subdir of log directory.
std::string mUserName; // Current user name
// <FS:Ansariel> Sound cache
std::string mSoundCacheDir; // Sound cache
// </FS:Ansariel>
// <FS:ND> To avoid doing IO calls (expensive) in walkdSearchedSkinDirs cache results.
struct SkinDirFile
{
std::string mName;
mutable bool mExists;
SkinDirFile( std::string const &aName, bool aExists )
: mName( aName )
, mExists( aExists )
{ }
bool operator<( SkinDirFile const &aRHS ) const
{ return mName < aRHS.mName; }
};
typedef std::set< SkinDirFile > tSkinDirCache;
mutable tSkinDirCache mSkinDirCache;
};
void dir_exists_or_crash(const std::string &dir_name);
extern LLDir *gDirUtilp;
#endif // LL_LLDIR_H
+330
View File
@@ -0,0 +1,330 @@
/**
* @file lldir_linux.cpp
* @brief Implementation of directory utilities for linux
*
* $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_linux.h"
#include "llerror.h"
#include "llrand.h"
#include "llstring.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <pwd.h>
static std::string getCurrentUserHome(char* fallback)
{
const uid_t uid = getuid();
struct passwd *pw;
pw = getpwuid(uid);
if ((pw != NULL) && (pw->pw_dir != NULL))
{
return pw->pw_dir;
}
LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL;
auto home_env = LLStringUtil::getoptenv("HOME");
if (home_env)
{
return *home_env;
}
else
{
LL_WARNS() << "Couldn't detect home directory! Falling back to " << fallback << LL_ENDL;
return fallback;
}
}
LLDir_Linux::LLDir_Linux()
{
mDirDelimiter = "/";
mCurrentDirIndex = -1;
mCurrentDirCount = -1;
mDirp = NULL;
char tmp_str[LL_MAX_PATH]; /* Flawfinder: ignore */
if (getcwd(tmp_str, LL_MAX_PATH) == NULL)
{
strcpy(tmp_str, "/tmp");
LL_WARNS() << "Could not get current directory; changing to "
<< tmp_str << LL_ENDL;
if (chdir(tmp_str) == -1)
{
LL_ERRS() << "Could not change directory to " << tmp_str << LL_ENDL;
}
}
mExecutableFilename = "";
mExecutablePathAndName = "";
mExecutableDir = tmp_str;
mWorkingDir = tmp_str;
#ifdef APP_RO_DATA_DIR
mAppRODataDir = APP_RO_DATA_DIR;
#else
mAppRODataDir = tmp_str;
#endif
std::string::size_type build_dir_pos = mExecutableDir.rfind("/build-linux-");
if (build_dir_pos != std::string::npos)
{
// ...we're in a dev checkout
mSkinBaseDir = mExecutableDir.substr(0, build_dir_pos) + "/indra/newview/skins";
LL_INFOS() << "Running in dev checkout with mSkinBaseDir "
<< mSkinBaseDir << LL_ENDL;
}
else
{
// ...normal installation running
mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins";
}
mOSUserDir = getCurrentUserHome(tmp_str);
mOSUserAppDir = "";
mLindenUserDir = "";
char path [32]; /* Flawfinder: ignore */
// *NOTE: /proc/%d/exe doesn't work on FreeBSD. But that's ok,
// because this is the linux implementation.
snprintf (path, sizeof(path), "/proc/%d/exe", (int) getpid ());
int rc = readlink (path, tmp_str, sizeof (tmp_str)-1); /* Flawfinder: ignore */
if ( (rc != -1) && (rc <= ((int) sizeof (tmp_str)-1)) )
{
tmp_str[rc] = '\0'; //readlink() doesn't 0-terminate the buffer
mExecutablePathAndName = tmp_str;
char *path_end;
if ((path_end = strrchr(tmp_str,'/')))
{
*path_end = '\0';
mExecutableDir = tmp_str;
mWorkingDir = tmp_str;
mExecutableFilename = path_end+1;
}
else
{
mExecutableFilename = tmp_str;
}
}
mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin";
// *TODO: don't use /tmp, use $HOME/.secondlife/tmp or something.
mTempDir = "/tmp";
}
LLDir_Linux::~LLDir_Linux()
{
}
// Implementation
void LLDir_Linux::initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir)
{
// Allow override so test apps can read newview directory
if (!app_read_only_data_dir.empty())
{
mAppRODataDir = app_read_only_data_dir;
mSkinBaseDir = add(mAppRODataDir, "skins");
}
mAppName = app_name;
std::string upper_app_name(app_name);
LLStringUtil::toUpper(upper_app_name);
auto app_home_env(LLStringUtil::getoptenv(upper_app_name + "_USER_DIR"));
if (app_home_env)
{
// user has specified own userappdir i.e. $SECONDLIFE_USER_DIR
mOSUserAppDir = *app_home_env;
}
else
{
// traditionally on unixoids, MyApp gets ~/.myapp dir for data
mOSUserAppDir = mOSUserDir;
mOSUserAppDir += "/";
mOSUserAppDir += ".";
std::string lower_app_name(app_name);
LLStringUtil::toLower(lower_app_name);
mOSUserAppDir += lower_app_name;
}
// create any directories we expect to write to.
int res = LLFile::mkdir(mOSUserAppDir);
if (res == -1)
{
LL_WARNS() << "Couldn't create app user dir " << mOSUserAppDir << LL_ENDL;
LL_WARNS() << "Default to base dir" << mOSUserDir << LL_ENDL;
mOSUserAppDir = mOSUserDir;
}
res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << LL_ENDL;
}
res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << LL_ENDL;
}
res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << LL_ENDL;
}
mCAFile = getExpandedFilename(LL_PATH_EXECUTABLE, "ca-bundle.crt");
}
U32 LLDir_Linux::countFilesInDir(const std::string &dirname, const std::string &mask)
{
U32 file_count = 0;
glob_t g;
std::string tmp_str;
tmp_str = dirname;
tmp_str += mask;
if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0)
{
file_count = g.gl_pathc;
globfree(&g);
}
return (file_count);
}
// get the next file in the directory
// AO: Used by LGG Selection Beams
bool LLDir_Linux::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname)
{
glob_t g;
bool result = false;
fname = "";
if(!(dirname == mCurrentDir))
{
// different dir specified, close old search
mCurrentDirIndex = -1;
mCurrentDirCount = -1;
mCurrentDir = dirname;
}
std::string tmp_str;
tmp_str = dirname;
tmp_str += mask;
if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0)
{
if(g.gl_pathc > 0)
{
if((int)g.gl_pathc != mCurrentDirCount)
{
// Number of matches has changed since the last search, meaning a file has been added or deleted.
// Reset the index.
mCurrentDirIndex = -1;
mCurrentDirCount = g.gl_pathc;
}
mCurrentDirIndex++;
if(mCurrentDirIndex < (int)g.gl_pathc)
{
// LL_INFOS() << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << LL_ENDL;
// The API wants just the filename, not the full path.
//fname = g.gl_pathv[mCurrentDirIndex];
char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/');
if(s == NULL)
s = g.gl_pathv[mCurrentDirIndex];
else if(s[0] == '/')
s++;
fname = s;
result = true;
}
}
globfree(&g);
}
return(result);
}
std::string LLDir_Linux::getCurPath()
{
char tmp_str[LL_MAX_PATH]; /* Flawfinder: ignore */
if (getcwd(tmp_str, LL_MAX_PATH) == NULL)
{
LL_WARNS() << "Could not get current directory" << LL_ENDL;
tmp_str[0] = '\0';
}
return tmp_str;
}
bool LLDir_Linux::fileExists(const std::string &filename) const
{
struct stat stat_data;
// Check the age of the file
// Now, we see if the files we've gathered are recent...
int res = stat(filename.c_str(), &stat_data);
if (!res)
{
return true;
}
else
{
return false;
}
}
/*virtual*/ std::string LLDir_Linux::getLLPluginLauncher()
{
return gDirUtilp->getExecutableDir() + gDirUtilp->getDirDelimiter() +
"SLPlugin";
}
/*virtual*/ std::string LLDir_Linux::getLLPluginFilename(std::string base_name)
{
return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() +
"lib" + base_name + ".so";
}
+65
View File
@@ -0,0 +1,65 @@
/**
* @file lldir_linux.h
* @brief Definition of directory utilities class for linux
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#if !LL_LINUX
#error This header must not be included when compiling for any target other than Linux. Consider including lldir.h instead.
#endif // !LL_LINUX
#ifndef LL_LLDIR_LINUX_H
#define LL_LLDIR_LINUX_H
#include "lldir.h"
#include <dirent.h>
#include <errno.h>
class LLDir_Linux : public LLDir
{
public:
LLDir_Linux();
virtual ~LLDir_Linux();
/*virtual*/ void initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir);
virtual std::string getCurPath();
virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask);
virtual bool getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname);
/*virtual*/ bool fileExists(const std::string &filename) const;
/*virtual*/ std::string getLLPluginLauncher();
/*virtual*/ std::string getLLPluginFilename(std::string base_name);
private:
DIR *mDirp;
int mCurrentDirIndex;
int mCurrentDirCount;
std::string mCurrentDir;
};
#endif // LL_LLDIR_LINUX_H
+297
View File
@@ -0,0 +1,297 @@
/**
* @file lldir_mac.cpp
* @brief Implementation of directory utilities for macOS
*
* $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$
*/
#if LL_DARWIN
#include "linden_common.h"
#include "lldir_mac.h"
#include "llerror.h"
#include "llrand.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <boost/filesystem.hpp>
#include "lldir_utils_objc.h"
// --------------------------------------------------------------------------------
static bool CreateDirectory(const std::string &parent,
const std::string &child,
std::string *fullname)
{
boost::filesystem::path p(parent);
p /= child;
if (fullname)
*fullname = std::string(p.string());
if (! boost::filesystem::create_directory(p))
{
return (boost::filesystem::is_directory(p));
}
return true;
}
// --------------------------------------------------------------------------------
LLDir_Mac::LLDir_Mac()
{
mDirDelimiter = "/";
const std::string secondLifeString = "Firestorm";
std::string executablepathstr = getSystemExecutableFolder();
//NOTE: LLINFOS/LLERRS will not output to log here. The streams are not initialized.
if (!executablepathstr.empty())
{
// mExecutablePathAndName
mExecutablePathAndName = executablepathstr;
boost::filesystem::path executablepath(executablepathstr);
# ifndef BOOST_SYSTEM_NO_DEPRECATED
#endif
mExecutableFilename = executablepath.filename().string();
mExecutableDir = executablepath.parent_path().string();
// mAppRODataDir
std::string resourcepath = getSystemResourceFolder();
mAppRODataDir = resourcepath;
// *NOTE: When running in a dev tree, use the copy of
// skins in indra/newview/ rather than in the application bundle. This
// mirrors Windows dev environment behavior and allows direct checkin
// of edited skins/xui files. JC
// MBW -- This keeps the mac application from finding other things.
// If this is really for skins, it should JUST apply to skins.
std::string::size_type build_dir_pos = mExecutableDir.rfind("/build-darwin-");
if (build_dir_pos != std::string::npos)
{
// ...we're in a dev checkout
mSkinBaseDir = mExecutableDir.substr(0, build_dir_pos)
+ "/indra/newview/skins";
LL_INFOS() << "Running in dev checkout with mSkinBaseDir "
<< mSkinBaseDir << LL_ENDL;
}
else
{
// ...normal installation running
mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins";
}
// mOSUserDir
std::string appdir = getSystemApplicationSupportFolder();
std::string rootdir;
//Create root directory
if (CreateDirectory(appdir, secondLifeString, &rootdir))
{
// Save the full path to the folder
mOSUserDir = rootdir;
// Create our sub-dirs
CreateDirectory(rootdir, std::string("data"), NULL);
CreateDirectory(rootdir, std::string("logs"), NULL);
CreateDirectory(rootdir, std::string("user_settings"), NULL);
CreateDirectory(rootdir, std::string("browser_profile"), NULL);
}
//mOSCacheDir
std::string cachedir = getSystemCacheFolder();
if (!cachedir.empty())
{
mOSCacheDir = cachedir;
//TODO: This changes from ~/Library/Cache/Secondlife to ~/Library/Cache/com.app.secondlife/Secondlife. Last dir level could go away.
//<FS:TS> Adjust the cache directory to match what's expected in lldir.
//CreateDirectory(mOSCacheDir, secondLifeString, NULL);
std::string FSCacheDirName = secondLifeString;
// This was lifted from Cinder's fix for FIRE-8226.
#ifdef OPENSIM
#if ADDRESS_SIZE == 64
FSCacheDirName.append("OS_x64");
#else
FSCacheDirName.append("OS");
#endif
#else
#if ADDRESS_SIZE == 64
FSCacheDirName.append("_x64");
#endif
#endif // OPENSIM
CreateDirectory(mOSCacheDir, FSCacheDirName, NULL);
//</FS:TS>
}
// mOSUserAppDir
mOSUserAppDir = mOSUserDir;
// mTempDir
//Aura 120920 boost::filesystem::temp_directory_path() not yet implemented on mac. :(
std::string tmpdir = getSystemTempFolder();
if (!tmpdir.empty())
{
CreateDirectory(tmpdir, secondLifeString, &mTempDir);
}
mWorkingDir = getCurPath();
mLLPluginDir = mAppRODataDir + mDirDelimiter + "llplugin";
}
}
LLDir_Mac::~LLDir_Mac()
{
}
// Implementation
void LLDir_Mac::initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir)
{
// Allow override so test apps can read newview directory
if (!app_read_only_data_dir.empty())
{
mAppRODataDir = app_read_only_data_dir;
mSkinBaseDir = add(mAppRODataDir, "skins");
}
mCAFile = add(mAppRODataDir, "ca-bundle.crt");
}
//<FS:TS> Used by LGG's selection beams
U32 LLDir_Mac::countFilesInDir(const std::string &dirname, const std::string &mask)
{
U32 file_count = 0;
glob_t g;
std::string tmp_str;
tmp_str = dirname;
tmp_str += mask;
if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0)
{
file_count = g.gl_pathc;
globfree(&g);
}
return (file_count);
}
// get the next file in the directory
// AO: Used by LGG Selection Beams
bool LLDir_Mac::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname)
{
glob_t g;
bool result = false;
fname = "";
if(!(dirname == mCurrentDir))
{
// different dir specified, close old search
mCurrentDirIndex = -1;
mCurrentDirCount = -1;
mCurrentDir = dirname;
}
std::string tmp_str;
tmp_str = dirname;
tmp_str += mask;
if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0)
{
if(g.gl_pathc > 0)
{
if(g.gl_pathc != mCurrentDirCount)
{
// Number of matches has changed since the last search, meaning a file has been added or deleted.
// Reset the index.
mCurrentDirIndex = -1;
mCurrentDirCount = g.gl_pathc;
}
mCurrentDirIndex++;
if(mCurrentDirIndex < g.gl_pathc)
{
// LL_INFOS() << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << LL_ENDL;
// The API wants just the filename, not the full path.
//fname = g.gl_pathv[mCurrentDirIndex];
char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/');
if(s == NULL)
s = g.gl_pathv[mCurrentDirIndex];
else if(s[0] == '/')
s++;
fname = s;
result = true;
}
}
globfree(&g);
}
return(result);
}
std::string LLDir_Mac::getCurPath()
{
return boost::filesystem::path( boost::filesystem::current_path() ).string();
}
bool LLDir_Mac::fileExists(const std::string &filename) const
{
return boost::filesystem::exists(filename);
}
/*virtual*/ std::string LLDir_Mac::getLLPluginLauncher()
{
return gDirUtilp->getAppRODataDir() + gDirUtilp->getDirDelimiter() +
"SLPlugin.app/Contents/MacOS/SLPlugin";
}
/*virtual*/ std::string LLDir_Mac::getLLPluginFilename(std::string base_name)
{
return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() +
base_name + ".dylib";
}
#endif // LL_DARWIN
+64
View File
@@ -0,0 +1,64 @@
/**
* @file lldir_mac.h
* @brief Definition of directory utilities class for macOS
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#if !LL_DARWIN
#error This header must not be included when compiling for any target other than Mac OS. Consider including lldir.h instead.
#endif // !LL_DARWIN
#ifndef LL_LLDIR_MAC_H
#define LL_LLDIR_MAC_H
#include "lldir.h"
#include <dirent.h>
class LLDir_Mac : public LLDir
{
public:
LLDir_Mac();
virtual ~LLDir_Mac();
/*virtual*/ void initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir);
virtual std::string getCurPath();
virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask);
virtual bool getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname);
virtual bool fileExists(const std::string &filename) const;
/*virtual*/ std::string getLLPluginLauncher();
/*virtual*/ std::string getLLPluginFilename(std::string base_name);
//<FS:TS> Used by LGG's selection beams
private:
int mCurrentDirIndex;
int mCurrentDirCount;
std::string mCurrentDir;
};
#endif // LL_LLDIR_MAC_H
+43
View File
@@ -0,0 +1,43 @@
/**
* @file lldir_utils_objc.h
* @brief Definition of directory utilities class for macOS
*
* $LicenseInfo:firstyear=2020&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$
*/
#if !LL_DARWIN
#error This header must not be included when compiling for any target other than Mac OS. Consider including lldir.h instead.
#endif // !LL_DARWIN
#ifndef LL_LLDIR_UTILS_OBJC_H
#define LL_LLDIR_UTILS_OBJC_H
#include <iostream>
std::string getSystemTempFolder();
std::string getSystemCacheFolder();
std::string getSystemApplicationSupportFolder();
std::string getSystemResourceFolder();
std::string getSystemExecutableFolder();
#endif // LL_LLDIR_UTILS_OBJC_H
+108
View File
@@ -0,0 +1,108 @@
/**
* @file lldir_utils_objc.mm
* @brief Cocoa implementation of directory utilities for macOS
*
* $LicenseInfo:firstyear=2020&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$
*/
#if LL_DARWIN
//WARNING: This file CANNOT use standard linden includes due to conflicts between definitions of BOOL
#include "lldir_utils_objc.h"
#import <Cocoa/Cocoa.h>
std::string getSystemTempFolder()
{
std::string result;
@autoreleasepool {
NSString * tempDir = NSTemporaryDirectory();
if (tempDir == nil)
tempDir = @"/tmp";
result = std::string([tempDir UTF8String]);
}
return result;
}
//findSystemDirectory scoped exclusively to this file.
std::string findSystemDirectory(NSSearchPathDirectory searchPathDirectory,
NSSearchPathDomainMask domainMask)
{
std::string result;
@autoreleasepool {
NSString *path = nil;
// Search for the path
NSArray* paths = NSSearchPathForDirectoriesInDomains(searchPathDirectory,
domainMask,
YES);
if ([paths count])
{
path = [paths objectAtIndex:0];
//HACK: Always attempt to create directory, ignore errors.
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
result = std::string([path UTF8String]);
}
}
return result;
}
std::string getSystemExecutableFolder()
{
std::string result;
@autoreleasepool {
NSString *bundlePath = [[NSBundle mainBundle] executablePath];
result = std::string([bundlePath UTF8String]);
}
return result;
}
std::string getSystemResourceFolder()
{
std::string result;
@autoreleasepool {
NSString *bundlePath = [[NSBundle mainBundle] resourcePath];
result = std::string([bundlePath UTF8String]);
}
return result;
}
std::string getSystemCacheFolder()
{
return findSystemDirectory (NSCachesDirectory,
NSUserDomainMask);
}
std::string getSystemApplicationSupportFolder()
{
return findSystemDirectory (NSApplicationSupportDirectory,
NSUserDomainMask);
}
#endif // LL_DARWIN
+520
View File
@@ -0,0 +1,520 @@
/**
* @file lldir_win32.cpp
* @brief Implementation of directory utilities for windows
*
* $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$
*/
#if LL_WINDOWS
#include "linden_common.h"
#include "lldir_win32.h"
#include "llerror.h"
#include "llstring.h"
#include "stringize.h"
#include "llfile.h"
#include <shlobj.h>
#include <fstream>
#include <direct.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
// Utility stuff to get versions of the sh
#define PACKVERSION(major,minor) MAKELONG(minor,major)
DWORD GetDllVersion(LPCTSTR lpszDllName);
namespace
{ // anonymous
enum class prst { INIT, OPEN, SKIP };
prst state{ prst::INIT };
// This is called so early that we can't count on static objects being
// properly constructed yet, so declare a pointer instead of an instance.
std::ofstream* prelogf = nullptr;
void prelog(const std::string& message)
{
std::optional<std::string> prelog_name;
switch (state)
{
case prst::INIT:
// assume we failed, until we succeed
state = prst::SKIP;
prelog_name = LLStringUtil::getoptenv("PRELOG");
if (! prelog_name)
// no PRELOG variable set, carry on
return;
prelogf = new llofstream(*prelog_name, std::ios_base::app);
if (! (prelogf && prelogf->is_open()))
// can't complain to anybody; how?
return;
// got the log file open, cool!
state = prst::OPEN;
(*prelogf) << "========================================================================"
<< std::endl;
// fall through, don't break
[[fallthrough]];
case prst::OPEN:
(*prelogf) << message << std::endl;
break;
case prst::SKIP:
// either PRELOG isn't set, or we failed to open that pathname
break;
}
}
} // anonymous namespace
#define PRELOG(expression) prelog(STRINGIZE(expression))
LLDir_Win32::LLDir_Win32()
{
// set this first: used by append() and add() methods
mDirDelimiter = "\\";
WCHAR w_str[MAX_PATH];
// Application Data is where user settings go. We rely on $APPDATA being
// correct.
auto APPDATA = LLStringUtil::getoptenv("APPDATA");
if (APPDATA)
{
mOSUserDir = *APPDATA;
}
PRELOG("APPDATA='" << mOSUserDir << "'");
// On Windows, we could have received a plain-ASCII pathname in which
// non-ASCII characters have been munged to '?', or the pathname could
// have been badly encoded and decoded such that we now have garbage
// instead of a valid path. Check that mOSUserDir actually exists.
if (mOSUserDir.empty() || ! fileExists(mOSUserDir))
{
PRELOG("APPDATA does not exist");
//HRESULT okay = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str);
wchar_t *pwstr = NULL;
HRESULT okay = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pwstr);
PRELOG("SHGetKnownFolderPath(FOLDERID_RoamingAppData) returned " << okay);
if (SUCCEEDED(okay) && pwstr)
{
// But of course, only update mOSUserDir if SHGetKnownFolderPath() works.
mOSUserDir = ll_convert_wide_to_string(pwstr);
// Not only that: update our environment so that child processes
// will see a reasonable value as well.
_wputenv_s(L"APPDATA", pwstr);
// SHGetKnownFolderPath() contract requires us to free pwstr
CoTaskMemFree(pwstr);
PRELOG("mOSUserDir='" << mOSUserDir << "'");
}
}
// We want cache files to go on the local disk, even if the
// user is on a network with a "roaming profile".
//
// On Vista this is:
// C:\Users\James\AppData\Local
//
// We used to store the cache in AppData\Roaming, and the installer
// cleans up that version on upgrade. JC
auto LOCALAPPDATA = LLStringUtil::getoptenv("LOCALAPPDATA");
if (LOCALAPPDATA)
{
mOSCacheDir = *LOCALAPPDATA;
}
PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'");
// Windows really does not deal well with pathnames containing non-ASCII
// characters. See above remarks about APPDATA.
if (mOSCacheDir.empty() || ! fileExists(mOSCacheDir))
{
PRELOG("LOCALAPPDATA does not exist");
//HRESULT okay = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str);
wchar_t *pwstr = NULL;
HRESULT okay = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pwstr);
PRELOG("SHGetKnownFolderPath(FOLDERID_LocalAppData) returned " << okay);
if (SUCCEEDED(okay) && pwstr)
{
// But of course, only update mOSCacheDir if SHGetKnownFolderPath() works.
mOSCacheDir = ll_convert_wide_to_string(pwstr);
// Update our environment so that child processes will see a
// reasonable value as well.
_wputenv_s(L"LOCALAPPDATA", pwstr);
// SHGetKnownFolderPath() contract requires us to free pwstr
CoTaskMemFree(pwstr);
PRELOG("mOSCacheDir='" << mOSCacheDir << "'");
}
}
if (GetTempPath(MAX_PATH, w_str))
{
if (wcslen(w_str)) /* Flawfinder: ignore */
{
w_str[wcslen(w_str)-1] = '\0'; /* Flawfinder: ignore */ // remove trailing slash
}
mTempDir = utf16str_to_utf8str(llutf16string(w_str));
if (mOSUserDir.empty())
{
mOSUserDir = mTempDir;
}
if (mOSCacheDir.empty())
{
mOSCacheDir = mTempDir;
}
}
else
{
mTempDir = mOSUserDir;
}
/*==========================================================================*|
// Now that we've got mOSUserDir, one way or another, let's see how we did
// with our environment variables.
{
auto report = [this](std::ostream& out){
out << "mOSUserDir = '" << mOSUserDir << "'\n"
<< "mOSCacheDir = '" << mOSCacheDir << "'\n"
<< "mTempDir = '" << mTempDir << "'" << std::endl;
};
int res = LLFile::mkdir(mOSUserDir);
if (res == -1)
{
// If we couldn't even create the directory, just blurt to stderr
report(std::cerr);
}
else
{
// successfully created logdir, plunk a log file there
std::string logfilename(add(mOSUserDir, "lldir.log"));
std::ofstream logfile(logfilename.c_str());
if (! logfile.is_open())
{
report(std::cerr);
}
else
{
report(logfile);
}
}
}
|*==========================================================================*/
// fprintf(stderr, "mTempDir = <%s>",mTempDir);
#if 1
// Don't use the real app path for now, as we'll have to add parsing to detect if
// we're in a developer tree, which has a different structure from the installed product.
S32 size = GetModuleFileName(NULL, w_str, MAX_PATH);
if (size)
{
w_str[size] = '\0';
mExecutablePathAndName = utf16str_to_utf8str(llutf16string(w_str));
auto path_end = mExecutablePathAndName.find_last_of('\\');
if (path_end != std::string::npos)
{
mExecutableDir = mExecutablePathAndName.substr(0, path_end);
mExecutableFilename = mExecutablePathAndName.substr(path_end+1, std::string::npos);
}
else
{
mExecutableFilename = mExecutablePathAndName;
}
GetCurrentDirectory(MAX_PATH, w_str);
mWorkingDir = utf16str_to_utf8str(llutf16string(w_str));
}
else
{
LL_WARNS("AppInit") << "Couldn't get APP path, assuming current directory!\n" << LL_ENDL;
GetCurrentDirectory(MAX_PATH, w_str);
mExecutableDir = utf16str_to_utf8str(llutf16string(w_str));
// Assume it's the current directory
}
#else
GetCurrentDirectory(MAX_PATH, w_str);
mExecutableDir = utf16str_to_utf8str(llutf16string(w_str));
#endif
mAppRODataDir = mWorkingDir;
// if (mExecutableDir.find("indra") == std::string::npos)
// *NOTE:Mani - It is a mistake to put viewer specific code in
// the LLDir implementation. The references to 'skins' and
// 'llplugin' need to go somewhere else.
// alas... this also gets called during static initialization
// time due to the construction of gDirUtil in lldir.cpp.
if(! LLFile::isdir(add(mAppRODataDir, "skins")) || ! LLFile::isdir(add(mAppRODataDir, "app_settings")))
{
// What? No skins or app_settings in the working dir?
// Try the executable's directory.
mAppRODataDir = mExecutableDir;
}
// LL_INFOS() << "mAppRODataDir = " << mAppRODataDir << LL_ENDL;
mSkinBaseDir = add(mAppRODataDir, "skins");
// Build the default cache directory
mDefaultCacheDir = buildSLOSCacheDir();
// Make sure it exists
int res = LLFile::mkdir(mDefaultCacheDir);
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_CACHE dir " << mDefaultCacheDir << LL_ENDL;
}
mLLPluginDir = add(mExecutableDir, "llplugin");
}
LLDir_Win32::~LLDir_Win32()
{
}
// Implementation
void LLDir_Win32::initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir)
{
// Allow override so test apps can read newview directory
if (!app_read_only_data_dir.empty())
{
mAppRODataDir = app_read_only_data_dir;
mSkinBaseDir = add(mAppRODataDir, "skins");
}
mAppName = app_name;
mOSUserAppDir = add(mOSUserDir, app_name);
int res = LLFile::mkdir(mOSUserAppDir);
if (res == -1)
{
LL_WARNS() << "Couldn't create app user dir " << mOSUserAppDir << LL_ENDL;
LL_WARNS() << "Default to base dir" << mOSUserDir << LL_ENDL;
mOSUserAppDir = mOSUserDir;
}
//dumpCurrentDirectories();
res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << LL_ENDL;
}
res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << LL_ENDL;
}
res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,""));
if (res == -1)
{
LL_WARNS() << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << LL_ENDL;
}
mCAFile = getExpandedFilename( LL_PATH_EXECUTABLE, "ca-bundle.crt" );
}
U32 LLDir_Win32::countFilesInDir(const std::string &dirname, const std::string &mask)
{
HANDLE count_search_h;
U32 file_count;
file_count = 0;
WIN32_FIND_DATA FileData;
llutf16string pathname = utf8str_to_utf16str(dirname);
pathname += utf8str_to_utf16str(mask);
if ((count_search_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE)
{
file_count++;
while (FindNextFile(count_search_h, &FileData))
{
file_count++;
}
FindClose(count_search_h);
}
return (file_count);
}
// get the next file in the directory
// AO: Used by LGG selection beams
bool LLDir_Win32::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname)
{
bool fileFound = false;
fname = "";
WIN32_FIND_DATAW FileData;
llutf16string pathname = utf8str_to_utf16str(dirname) + utf8str_to_utf16str(mask);
if (pathname != mCurrentDir)
{
// different dir specified, close old search
if (mCurrentDir[0])
{
FindClose(mDirSearch_h);
}
mCurrentDir = pathname;
// and open new one
// Check error opening Directory structure
if ((mDirSearch_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE)
{
fileFound = true;
}
}
// Loop to skip over the current (.) and parent (..) directory entries
// (apparently returned in Win7 but not XP)
do
{
if ( fileFound
&& ( (lstrcmp(FileData.cFileName, (LPCTSTR)TEXT(".")) == 0)
||(lstrcmp(FileData.cFileName, (LPCTSTR)TEXT("..")) == 0)
)
)
{
fileFound = false;
}
} while ( mDirSearch_h != INVALID_HANDLE_VALUE
&& !fileFound
&& (fileFound = FindNextFile(mDirSearch_h, &FileData)
)
);
if (!fileFound && GetLastError() == ERROR_NO_MORE_FILES)
{
// No more files, so reset to beginning of directory
FindClose(mDirSearch_h);
mCurrentDir[0] = '\000';
}
if (fileFound)
{
// convert from TCHAR to char
fname = utf16str_to_utf8str(FileData.cFileName);
}
return fileFound;
}
std::string LLDir_Win32::getCurPath()
{
WCHAR w_str[MAX_PATH];
GetCurrentDirectory(MAX_PATH, w_str);
return utf16str_to_utf8str(llutf16string(w_str));
}
bool LLDir_Win32::fileExists(const std::string &filename) const
{
llstat stat_data;
// Check the age of the file
// Now, we see if the files we've gathered are recent...
int res = LLFile::stat(filename, &stat_data);
if (!res)
{
return true;
}
else
{
return false;
}
}
/*virtual*/ std::string LLDir_Win32::getLLPluginLauncher()
{
return gDirUtilp->getExecutableDir() + gDirUtilp->getDirDelimiter() +
"SLPlugin.exe";
}
/*virtual*/ std::string LLDir_Win32::getLLPluginFilename(std::string base_name)
{
return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() +
base_name + ".dll";
}
#if 0
// Utility function to get version number of a DLL
#define PACKVERSION(major,minor) MAKELONG(minor,major)
DWORD GetDllVersion(LPCTSTR lpszDllName)
{
HINSTANCE hinstDll;
DWORD dwVersion = 0;
hinstDll = LoadLibrary(lpszDllName); /* Flawfinder: ignore */
if(hinstDll)
{
DLLGETVERSIONPROC pDllGetVersion;
pDllGetVersion = (DLLGETVERSIONPROC) GetProcAddress(hinstDll, "DllGetVersion");
/*Because some DLLs might not implement this function, you
must test for it explicitly. Depending on the particular
DLL, the lack of a DllGetVersion function can be a useful
indicator of the version.
*/
if(pDllGetVersion)
{
DLLVERSIONINFO dvi;
HRESULT hr;
ZeroMemory(&dvi, sizeof(dvi));
dvi.cbSize = sizeof(dvi);
hr = (*pDllGetVersion)(&dvi);
if(SUCCEEDED(hr))
{
dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion);
}
}
FreeLibrary(hinstDll);
}
return dwVersion;
}
#endif
#endif
+59
View File
@@ -0,0 +1,59 @@
/**
* @file lldir_win32.h
* @brief Definition of directory utilities class for windows
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#if !LL_WINDOWS
#error This header must not be included when compiling for any target other than Windows. Consider including lldir.h instead.
#endif // !LL_WINDOWS
#ifndef LL_LLDIR_WIN32_H
#define LL_LLDIR_WIN32_H
#include "lldir.h"
class LLDir_Win32 : public LLDir
{
public:
LLDir_Win32();
virtual ~LLDir_Win32();
/*virtual*/ void initAppDirs(const std::string &app_name,
const std::string& app_read_only_data_dir);
/*virtual*/ std::string getCurPath();
/*virtual*/ U32 countFilesInDir(const std::string &dirname, const std::string &mask);
/*virtual*/ bool fileExists(const std::string &filename) const;
/*virtual*/ bool getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); //FS:LGG for LGG's particle beam
/*virtual*/ std::string getLLPluginLauncher();
/*virtual*/ std::string getLLPluginFilename(std::string base_name);
private:
void* mDirSearch_h{ nullptr };
llutf16string mCurrentDir;
};
#endif // LL_LLDIR_WIN32_H
+72
View File
@@ -0,0 +1,72 @@
/**
* @file lldirguard.h
* @brief Protect working directory from being changed in scope.
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_DIRGUARD_H
#define LL_DIRGUARD_H
#include "linden_common.h"
#include "llerror.h"
#if LL_WINDOWS
class LLDirectoryGuard
{
public:
LLDirectoryGuard()
{
mOrigDirLen = GetCurrentDirectory(MAX_PATH, mOrigDir);
}
~LLDirectoryGuard()
{
mFinalDirLen = GetCurrentDirectory(MAX_PATH, mFinalDir);
if ((mOrigDirLen!=mFinalDirLen) ||
(wcsncmp(mOrigDir,mFinalDir,mOrigDirLen)!=0))
{
// Dir has changed
std::string mOrigDirUtf8 = utf16str_to_utf8str(llutf16string(mOrigDir));
std::string mFinalDirUtf8 = utf16str_to_utf8str(llutf16string(mFinalDir));
LL_INFOS() << "Resetting working dir from " << mFinalDirUtf8 << " to " << mOrigDirUtf8 << LL_ENDL;
SetCurrentDirectory(mOrigDir);
}
}
private:
TCHAR mOrigDir[MAX_PATH];
DWORD mOrigDirLen;
TCHAR mFinalDir[MAX_PATH];
DWORD mFinalDirLen;
};
#else // No-op outside Windows.
class LLDirectoryGuard
{
public:
LLDirectoryGuard() {}
~LLDirectoryGuard() {}
};
#endif
#endif
+243
View File
@@ -0,0 +1,243 @@
/**
* @file lldiriterator.cpp
* @brief Iterator through directory entries matching the search pattern.
*
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "lldiriterator.h"
#include "fix_macros.h"
#include "llregex.h"
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
static std::string glob_to_regex(const std::string& glob);
class LLDirIterator::Impl
{
public:
Impl(const std::string &dirname, const std::string &mask);
~Impl();
bool next(std::string &fname);
private:
boost::regex mFilterExp;
fs::directory_iterator mIter;
bool mIsValid;
};
LLDirIterator::Impl::Impl(const std::string &dirname, const std::string &mask)
: mIsValid(false)
{
#ifdef LL_WINDOWS // or BOOST_WINDOWS_API
fs::path dir_path(utf8str_to_utf16str(dirname));
#else
fs::path dir_path(dirname);
#endif
bool is_dir = false;
// Check if path is a directory.
try
{
is_dir = fs::is_directory(dir_path);
}
catch (const fs::filesystem_error& e)
{
LL_WARNS() << e.what() << LL_ENDL;
return;
}
if (!is_dir)
{
LL_WARNS() << "Invalid path: \"" << dir_path.string() << "\"" << LL_ENDL;
return;
}
// Initialize the directory iterator for the given path.
try
{
mIter = fs::directory_iterator(dir_path);
}
catch (const fs::filesystem_error& e)
{
LL_WARNS() << e.what() << LL_ENDL;
return;
}
// Convert the glob mask to a regular expression
std::string exp = glob_to_regex(mask);
// Initialize boost::regex with the expression converted from
// the glob mask.
// An exception is thrown if the expression is not valid.
try
{
mFilterExp.assign(exp);
}
catch (boost::regex_error& e)
{
LL_WARNS() << "\"" << exp << "\" is not a valid regular expression: "
<< e.what() << LL_ENDL;
return;
}
mIsValid = true;
}
LLDirIterator::Impl::~Impl()
{
}
bool LLDirIterator::Impl::next(std::string &fname)
{
fname = "";
if (!mIsValid)
{
LL_WARNS() << "The iterator is not correctly initialized." << LL_ENDL;
return false;
}
fs::directory_iterator end_itr; // default construction yields past-the-end
bool found = false;
// Check if path is a directory.
try
{
while (mIter != end_itr && !found)
{
boost::smatch match;
std::string name = mIter->path().filename().string();
found = ll_regex_match(name, match, mFilterExp);
if (found)
{
fname = name;
}
++mIter;
}
}
catch (const fs::filesystem_error& e)
{
LL_WARNS() << e.what() << LL_ENDL;
}
return found;
}
/**
Converts the incoming glob into a regex. This involves
converting incoming glob expressions to regex equivilents and
at the same time, escaping any regex meaningful characters which
do not have glob meaning, i.e.
.()+|^$
in the input.
*/
std::string glob_to_regex(const std::string& glob)
{
std::string regex;
regex.reserve(glob.size()<<1);
S32 braces = 0;
bool escaped = false;
bool square_brace_open = false;
for (std::string::const_iterator i = glob.begin(); i != glob.end(); ++i)
{
char c = *i;
switch (c)
{
case '*':
if (glob.begin() == i)
{
regex+="[^.].*";
}
else
{
regex+= escaped ? "*" : ".*";
}
break;
case '?':
regex+= escaped ? '?' : '.';
break;
case '{':
braces++;
regex+='(';
break;
case '}':
if (!braces)
{
LL_ERRS() << "glob_to_regex: Closing brace without an equivalent opening brace: " << glob << LL_ENDL;
}
regex+=')';
braces--;
break;
case ',':
regex+= braces ? '|' : c;
break;
case '!':
regex+= square_brace_open ? '^' : c;
break;
case '.': // This collection have different regex meaning
case '^': // and so need escaping.
case '(':
case ')':
case '+':
case '|':
case '$':
regex += '\\';
default:
regex += c;
break;
}
escaped = ('\\' == c);
square_brace_open = ('[' == c);
}
if (braces)
{
LL_ERRS() << "glob_to_regex: Unterminated brace expression: " << glob << LL_ENDL;
}
return regex;
}
LLDirIterator::LLDirIterator(const std::string &dirname, const std::string &mask)
{
mImpl = new Impl(dirname, mask);
}
LLDirIterator::~LLDirIterator()
{
delete mImpl;
}
bool LLDirIterator::next(std::string &fname)
{
return mImpl->next(fname);
}
+87
View File
@@ -0,0 +1,87 @@
/**
* @file lldiriterator.h
* @brief Iterator through directory entries matching the search pattern.
*
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLDIRITERATOR_H
#define LL_LLDIRITERATOR_H
#include "linden_common.h"
/**
* Class LLDirIterator
*
* Iterates through directory entries matching the search pattern.
*/
class LLDirIterator
{
public:
/**
* Constructs LLDirIterator object to search for glob pattern
* matches in a directory.
*
* @param dirname - name of a directory to search in.
* @param mask - search pattern, a glob expression
*
* Wildcards supported in glob expressions:
* --------------------------------------------------------------
* | Wildcard | Matches |
* --------------------------------------------------------------
* | * |zero or more characters |
* | ? |exactly one character |
* | [abcde] |exactly one character listed |
* | [a-e] |exactly one character in the given range |
* | [!abcde] |any character that is not listed |
* | [!a-e] |any character that is not in the given range |
* | {abc,xyz} |exactly one entire word in the options given |
* --------------------------------------------------------------
*/
LLDirIterator(const std::string &dirname, const std::string &mask);
~LLDirIterator();
/**
* Searches for the next directory entry matching the glob mask
* specified upon iterator construction.
* Returns true if a match is found, sets fname
* parameter to the name of the matched directory entry and
* increments the iterator position.
*
* Typical usage:
* <code>
* LLDirIterator iter(directory, pattern);
* if ( iter.next(scanResult) )
* </code>
*
* @param fname - name of the matched directory entry.
* @return true if a match is found, false otherwise.
*/
bool next(std::string &fname);
protected:
class Impl;
Impl* mImpl;
};
#endif //LL_LLDIRITERATOR_H
+572
View File
@@ -0,0 +1,572 @@
/**
* @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 "llapp.h"
#include "llassettype.h"
#include "lldir.h"
#include <boost/filesystem.hpp>
#include <chrono>
#include "lldiskcache.h"
/**
* 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.
*/
static const std::string CACHE_FILENAME_PREFIX("sl_cache");
std::string LLDiskCache::sCacheDir;
// <FS:Ansariel> Optimize asset simple disk cache
static const char* subdirs = "0123456789abcdef";
LLDiskCache::LLDiskCache(const std::string& cache_dir,
const uintmax_t max_size_bytes,
const bool enable_cache_debug_info
// <FS:Beq> Add High/Low water mark support
,const F32 highwater_mark_percent
,const F32 lowwater_mark_percent
// </FS:Beq>
) :
mMaxSizeBytes(max_size_bytes),
mEnableCacheDebugInfo(enable_cache_debug_info)
{
sCacheDir = cache_dir;
LLFile::mkdir(cache_dir);
// <FS:Ansariel> Optimize asset simple disk cache
for (S32 i = 0; i < 16; i++)
{
std::string dirname = cache_dir + gDirUtilp->getDirDelimiter() + subdirs[i];
LLFile::mkdir(dirname);
}
// </FS:Ansariel>
// <FS:Beq> add static assets into the new cache after clear.
// Only missing entries are copied on init, skiplist is setup
// For everything we populate FS specific assets to allow future updates
prepopulateCacheWithStatic();
// </FS:Beq>
}
// 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)
{
LL_INFOS() << "Total dir size before purge is " << dirFileSize(sCacheDir) << LL_ENDL;
}
boost::system::error_code ec;
auto start_time = std::chrono::high_resolution_clock::now();
typedef std::pair<std::time_t, std::pair<uintmax_t, std::string>> file_info_t;
std::vector<file_info_t> file_info;
#if LL_WINDOWS
std::wstring cache_path(utf8str_to_utf16str(sCacheDir));
#else
std::string cache_path(sCacheDir);
#endif
uintmax_t file_size_total = 0; // <FS:Beq/> try to make simple cache less naive.
if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
{
// <FS:Ansariel> Optimize asset simple disk cache
//boost::filesystem::directory_iterator iter(cache_path, ec);
//while (iter != boost::filesystem::directory_iterator() && !ec.failed())
boost::filesystem::recursive_directory_iterator iter(cache_path, ec);
while (iter != boost::filesystem::recursive_directory_iterator() && !ec.failed())
// </FS:Ansariel>
{
if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
{
if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos)
{
uintmax_t file_size = boost::filesystem::file_size(*iter, ec);
if (ec.failed())
{
continue;
}
const std::string file_path = (*iter).path().string();
const std::time_t file_time = boost::filesystem::last_write_time(*iter, ec);
if (ec.failed())
{
continue;
}
file_size_total += file_size; // <FS:Beq/> try to make simple cache less naive.
file_info.push_back(file_info_t(file_time, { file_size, file_path }));
}
}
iter.increment(ec);
}
}
// <FS:Beq> add high water/low water thresholds to reduce the churn in the cache.
LL_DEBUGS("LLDiskCache") << "Cache is " << (int)(((F32)file_size_total)/mMaxSizeBytes*100.0) << "% full" << LL_ENDL;
if( file_size_total < mMaxSizeBytes * (mHighPercent/100) )
{
// Nothing to do here
LL_DEBUGS("LLDiskCache") << "Not exceded high water - do nothing" << LL_ENDL;
return;
}
// If we reach here we are above the trigger level so we must purge until we've removed enough to take us down to the low water mark.
// </FS:Beq>
std::sort(file_info.begin(), file_info.end(), [](file_info_t& x, file_info_t& y)
{
return x.first < y.first; // <FS:Beq/> sort oldest to newest, to we can remove the oldest files first.
});
// <FS:Beq> add high water/low water thresholds to reduce the churn in the cache.
auto target_size = (uintmax_t)(mMaxSizeBytes * (mLowPercent/100));
LL_INFOS() << "Purging cache to a maximum of " << target_size << " bytes" << LL_ENDL;
// </FS:Beq>
// <FS:Beq> Extra accounting to track the retention of static assets
//std::vector<bool> file_removed;
enum class purge_action { delete_file=0, keep_file, skip_file };
std::map<std::string,purge_action> file_removed;
auto keep{file_info.size()};
auto del{0};
auto skip{0};
// </FS:Beq>
// <FS:Beq> revised purge logic to track amount removed not retained to shortern loop
// uintmax_t file_size_total = 0;
// if (mEnableCacheDebugInfo)
// {
// file_removed.reserve(file_info.size());
// }
// uintmax_t file_size_total = 0;
// for (file_info_t& entry : file_info)
// {
// file_size_total += entry.second.first;
// bool should_remove = file_size_total > mMaxSizeBytes;
// if (mEnableCacheDebugInfo)
// {
// file_removed.push_back(should_remove);
// }
uintmax_t deleted_size_total = 0;
for (const file_info_t& entry : file_info)
{
// first check if we still need to delete more files
bool should_remove = (file_size_total - deleted_size_total) > target_size;
// <FS> Make sure static assets are not eliminated
auto action{ should_remove ? purge_action::delete_file : purge_action::keep_file };
if (!should_remove)
{
break;
}
auto this_file_size = entry.second.first;
deleted_size_total += this_file_size;
auto uuid_as_string = gDirUtilp->getBaseFileName(entry.second.second, true);
uuid_as_string = uuid_as_string.substr(CACHE_FILENAME_PREFIX.size() + 1, 36); // skip "sl_cache_" and trailing "_N"
// LL_INFOS() << "checking UUID=" <<uuid_as_string<< LL_ENDL;
if (std::find(mSkipList.begin(), mSkipList.end(), uuid_as_string) != mSkipList.end())
{
// this is one of our protected items so no purging
should_remove = false;
action = purge_action::skip_file;
boost::filesystem::last_write_time(entry.second.second, ec); // force these to the front of the list next time so that purge size works
skip++;
}
else{
del++;
}
keep--;
if (mEnableCacheDebugInfo)
{
file_removed.emplace(entry.second.second, action);
}
// </FS>
if (should_remove)
{
boost::filesystem::remove(entry.second.second, ec);
if (ec.failed())
{
LL_WARNS() << "Failed to delete cache file " << entry.second.second << ": " << ec.message() << LL_ENDL;
}
}
}
// <FS:Beq> update the debug logging to be more useful
auto end_time = std::chrono::high_resolution_clock::now();
auto execute_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
// </FS:Beq>
if (mEnableCacheDebugInfo)
{
// <FS:Beq> update the debug logging to be more useful
// auto end_time = std::chrono::high_resolution_clock::now();
// auto execute_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
// </FS:Beq>
// Log afterward so it doesn't affect the time measurement
// Logging thousands of file results can take hundreds of milliseconds
uintmax_t deleted_so_far{ 0 }; // <FS:Beq/> update the debug logging to be more useful
for (size_t i = 0; i < file_info.size(); ++i)
{
const file_info_t& entry = file_info[i];
// <FS> Static asset stuff
deleted_so_far += entry.second.first; // <FS:Beq/> update the debug logging to be more useful
//const bool removed = file_removed[i];
//const std::string action = removed ? "DELETE:" : "KEEP:";
std::string action{};
// Check if the file exists in the map
auto& filename{ entry.second.second };
if (file_removed.find(filename) != file_removed.end()) {
// File found in the map, retrieve the corresponding enum value
switch (file_removed[filename])
{
case purge_action::delete_file:
action = "DELETE";
del++;
break;
case purge_action::skip_file:
action = "STATIC";
skip++;
break;
default:
// Handle any unexpected enum value
action = "UNKNOWN";
break;
}
}
else
{
action = "KEEP";
}
// </FS>
// 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 - deleted_so_far << "/" << mMaxSizeBytes << ")"; // <FS:Beq/> update the debug logging to be more useful
LL_INFOS() << line.str() << LL_ENDL;
}
// <FS:Beq> make the summary stats more easily enabled.
}
// <FS:Beq> update the debug logging to be more useful
// LL_INFOS() << "Total dir size after purge is " << dirFileSize(sCacheDir) << LL_ENDL;
// LL_INFOS() << "Cache purge took " << execute_time << " ms to execute for " << file_info.size() << " files" << LL_ENDL;
auto newCacheSize = updateCacheSize(file_size_total - deleted_size_total);
LL_INFOS("LLDiskCache") << "Total dir size after purge is " << newCacheSize << LL_ENDL;
LL_INFOS("LLDiskCache") << "Cache purge took " << execute_time << " ms to execute for " << file_info.size() << " files" << LL_ENDL;
// </FS:Beq>
LL_INFOS("LLDiskCache") << "Deleted: " << del << " Skipped: " << skip << " Kept: " << keep << LL_ENDL; // <FS:Beq/> Extra accounting to track the retention of static assets
LL_INFOS("LLDiskCache") << "Total of " << deleted_size_total << " bytes removed." << LL_ENDL; // <FS:Beq/> Extra accounting to track the retention of static assets
// } <FS:Beq/> this bracket was moved up a few lines.
}
const std::string LLDiskCache::metaDataToFilepath(const LLUUID& id, LLAssetType::EType at)
{
return llformat("%s%s%s_%s_0.asset", sCacheDir.c_str(), gDirUtilp->getDirDelimiter().c_str(), CACHE_FILENAME_PREFIX.c_str(), id.asString().c_str());
}
const std::string LLDiskCache::getCacheInfo()
{
LL_PROFILE_ZONE_SCOPED; // <FS:Beq/> add some instrumentation
std::ostringstream cache_info;
F32 max_in_mb = (F32)mMaxSizeBytes / (1024.0f * 1024.0f);
// <FS:Beq> stall prevention. We still need to make sure this initialised when called at startup.
F32 percent_used;
if (mStoredCacheSize > 0)
{
percent_used = ((F32)mStoredCacheSize / (F32)mMaxSizeBytes) * 100.0f;
}
else
{
percent_used = ((F32)dirFileSize(sCacheDir) / (F32)mMaxSizeBytes) * 100.0f;
}
// </FS:Beq>
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();
}
// <FS:Beq> Copy static items into cache and add to the skip list that prevents their purging
// Note that there is no de-duplication nor other validation of the list.
void LLDiskCache::prepopulateCacheWithStatic()
{
mSkipList.clear();
std::vector<std::string> from_folders;
from_folders.emplace_back(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "fs_static_assets"));
#ifdef OPENSIM
from_folders.emplace_back(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "static_assets"));
#endif
for (const auto& from_folder : from_folders)
{
if (gDirUtilp->fileExists(from_folder))
{
auto assets_to_copy = gDirUtilp->getFilesInDir(from_folder);
for (auto from_asset_file : assets_to_copy)
{
from_asset_file = from_folder + gDirUtilp->getDirDelimiter() + from_asset_file;
// we store static assets as UUID.asset_type the asset_type is not used in the current simple cache format
auto uuid_as_string{ gDirUtilp->getBaseFileName(from_asset_file, true) };
LLUUID uuid{ uuid_as_string };
auto to_asset_file = metaDataToFilepath(uuid, LLAssetType::AT_UNKNOWN);
if (!gDirUtilp->fileExists(to_asset_file))
{
if (mEnableCacheDebugInfo)
{
LL_INFOS("LLDiskCache") << "Copying static asset " << from_asset_file << " to cache from " << from_folder << LL_ENDL;
}
if (!LLFile::copy(from_asset_file, to_asset_file))
{
LL_WARNS("LLDiskCache") << "Failed to copy " << from_asset_file << " to " << to_asset_file << LL_ENDL;
}
}
if (std::find(mSkipList.begin(), mSkipList.end(), uuid_as_string) == mSkipList.end())
{
if (mEnableCacheDebugInfo)
{
LL_INFOS("LLDiskCache") << "Adding " << uuid_as_string << " to skip list" << LL_ENDL;
}
mSkipList.emplace_back(uuid_as_string);
}
}
}
}
}
// </FS:Beq>
void LLDiskCache::clearCache()
{
LL_INFOS() << "clearing cache " << sCacheDir << LL_ENDL;
/**
* 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
*/
boost::system::error_code ec;
#if LL_WINDOWS
std::wstring cache_path(utf8str_to_utf16str(sCacheDir));
#else
std::string cache_path(sCacheDir);
#endif
if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
{
// <FS:Ansariel> Optimize asset simple disk cache
//boost::filesystem::directory_iterator iter(cache_path, ec);
//while (iter != boost::filesystem::directory_iterator() && !ec.failed())
boost::filesystem::recursive_directory_iterator iter(cache_path, ec);
while (iter != boost::filesystem::recursive_directory_iterator() && !ec.failed())
// </FS:Ansariel>
{
if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
{
if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos)
{
boost::filesystem::remove(*iter, ec);
if (ec.failed())
{
LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL;
}
}
}
iter.increment(ec);
}
// <FS:Beq> add static assets into the new cache after clear
LL_INFOS() << "prepopulating new cache " << LL_ENDL;
prepopulateCacheWithStatic();
}
LL_INFOS() << "Cleared cache " << sCacheDir << LL_ENDL;
}
void LLDiskCache::removeOldVFSFiles()
{
//VFS files won't be created, so consider removing this code later
static const char CACHE_FORMAT[] = "inv.llsd";
static const char DB_FORMAT[] = "db2.x";
boost::system::error_code ec;
#if LL_WINDOWS
std::wstring cache_path(utf8str_to_utf16str(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "")));
#else
std::string cache_path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""));
#endif
if (boost::filesystem::is_directory(cache_path, ec) && !ec.failed())
{
boost::filesystem::directory_iterator iter(cache_path, ec);
while (iter != boost::filesystem::directory_iterator() && !ec.failed())
{
if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
{
if (((*iter).path().string().find(CACHE_FORMAT) != std::string::npos) ||
((*iter).path().string().find(DB_FORMAT) != std::string::npos))
{
boost::filesystem::remove(*iter, ec);
if (ec.failed())
{
LL_WARNS() << "Failed to delete cache file " << *iter << ": " << ec.message() << LL_ENDL;
}
}
}
iter.increment(ec);
}
}
}
// <FS:Beq> Lets not scan every single time if we can avoid it eh?
// uintmax_t LLDiskCache::dirFileSize(const std::string& dir)
// {
uintmax_t LLDiskCache::updateCacheSize(const uintmax_t newsize)
{
mStoredCacheSize = newsize;
mLastScanTime = system_clock::now();
return mStoredCacheSize;
}
uintmax_t LLDiskCache::dirFileSize(const std::string& dir, bool force)
{
using namespace std::chrono;
const seconds cache_duration{ 120 };// A rather arbitrary number. it takes 5 seconds+ on a fast drive to scan 80K+ items. purge runs every minute and will update. so 120 should mean we never need a superfluous cache scan.
const auto current_time = system_clock::now();
const auto time_difference = duration_cast<seconds>(current_time - mLastScanTime);
// Check if the cached result can be used
if( !force && time_difference < cache_duration )
{
LL_DEBUGS("LLDiskCache") << "Using cached result: " << mStoredCacheSize << LL_ENDL;
return mStoredCacheSize;
}
// </FS:Beq>
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.
*/
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, ec) && !ec.failed())
{
// <FS:Ansariel> Optimize asset simple disk cache
//boost::filesystem::directory_iterator iter(dir_path, ec);
//while (iter != boost::filesystem::directory_iterator() && !ec.failed())
boost::filesystem::recursive_directory_iterator iter(dir_path, ec);
while (iter != boost::filesystem::recursive_directory_iterator() && !ec.failed())
// </FS:Ansariel>
{
if (boost::filesystem::is_regular_file(*iter, ec) && !ec.failed())
{
if ((*iter).path().string().find(CACHE_FILENAME_PREFIX) != std::string::npos)
{
uintmax_t file_size = boost::filesystem::file_size(*iter, ec);
if (!ec.failed())
{
total_file_size += file_size;
}
}
}
iter.increment(ec);
}
}
// <FS:Beq> Lets not scan every single time if we can avoid it eh?
// return total_file_size;
return updateCacheSize(total_file_size);
// </FS:Beq>
}
LLPurgeDiskCacheThread::LLPurgeDiskCacheThread() :
LLThread("PurgeDiskCacheThread", nullptr)
{
}
void LLPurgeDiskCacheThread::run()
{
constexpr std::chrono::seconds CHECK_INTERVAL{60};
while (LLApp::instance()->sleep(CHECK_INTERVAL))
{
LLDiskCache::instance().purge();
}
}
+215
View File
@@ -0,0 +1,215 @@
/**
* @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"
#include <chrono>
using namespace std::chrono;
class LLDiskCache :
public LLParamSingleton<LLDiskCache>
{
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 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
* setting and send us their logs
*/
const bool enable_cache_debug_info,
// <FS:Beq> Add high/low threshold controls for cache purging
/**
* A floating point percentage of the max_size_bytes above which the cache purge will trigger.
*/
const F32 highwater_mark_percent,
/**
* A floating point percentage of the max_size_bytes which the cache purge will aim to reach once triggered.
*/
const F32 lowwater_mark_percent
// </FS:Beq>
);
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.
*/
static const std::string metaDataToFilepath(const LLUUID& id, LLAssetType::EType at);
/**
* 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!
*
* 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();
// <FS:Beq>
// copy from distribution into cache to replace static content
void prepopulateCacheWithStatic();
// </FS:Beq>
/**
* 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();
void removeOldVFSFiles();
// <FS:Ansariel> Better asset cache size control
void setMaxSizeBytes(uintmax_t size) { mMaxSizeBytes = size; }
// <FS:Beq> High/Low water control
void setHighWaterPercentage(F32 HiPct) { mHighPercent = llclamp(HiPct, mLowPercent, 100.0); };
void setLowWaterPercentage(F32 LowPct) { mLowPercent = llclamp(LowPct, 0.0, mHighPercent); };
// </FS:Beq>
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 updateCacheSize(const uintmax_t newsize); // <FS:Beq/> enable time based caching of dirfilesize except when force is true.
uintmax_t dirFileSize(const std::string& dir, bool force = false); // <FS:Beq/> enable time based caching of dirfilesize except when force is true.
/**
* cache the directory size cos it takes forever to calculate it
*
*/
uintmax_t mStoredCacheSize{ 0 };
time_point<system_clock> mLastScanTime{ };
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;
// <FS:Beq> High/Low water control
F32 mHighPercent { 95.0 };
F32 mLowPercent { 70.0 };
// </FS:Beq>
/**
* 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.
*/
static std::string sCacheDir;
/**
* When enabled, displays additional debugging information in
* various parts of the code
*/
bool mEnableCacheDebugInfo;
std::vector<std::string> mSkipList; // <FS:Beq/> Vector of "static" untouchable assets that should never be purged
};
class LLPurgeDiskCacheThread : public LLThread
{
public:
LLPurgeDiskCacheThread();
protected:
void run() override;
};
#endif // _LLDISKCACHE
+455
View File
@@ -0,0 +1,455 @@
/**
* @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"
#include "boost/filesystem.hpp"
constexpr S32 LLFileSystem::READ = 0x00000001;
constexpr S32 LLFileSystem::WRITE = 0x00000002;
constexpr S32 LLFileSystem::READ_WRITE = 0x00000003; // LLFileSystem::READ & LLFileSystem::WRITE
constexpr 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;
// 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)
const std::string filename = LLDiskCache::metaDataToFilepath(mFileID, mFileType);
// 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)
{
updateFileAccessTime(filename);
}
}
}
// static
bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType file_type)
{
LL_PROFILE_ZONE_SCOPED;
const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type);
// <FS:Ansariel> IO-streams replacement
//llifstream file(filename, std::ios::binary);
//if (file.is_open())
//{
// file.seekg(0, std::ios::end);
// return file.tellg() > 0;
//}
llstat file_stat;
if (LLFile::stat(filename, &file_stat) == 0)
{
return S_ISREG(file_stat.st_mode) && file_stat.st_size > 0;
}
// </FS:Ansariel>
return false;
}
// static
bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_error /*= 0*/)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type);
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)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
const std::string old_filename = LLDiskCache::metaDataToFilepath(old_file_id, old_file_type);
const std::string new_filename = LLDiskCache::metaDataToFilepath(new_file_id, new_file_type);
// 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_file_id << " reason: " << strerror(errno) << LL_ENDL;
}
return true;
}
// static
S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type);
S32 file_size = 0;
// <FS:Ansariel> IO-streams replacement
//llifstream file(filename, std::ios::binary);
//if (file.is_open())
//{
// file.seekg(0, std::ios::end);
// file_size = (S32)file.tellg();
//}
llstat file_stat;
if (LLFile::stat(filename, &file_stat) == 0)
{
file_size = file_stat.st_size;
}
// </FS:Ansariel>
return file_size;
}
bool LLFileSystem::read(U8* buffer, S32 bytes)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
bool success = false;
const std::string filename = LLDiskCache::metaDataToFilepath(mFileID, mFileType);
// <FS:Ansariel> IO-streams replacement
//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 = (S32)file.gcount();
// }
// file.close();
// mPosition += mBytesRead;
// if (mBytesRead)
// {
// success = true;
// }
//}
LLFILE* file = LLFile::fopen(filename, "rb");
if (file)
{
if (fseek(file, mPosition, SEEK_SET) == 0)
{
mBytesRead = static_cast<S32>(fread(buffer, 1, bytes, file));
fclose(file);
mPosition += mBytesRead;
// It probably would be correct to check for mBytesRead == bytes,
// but that will break avatar rezzing...
if (mBytesRead)
{
success = true;
}
}
}
// </FS:Ansariel>
return success;
}
S32 LLFileSystem::getLastBytesRead() const
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
return mBytesRead;
}
bool LLFileSystem::eof() const
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
return mPosition >= getSize();
}
bool LLFileSystem::write(const U8* buffer, S32 bytes)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
const std::string filename = LLDiskCache::metaDataToFilepath(mFileID, mFileType);
bool success = false;
// <FS:Ansariel> IO-streams replacement
//if (mMode == APPEND)
//{
// llofstream ofs(filename, std::ios::app | std::ios::binary);
// if (ofs)
// {
// ofs.write((const char*)buffer, bytes);
// mPosition = (S32)ofs.tellp();
// success = true;
// }
//}
//else if (mMode == READ_WRITE)
//{
// // Don't truncate if file already exists
// llofstream ofs(filename, std::ios::in | std::ios::binary);
// if (ofs)
// {
// ofs.seekp(mPosition, std::ios::beg);
// ofs.write((const char*)buffer, bytes);
// mPosition += bytes;
// success = true;
// }
// else
// {
// // File doesn't exist - open in write mode
// ofs.open(filename, std::ios::binary);
// if (ofs.is_open())
// {
// ofs.write((const char*)buffer, bytes);
// mPosition += bytes;
// success = true;
// }
// }
//}
//else
//{
// llofstream ofs(filename, std::ios::binary);
// if (ofs)
// {
// ofs.write((const char*)buffer, bytes);
// mPosition += bytes;
// success = true;
// }
//}
if (mMode == APPEND)
{
LLFILE* ofs = LLFile::fopen(filename, "a+b");
if (ofs)
{
S32 bytes_written = static_cast<S32>(fwrite(buffer, 1, bytes, ofs));
mPosition = ftell(ofs);
fclose(ofs);
success = (bytes_written == bytes);
}
}
else if (mMode == READ_WRITE)
{
LLFILE* ofs = LLFile::fopen(filename, "r+b");
if (ofs)
{
if (fseek(ofs, mPosition, SEEK_SET) == 0)
{
S32 bytes_written = static_cast<S32>(fwrite(buffer, 1, bytes, ofs));
mPosition = ftell(ofs);
fclose(ofs);
success = (bytes_written == bytes);
}
}
else
{
ofs = LLFile::fopen(filename, "wb");
if (ofs)
{
S32 bytes_written = static_cast<S32>(fwrite(buffer, 1, bytes, ofs));
mPosition = ftell(ofs);
fclose(ofs);
success = (bytes_written == bytes);
}
}
}
else
{
LLFILE* ofs = LLFile::fopen(filename, "wb");
if (ofs)
{
S32 bytes_written = static_cast<S32>(fwrite(buffer, 1, bytes, ofs));
mPosition = ftell(ofs);
fclose(ofs);
success = (bytes_written == bytes);
}
}
// </FS:Ansariel>
return success;
}
bool LLFileSystem::seek(S32 offset, S32 origin)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
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
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
return mPosition;
}
S32 LLFileSystem::getSize() const
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
return LLFileSystem::getFileSize(mFileID, mFileType);
}
S32 LLFileSystem::getMaxSize() const
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
// 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)
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
LLFileSystem::renameFile(mFileID, mFileType, new_id, new_type);
mFileID = new_id;
mFileType = new_type;
return true;
}
bool LLFileSystem::remove() const
{
LL_PROFILE_ZONE_COLOR(tracy::Color::Gold); // <FS:Beq> measure cache performance
LLFileSystem::removeFile(mFileID, mFileType);
return true;
}
void LLFileSystem::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
*/
constexpr std::time_t time_threshold = 1 * 60 * 60;
// 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), 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;
// 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, ec);
}
#else
// file last write time
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;
// 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, ec);
}
#endif
if (ec.failed())
{
LL_WARNS() << "Failed to update last write time for cache file " << file_path << ": " << ec.message() << LL_ENDL;
}
}
+83
View File
@@ -0,0 +1,83 @@
/**
* @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() = default;
bool read(U8* buffer, S32 bytes);
S32 getLastBytesRead() const;
bool eof() const;
bool write(const U8* buffer, S32 bytes);
bool seek(S32 offset, S32 origin = -1);
S32 tell() const;
S32 getSize() const;
S32 getMaxSize() const;
bool rename(const LLUUID& new_id, const LLAssetType::EType new_type);
bool remove() const;
/**
* 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);
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;
};
#endif // LL_FILESYSTEM_H
+244
View File
@@ -0,0 +1,244 @@
/**
* @file lllfsthread.cpp
* @brief LLLFSThread base class
*
* $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 "lllfsthread.h"
#include "llstl.h"
#include "llapr.h"
//============================================================================
/*static*/ LLLFSThread* LLLFSThread::sLocal = NULL;
//============================================================================
// Run on MAIN thread
//static
void LLLFSThread::initClass(bool local_is_threaded)
{
llassert(sLocal == NULL);
sLocal = new LLLFSThread(local_is_threaded);
}
//static
S32 LLLFSThread::updateClass(U32 ms_elapsed)
{
return static_cast<S32>(sLocal->update((F32)ms_elapsed));
}
//static
void LLLFSThread::cleanupClass()
{
llassert(sLocal != NULL);
sLocal->setQuitting();
while (sLocal->getPending())
{
sLocal->update(0);
}
sLocal->shutdown();
delete sLocal;
sLocal = NULL;
}
//----------------------------------------------------------------------------
LLLFSThread::LLLFSThread(bool threaded) :
LLQueuedThread("LFS", threaded)
{
if(!mLocalAPRFilePoolp)
{
mLocalAPRFilePoolp = new LLVolatileAPRPool() ;
}
}
LLLFSThread::~LLLFSThread()
{
// mLocalAPRFilePoolp cleanup in LLThread
// ~LLQueuedThread() will be called here
}
//----------------------------------------------------------------------------
LLLFSThread::handle_t LLLFSThread::read(const std::string& filename, /* Flawfinder: ignore */
U8* buffer, S32 offset, S32 numbytes,
Responder* responder)
{
LL_PROFILE_ZONE_SCOPED;
handle_t handle = generateHandle();
Request* req = new Request(this, handle,
FILE_READ, filename,
buffer, offset, numbytes,
responder);
bool res = addRequest(req);
if (!res)
{
LL_ERRS() << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << LL_ENDL;
}
return handle;
}
LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder)
{
LL_PROFILE_ZONE_SCOPED;
handle_t handle = generateHandle();
Request* req = new Request(this, handle,
FILE_WRITE, filename,
buffer, offset, numbytes,
responder);
bool res = addRequest(req);
if (!res)
{
LL_ERRS() << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << LL_ENDL;
}
return handle;
}
//============================================================================
LLLFSThread::Request::Request(LLLFSThread* thread,
handle_t handle,
operation_t op, const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder) :
QueuedRequest(handle, FLAG_AUTO_COMPLETE),
mThread(thread),
mOperation(op),
mFileName(filename),
mBuffer(buffer),
mOffset(offset),
mBytes(numbytes),
mBytesRead(0),
mResponder(responder)
{
if (numbytes <= 0)
{
LL_WARNS() << "LLLFSThread: Request with numbytes = " << numbytes << LL_ENDL;
}
}
LLLFSThread::Request::~Request()
{
}
// virtual, called from own thread
void LLLFSThread::Request::finishRequest(bool completed)
{
LL_PROFILE_ZONE_SCOPED;
if (mResponder.notNull())
{
mResponder->completed(completed ? mBytesRead : 0);
mResponder = NULL;
}
}
void LLLFSThread::Request::deleteRequest()
{
LL_PROFILE_ZONE_SCOPED;
if (getStatus() == STATUS_QUEUED)
{
LL_ERRS() << "Attempt to delete a queued LLLFSThread::Request!" << LL_ENDL;
}
if (mResponder.notNull())
{
mResponder->completed(0);
mResponder = NULL;
}
LLQueuedThread::QueuedRequest::deleteRequest();
}
bool LLLFSThread::Request::processRequest()
{
LL_PROFILE_ZONE_SCOPED;
bool complete = false;
if (mOperation == FILE_READ)
{
llassert(mOffset >= 0);
LLAPRFile infile ; // auto-closes
infile.open(mFileName, LL_APR_RB, mThread->getLocalAPRFilePool());
if (!infile.getFileHandle())
{
LL_WARNS() << "LLLFS: Unable to read file: " << mFileName << LL_ENDL;
mBytesRead = 0; // fail
return true;
}
S32 off;
if (mOffset < 0)
off = infile.seek(APR_END, 0);
else
off = infile.seek(APR_SET, mOffset);
llassert_always(off >= 0);
mBytesRead = infile.read(mBuffer, mBytes );
complete = true;
// LL_INFOS() << "LLLFSThread::READ:" << mFileName << " Bytes: " << mBytesRead << LL_ENDL;
}
else if (mOperation == FILE_WRITE)
{
apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY;
if (mOffset < 0)
flags |= APR_APPEND;
LLAPRFile outfile ; // auto-closes
outfile.open(mFileName, flags, mThread->getLocalAPRFilePool());
if (!outfile.getFileHandle())
{
LL_WARNS() << "LLLFS: Unable to write file: " << mFileName << LL_ENDL;
mBytesRead = 0; // fail
return true;
}
if (mOffset >= 0)
{
S32 seek = outfile.seek(APR_SET, mOffset);
if (seek < 0)
{
LL_WARNS() << "LLLFS: Unable to write file (seek failed): " << mFileName << LL_ENDL;
mBytesRead = 0; // fail
return true;
}
}
mBytesRead = outfile.write(mBuffer, mBytes );
complete = true;
// LL_INFOS() << "LLLFSThread::WRITE:" << mFileName << " Bytes: " << mBytesRead << "/" << mBytes << " Offset:" << mOffset << LL_ENDL;
}
else
{
LL_ERRS() << "LLLFSThread::unknown operation: " << (S32)mOperation << LL_ENDL;
}
return complete;
}
//============================================================================
LLLFSThread::Responder::~Responder()
{
}
//============================================================================
+140
View File
@@ -0,0 +1,140 @@
/**
* @file lllfsthread.h
* @brief LLLFSThread base class
*
* $LicenseInfo:firstyear=2000&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_LLLFSTHREAD_H
#define LL_LLLFSTHREAD_H
#include <queue>
#include <string>
#include <map>
#include <set>
#include "llpointer.h"
#include "llqueuedthread.h"
//============================================================================
// Threaded Local File System
//============================================================================
class LLLFSThread : public LLQueuedThread
{
//------------------------------------------------------------------------
public:
enum operation_t {
FILE_READ,
FILE_WRITE,
FILE_RENAME,
FILE_REMOVE
};
//------------------------------------------------------------------------
public:
class Responder : public LLThreadSafeRefCount
{
protected:
~Responder();
public:
virtual void completed(S32 bytes) = 0;
};
class Request : public QueuedRequest
{
protected:
virtual ~Request(); // use deleteRequest()
public:
Request(LLLFSThread* thread,
handle_t handle,
operation_t op, const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder);
S32 getBytes()
{
return mBytes;
}
S32 getBytesRead()
{
return mBytesRead;
}
S32 getOperation()
{
return mOperation;
}
U8* getBuffer()
{
return mBuffer;
}
const std::string& getFilename()
{
return mFileName;
}
/*virtual*/ bool processRequest();
/*virtual*/ void finishRequest(bool completed);
/*virtual*/ void deleteRequest();
private:
LLLFSThread* mThread;
operation_t mOperation;
std::string mFileName;
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
S32 mBytesRead; // bytes read from file
LLPointer<Responder> mResponder;
};
//------------------------------------------------------------------------
public:
LLLFSThread(bool threaded = true);
~LLLFSThread();
// Return a Request handle
handle_t read(const std::string& filename, /* Flawfinder: ignore */
U8* buffer, S32 offset, S32 numbytes,
Responder* responder);
handle_t write(const std::string& filename,
U8* buffer, S32 offset, S32 numbytes,
Responder* responder);
// static initializers
static void initClass(bool local_is_threaded = true); // Setup sLocal
static S32 updateClass(U32 ms_elapsed);
static void cleanupClass(); // Delete sLocal
public:
static LLLFSThread* sLocal; // Default local file thread
};
//============================================================================
#endif // LL_LLLFSTHREAD_H
+766
View File
@@ -0,0 +1,766 @@
/**
* @file lldir_test.cpp
* @date 2008-05
* @brief LLDir test cases.
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llstring.h"
#include "tests/StringVec.h"
#include "../lldir.h"
#include "../lldiriterator.h"
#include "../test/lltut.h"
#include "stringize.h"
#include <boost/assign/list_of.hpp>
using boost::assign::list_of;
// We use ensure_equals(..., vec(list_of(...))) not because it's functionally
// required, but because ensure_equals() knows how to format a StringVec.
// Turns out that when ensure_equals() displays a test failure with just
// list_of("string")("another"), you see 'stringanother' vs. '("string",
// "another")'.
StringVec vec(const StringVec& v)
{
return v;
}
// For some tests, use a dummy LLDir that uses memory data instead of touching
// the filesystem
struct LLDir_Dummy: public LLDir
{
/*----------------------------- LLDir API ------------------------------*/
LLDir_Dummy()
{
// Initialize important LLDir data members based on the filesystem
// data below.
mDirDelimiter = "/";
mExecutableDir = "install";
mExecutableFilename = "test";
mExecutablePathAndName = add(mExecutableDir, mExecutableFilename);
mWorkingDir = mExecutableDir;
mAppRODataDir = "install";
mSkinBaseDir = add(mAppRODataDir, "skins");
mOSUserDir = "user";
mOSUserAppDir = mOSUserDir;
mLindenUserDir = "";
// Make the dummy filesystem look more or less like what we expect in
// the real one.
static const char* preload[] =
{
// We group these fixture-data pathnames by basename, rather than
// sorting by full path as you might expect, because the outcome
// of each test strongly depends on which skins/languages provide
// a given basename.
"install/skins/default/colors.xml",
"install/skins/steam/colors.xml",
"user/skins/default/colors.xml",
"user/skins/steam/colors.xml",
"install/skins/default/xui/en/strings.xml",
"install/skins/default/xui/fr/strings.xml",
"install/skins/steam/xui/en/strings.xml",
"install/skins/steam/xui/fr/strings.xml",
"user/skins/default/xui/en/strings.xml",
"user/skins/default/xui/fr/strings.xml",
"user/skins/steam/xui/en/strings.xml",
"user/skins/steam/xui/fr/strings.xml",
"install/skins/default/xui/en/floater.xml",
"install/skins/default/xui/fr/floater.xml",
"user/skins/default/xui/fr/floater.xml",
"install/skins/default/xui/en/newfile.xml",
"install/skins/default/xui/fr/newfile.xml",
"user/skins/default/xui/en/newfile.xml",
"install/skins/default/html/en-us/welcome.html",
"install/skins/default/html/fr/welcome.html",
"install/skins/default/textures/only_default.jpeg",
"install/skins/steam/textures/only_steam.jpeg",
"user/skins/default/textures/only_user_default.jpeg",
"user/skins/steam/textures/only_user_steam.jpeg",
"install/skins/default/future/somefile.txt"
};
for (const char* path : preload)
{
buildFilesystem(path);
}
}
virtual ~LLDir_Dummy() {}
virtual void initAppDirs(const std::string& app_name, const std::string& app_read_only_data_dir)
{
// Implement this when we write a test that needs it
}
virtual std::string getCurPath()
{
// Implement this when we write a test that needs it
return "";
}
virtual U32 countFilesInDir(const std::string& dirname, const std::string& mask)
{
// Implement this when we write a test that needs it
return 0;
}
virtual bool fileExists(const std::string& pathname) const
{
// Record fileExists() calls so we can check whether caching is
// working right. Certain LLDir calls should be able to make decisions
// without calling fileExists() again, having already checked existence.
mChecked.insert(pathname);
// For our simple flat set of strings, see whether the identical
// pathname exists in our set.
return (mFilesystem.find(pathname) != mFilesystem.end());
}
virtual std::string getLLPluginLauncher()
{
// Implement this when we write a test that needs it
return "";
}
virtual std::string getLLPluginFilename(std::string base_name)
{
// Implement this when we write a test that needs it
return "";
}
/*----------------------------- Dummy data -----------------------------*/
void clearFilesystem() { mFilesystem.clear(); }
void buildFilesystem(const std::string& path)
{
// Split the pathname on slashes, ignoring leading, trailing, doubles
StringVec components;
LLStringUtil::getTokens(path, components, "/");
// Ensure we have an entry representing every level of this path
std::string partial;
for (std::string component : components)
{
append(partial, component);
mFilesystem.insert(partial);
}
}
void clear_checked() { mChecked.clear(); }
void ensure_checked(const std::string& pathname) const
{
tut::ensure(STRINGIZE(pathname << " was not checked but should have been"),
mChecked.find(pathname) != mChecked.end());
}
void ensure_not_checked(const std::string& pathname) const
{
tut::ensure(STRINGIZE(pathname << " was checked but should not have been"),
mChecked.find(pathname) == mChecked.end());
}
std::set<std::string> mFilesystem;
mutable std::set<std::string> mChecked;
};
namespace tut
{
struct LLDirTest
{
};
typedef test_group<LLDirTest> LLDirTest_t;
typedef LLDirTest_t::object LLDirTest_object_t;
tut::LLDirTest_t tut_LLDirTest("LLDir");
template<> template<>
void LLDirTest_object_t::test<1>()
// getDirDelimiter
{
ensure("getDirDelimiter", !gDirUtilp->getDirDelimiter().empty());
}
template<> template<>
void LLDirTest_object_t::test<2>()
// getBaseFileName
{
std::string delim = gDirUtilp->getDirDelimiter();
std::string rawFile = "foo";
std::string rawFileExt = "foo.bAr";
std::string rawFileNullExt = "foo.";
std::string rawExt = ".bAr";
std::string rawDot = ".";
std::string pathNoExt = "aa" + delim + "bb" + delim + "cc" + delim + "dd" + delim + "ee";
std::string pathExt = pathNoExt + ".eXt";
std::string dottedPathNoExt = "aa" + delim + "bb" + delim + "cc.dd" + delim + "ee";
std::string dottedPathExt = dottedPathNoExt + ".eXt";
// foo[.bAr]
ensure_equals("getBaseFileName/r-no-ext/no-strip-exten",
gDirUtilp->getBaseFileName(rawFile, false),
"foo");
ensure_equals("getBaseFileName/r-no-ext/strip-exten",
gDirUtilp->getBaseFileName(rawFile, true),
"foo");
ensure_equals("getBaseFileName/r-ext/no-strip-exten",
gDirUtilp->getBaseFileName(rawFileExt, false),
"foo.bAr");
ensure_equals("getBaseFileName/r-ext/strip-exten",
gDirUtilp->getBaseFileName(rawFileExt, true),
"foo");
// foo.
ensure_equals("getBaseFileName/rn-no-ext/no-strip-exten",
gDirUtilp->getBaseFileName(rawFileNullExt, false),
"foo.");
ensure_equals("getBaseFileName/rn-no-ext/strip-exten",
gDirUtilp->getBaseFileName(rawFileNullExt, true),
"foo");
// .bAr
// interesting case - with no basename, this IS the basename, not the extension.
ensure_equals("getBaseFileName/e-ext/no-strip-exten",
gDirUtilp->getBaseFileName(rawExt, false),
".bAr");
ensure_equals("getBaseFileName/e-ext/strip-exten",
gDirUtilp->getBaseFileName(rawExt, true),
".bAr");
// .
ensure_equals("getBaseFileName/d/no-strip-exten",
gDirUtilp->getBaseFileName(rawDot, false),
".");
ensure_equals("getBaseFileName/d/strip-exten",
gDirUtilp->getBaseFileName(rawDot, true),
".");
// aa/bb/cc/dd/ee[.eXt]
ensure_equals("getBaseFileName/no-ext/no-strip-exten",
gDirUtilp->getBaseFileName(pathNoExt, false),
"ee");
ensure_equals("getBaseFileName/no-ext/strip-exten",
gDirUtilp->getBaseFileName(pathNoExt, true),
"ee");
ensure_equals("getBaseFileName/ext/no-strip-exten",
gDirUtilp->getBaseFileName(pathExt, false),
"ee.eXt");
ensure_equals("getBaseFileName/ext/strip-exten",
gDirUtilp->getBaseFileName(pathExt, true),
"ee");
// aa/bb/cc.dd/ee[.eXt]
ensure_equals("getBaseFileName/d-no-ext/no-strip-exten",
gDirUtilp->getBaseFileName(dottedPathNoExt, false),
"ee");
ensure_equals("getBaseFileName/d-no-ext/strip-exten",
gDirUtilp->getBaseFileName(dottedPathNoExt, true),
"ee");
ensure_equals("getBaseFileName/d-ext/no-strip-exten",
gDirUtilp->getBaseFileName(dottedPathExt, false),
"ee.eXt");
ensure_equals("getBaseFileName/d-ext/strip-exten",
gDirUtilp->getBaseFileName(dottedPathExt, true),
"ee");
}
template<> template<>
void LLDirTest_object_t::test<3>()
// getDirName
{
std::string delim = gDirUtilp->getDirDelimiter();
std::string rawFile = "foo";
std::string rawFileExt = "foo.bAr";
std::string pathNoExt = "aa" + delim + "bb" + delim + "cc" + delim + "dd" + delim + "ee";
std::string pathExt = pathNoExt + ".eXt";
std::string dottedPathNoExt = "aa" + delim + "bb" + delim + "cc.dd" + delim + "ee";
std::string dottedPathExt = dottedPathNoExt + ".eXt";
// foo[.bAr]
ensure_equals("getDirName/r-no-ext",
gDirUtilp->getDirName(rawFile),
"");
ensure_equals("getDirName/r-ext",
gDirUtilp->getDirName(rawFileExt),
"");
// aa/bb/cc/dd/ee[.eXt]
ensure_equals("getDirName/no-ext",
gDirUtilp->getDirName(pathNoExt),
"aa" + delim + "bb" + delim + "cc" + delim + "dd");
ensure_equals("getDirName/ext",
gDirUtilp->getDirName(pathExt),
"aa" + delim + "bb" + delim + "cc" + delim + "dd");
// aa/bb/cc.dd/ee[.eXt]
ensure_equals("getDirName/d-no-ext",
gDirUtilp->getDirName(dottedPathNoExt),
"aa" + delim + "bb" + delim + "cc.dd");
ensure_equals("getDirName/d-ext",
gDirUtilp->getDirName(dottedPathExt),
"aa" + delim + "bb" + delim + "cc.dd");
}
template<> template<>
void LLDirTest_object_t::test<4>()
// getExtension
{
std::string delim = gDirUtilp->getDirDelimiter();
std::string rawFile = "foo";
std::string rawFileExt = "foo.bAr";
std::string rawFileNullExt = "foo.";
std::string rawExt = ".bAr";
std::string rawDot = ".";
std::string pathNoExt = "aa" + delim + "bb" + delim + "cc" + delim + "dd" + delim + "ee";
std::string pathExt = pathNoExt + ".eXt";
std::string dottedPathNoExt = "aa" + delim + "bb" + delim + "cc.dd" + delim + "ee";
std::string dottedPathExt = dottedPathNoExt + ".eXt";
// foo[.bAr]
ensure_equals("getExtension/r-no-ext",
gDirUtilp->getExtension(rawFile),
"");
ensure_equals("getExtension/r-ext",
gDirUtilp->getExtension(rawFileExt),
"bar");
// foo.
ensure_equals("getExtension/rn-no-ext",
gDirUtilp->getExtension(rawFileNullExt),
"");
// .bAr
// interesting case - with no basename, this IS the basename, not the extension.
ensure_equals("getExtension/e-ext",
gDirUtilp->getExtension(rawExt),
"");
// .
ensure_equals("getExtension/d",
gDirUtilp->getExtension(rawDot),
"");
// aa/bb/cc/dd/ee[.eXt]
ensure_equals("getExtension/no-ext",
gDirUtilp->getExtension(pathNoExt),
"");
ensure_equals("getExtension/ext",
gDirUtilp->getExtension(pathExt),
"ext");
// aa/bb/cc.dd/ee[.eXt]
ensure_equals("getExtension/d-no-ext",
gDirUtilp->getExtension(dottedPathNoExt),
"");
ensure_equals("getExtension/d-ext",
gDirUtilp->getExtension(dottedPathExt),
"ext");
}
std::string makeTestFile( const std::string& dir, const std::string& file )
{
std::string path = dir + file;
LLFILE* handle = LLFile::fopen( path, "w" );
ensure("failed to open test file '"+path+"'", handle != NULL );
// Harbison & Steele, 4th ed., p. 366: "If an error occurs, fputs
// returns EOF; otherwise, it returns some other, nonnegative value."
ensure("failed to write to test file '"+path+"'", EOF != fputs("test file", handle) );
fclose(handle);
return path;
}
std::string makeTestDir( const std::string& dirbase )
{
int counter;
std::string uniqueDir;
bool foundUnused;
std::string delim = gDirUtilp->getDirDelimiter();
for (counter=0, foundUnused=false; !foundUnused; counter++ )
{
char counterStr[3];
sprintf(counterStr, "%02d", counter);
uniqueDir = dirbase + counterStr;
foundUnused = ! ( LLFile::isdir(uniqueDir) || LLFile::isfile(uniqueDir) );
}
ensure("test directory '" + uniqueDir + "' creation failed", !LLFile::mkdir(uniqueDir));
return uniqueDir + delim; // HACK - apparently, the trailing delimiter is needed...
}
static const char* DirScanFilename[5] = { "file1.abc", "file2.abc", "file1.xyz", "file2.xyz", "file1.mno" };
void scanTest(const std::string& directory, const std::string& pattern, bool correctResult[5])
{
// Scan directory and see if any file1.* files are found
std::string scanResult;
int found = 0;
bool filesFound[5] = { false, false, false, false, false };
//std::cerr << "searching '"+directory+"' for '"+pattern+"'\n";
LLDirIterator iter(directory, pattern);
while ( found <= 5 && iter.next(scanResult) )
{
found++;
//std::cerr << " found '"+scanResult+"'\n";
int check;
for (check=0; check < 5 && ! ( scanResult == DirScanFilename[check] ); check++)
{
}
// check is now either 5 (not found) or the index of the matching name
if (check < 5)
{
ensure( "found file '"+(std::string)DirScanFilename[check]+"' twice", ! filesFound[check] );
filesFound[check] = true;
}
else // check is 5 - should not happen
{
fail( "found unknown file '"+scanResult+"'");
}
}
for (int i=0; i<5; i++)
{
if (correctResult[i])
{
ensure("scan of '"+directory+"' using '"+pattern+"' did not return '"+DirScanFilename[i]+"'", filesFound[i]);
}
else
{
ensure("scan of '"+directory+"' using '"+pattern+"' incorrectly returned '"+DirScanFilename[i]+"'", !filesFound[i]);
}
}
}
template<> template<>
void LLDirTest_object_t::test<5>()
// LLDirIterator::next
{
std::string delim = gDirUtilp->getDirDelimiter();
std::string dirTemp = LLFile::tmpdir();
// Create the same 5 file names of the two directories
std::string dir1 = makeTestDir(dirTemp + "LLDirIterator");
std::string dir2 = makeTestDir(dirTemp + "LLDirIterator");
std::string dir1files[5];
std::string dir2files[5];
for (int i=0; i<5; i++)
{
dir1files[i] = makeTestFile(dir1, DirScanFilename[i]);
dir2files[i] = makeTestFile(dir2, DirScanFilename[i]);
}
// Scan dir1 and see if each of the 5 files is found exactly once
bool expected1[5] = { true, true, true, true, true };
scanTest(dir1, "*", expected1);
// Scan dir2 and see if only the 2 *.xyz files are found
bool expected2[5] = { false, false, true, true, false };
scanTest(dir1, "*.xyz", expected2);
// Scan dir2 and see if only the 1 *.mno file is found
bool expected3[5] = { false, false, false, false, true };
scanTest(dir2, "*.mno", expected3);
// Scan dir1 and see if any *.foo files are found
bool expected4[5] = { false, false, false, false, false };
scanTest(dir1, "*.foo", expected4);
// Scan dir1 and see if any file1.* files are found
bool expected5[5] = { true, false, true, false, true };
scanTest(dir1, "file1.*", expected5);
// Scan dir1 and see if any file1.* files are found
bool expected6[5] = { true, true, false, false, false };
scanTest(dir1, "file?.abc", expected6);
// Scan dir2 and see if any file?.x?z files are found
bool expected7[5] = { false, false, true, true, false };
scanTest(dir2, "file?.x?z", expected7);
// Scan dir2 and see if any file?.??c files are found
bool expected8[5] = { true, true, false, false, false };
scanTest(dir2, "file?.??c", expected8);
scanTest(dir2, "*.??c", expected8);
// Scan dir1 and see if any *.?n? files are found
bool expected9[5] = { false, false, false, false, true };
scanTest(dir1, "*.?n?", expected9);
// Scan dir1 and see if any *.???? files are found
bool expected10[5] = { false, false, false, false, false };
scanTest(dir1, "*.????", expected10);
// Scan dir1 and see if any ?????.* files are found
bool expected11[5] = { true, true, true, true, true };
scanTest(dir1, "?????.*", expected11);
// Scan dir1 and see if any ??l??.xyz files are found
bool expected12[5] = { false, false, true, true, false };
scanTest(dir1, "??l??.xyz", expected12);
bool expected13[5] = { true, false, true, false, false };
scanTest(dir1, "file1.{abc,xyz}", expected13);
bool expected14[5] = { true, true, false, false, false };
scanTest(dir1, "file[0-9].abc", expected14);
bool expected15[5] = { true, true, false, false, false };
scanTest(dir1, "file[!a-z].abc", expected15);
// clean up all test files and directories
for (int i=0; i<5; i++)
{
LLFile::remove(dir1files[i]);
LLFile::remove(dir2files[i]);
}
LLFile::rmdir(dir1);
LLFile::rmdir(dir2);
}
template<> template<>
void LLDirTest_object_t::test<6>()
{
set_test_name("findSkinnedFilenames()");
LLDir_Dummy lldir;
/*------------------------ "default", "en" -------------------------*/
// Setting "default" means we shouldn't consider any "*/skins/steam"
// directories; setting "en" means we shouldn't consider any "xui/fr"
// directories.
lldir.setSkinFolder("default", "en");
ensure_equals(lldir.getSkinFolder(), "default");
ensure_equals(lldir.getLanguage(), "en");
// top-level directory of a skin isn't localized
ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS),
vec(list_of("install/skins/default/colors.xml")
("user/skins/default/colors.xml")));
// We should not have needed to check for skins/default/en. We should
// just "know" that SKINBASE is not localized.
lldir.ensure_not_checked("install/skins/default/en");
ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"),
vec(list_of("install/skins/default/textures/only_default.jpeg")));
// Nor should we have needed to check skins/default/textures/en
// because textures is known not to be localized.
lldir.ensure_not_checked("install/skins/default/textures/en");
StringVec expected(vec(list_of("install/skins/default/xui/en/strings.xml")
("user/skins/default/xui/en/strings.xml")));
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
expected);
// The first time, we had to probe to find out whether xui was localized.
lldir.ensure_checked("install/skins/default/xui/en");
lldir.clear_checked();
// Now make the same call again -- should return same result --
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
expected);
// but this time it should remember that xui is localized.
lldir.ensure_not_checked("install/skins/default/xui/en");
// localized subdir with "en-us" instead of "en"
ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
vec(list_of("install/skins/default/html/en-us/welcome.html")));
lldir.ensure_checked("install/skins/default/html/en");
lldir.ensure_checked("install/skins/default/html/en-us");
lldir.clear_checked();
ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
vec(list_of("install/skins/default/html/en-us/welcome.html")));
lldir.ensure_not_checked("install/skins/default/html/en");
lldir.ensure_not_checked("install/skins/default/html/en-us");
ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"),
vec(list_of("install/skins/default/future/somefile.txt")));
// Test probing for an unrecognized unlocalized future subdir.
lldir.ensure_checked("install/skins/default/future/en");
lldir.clear_checked();
ensure_equals(lldir.findSkinnedFilenames("future", "somefile.txt"),
vec(list_of("install/skins/default/future/somefile.txt")));
// Second time it should remember that future is unlocalized.
lldir.ensure_not_checked("install/skins/default/future/en");
// When language is set to "en", requesting an html file pulls up the
// "en-us" version -- not because it magically matches those strings,
// but because there's no "en" localization and it falls back on the
// default "en-us"! Note that it would probably still be better to
// make the default localization be "en" and allow "en-gb" (or
// whatever) localizations, which would work much more the way you'd
// expect.
ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
vec(list_of("install/skins/default/html/en-us/welcome.html")));
/*------------------------ "default", "fr" -------------------------*/
// We start being able to distinguish localized subdirs from
// unlocalized when we ask for a non-English language.
lldir.setSkinFolder("default", "fr");
ensure_equals(lldir.getLanguage(), "fr");
// pass merge=true to request this filename in all relevant skins
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
vec(list_of
("install/skins/default/xui/en/strings.xml")
("install/skins/default/xui/fr/strings.xml")
("user/skins/default/xui/en/strings.xml")
("user/skins/default/xui/fr/strings.xml")));
// pass (or default) merge=false to request only most specific skin
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
vec(list_of
("user/skins/default/xui/en/strings.xml")
("user/skins/default/xui/fr/strings.xml")));
// Our dummy floater.xml has a user localization (for "fr") but no
// English override. This is a case in which CURRENT_SKIN nonetheless
// returns paths from two different skins.
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "floater.xml"),
vec(list_of
("install/skins/default/xui/en/floater.xml")
("user/skins/default/xui/fr/floater.xml")));
// Our dummy newfile.xml has an English override but no user
// localization. This is another case in which CURRENT_SKIN
// nonetheless returns paths from two different skins.
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "newfile.xml"),
vec(list_of
("user/skins/default/xui/en/newfile.xml")
("install/skins/default/xui/fr/newfile.xml")));
ensure_equals(lldir.findSkinnedFilenames("html", "welcome.html"),
vec(list_of
("install/skins/default/html/en-us/welcome.html")
("install/skins/default/html/fr/welcome.html")));
/*------------------------ "default", "zh" -------------------------*/
lldir.setSkinFolder("default", "zh");
// Because strings.xml has only a "fr" override but no "zh" override
// in any skin, the most localized version we can find is "en".
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
vec(list_of("user/skins/default/xui/en/strings.xml")));
/*------------------------- "steam", "en" --------------------------*/
lldir.setSkinFolder("steam", "en");
ensure_equals(lldir.findSkinnedFilenames(LLDir::SKINBASE, "colors.xml", LLDir::ALL_SKINS),
vec(list_of
("install/skins/default/colors.xml")
("install/skins/steam/colors.xml")
("user/skins/default/colors.xml")
("user/skins/steam/colors.xml")));
ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_default.jpeg"),
vec(list_of("install/skins/default/textures/only_default.jpeg")));
ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_steam.jpeg"),
vec(list_of("install/skins/steam/textures/only_steam.jpeg")));
ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_default.jpeg"),
vec(list_of("user/skins/default/textures/only_user_default.jpeg")));
ensure_equals(lldir.findSkinnedFilenames(LLDir::TEXTURES, "only_user_steam.jpeg"),
vec(list_of("user/skins/steam/textures/only_user_steam.jpeg")));
// CURRENT_SKIN
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
vec(list_of("user/skins/steam/xui/en/strings.xml")));
// pass constraint=ALL_SKINS to request this filename in all relevant skins
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
vec(list_of
("install/skins/default/xui/en/strings.xml")
("install/skins/steam/xui/en/strings.xml")
("user/skins/default/xui/en/strings.xml")
("user/skins/steam/xui/en/strings.xml")));
/*------------------------- "steam", "fr" --------------------------*/
lldir.setSkinFolder("steam", "fr");
// pass CURRENT_SKIN to request only the most specialized files
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml"),
vec(list_of
("user/skins/steam/xui/en/strings.xml")
("user/skins/steam/xui/fr/strings.xml")));
// pass ALL_SKINS to request this filename in all relevant skins
ensure_equals(lldir.findSkinnedFilenames(LLDir::XUI, "strings.xml", LLDir::ALL_SKINS),
vec(list_of
("install/skins/default/xui/en/strings.xml")
("install/skins/default/xui/fr/strings.xml")
("install/skins/steam/xui/en/strings.xml")
("install/skins/steam/xui/fr/strings.xml")
("user/skins/default/xui/en/strings.xml")
("user/skins/default/xui/fr/strings.xml")
("user/skins/steam/xui/en/strings.xml")
("user/skins/steam/xui/fr/strings.xml")));
}
template<> template<>
void LLDirTest_object_t::test<7>()
{
set_test_name("add()");
LLDir_Dummy lldir;
ensure_equals("both empty", lldir.add("", ""), "");
ensure_equals("path empty", lldir.add("", "b"), "b");
ensure_equals("name empty", lldir.add("a", ""), "a");
ensure_equals("both simple", lldir.add("a", "b"), "a/b");
ensure_equals("name leading slash", lldir.add("a", "/b"), "a/b");
ensure_equals("path trailing slash", lldir.add("a/", "b"), "a/b");
ensure_equals("both bring slashes", lldir.add("a/", "/b"), "a/b");
}
}
@@ -0,0 +1,65 @@
/**
* @file lldiriterator_test.cpp
* @date 2011-06
* @brief LLDirIterator test cases.
*
* $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 "linden_common.h"
#include "lltut.h"
#include "../lldiriterator.h"
namespace tut
{
struct LLDirIteratorFixture
{
LLDirIteratorFixture()
{
}
};
typedef test_group<LLDirIteratorFixture> LLDirIteratorTest_factory;
typedef LLDirIteratorTest_factory::object LLDirIteratorTest_t;
LLDirIteratorTest_factory tf("LLDirIterator");
/*
CHOP-662 was originally introduced to deal with crashes deleting files from
a directory (VWR-25500). However, this introduced a crash looking for
old chat logs as the glob_to_regex function in lldiriterator wasn't escaping lots of regexp characters
*/
void test_chop_662(void)
{
// Check a selection of bad group names from the crash reports
LLDirIterator iter(".","+bad-group-name]+?\?-??.*");
LLDirIterator iter1(".","))--@---bad-group-name2((?\?-??.*\\.txt");
LLDirIterator iter2(".","__^v--x)Cuide d sua vida(x--v^__?\?-??.*");
}
template<> template<>
void LLDirIteratorTest_t::test<1>()
{
test_chop_662();
}
}