From 3653727e7f84f10caefb6ea7dc33859455ebfa0b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 Oct 2012 14:57:43 -0400 Subject: Introduce new LLDir::findSkinnedFilenames() method. Use as needed. In a number of different places, for different reasons, the viewer wants to load a UI-related file that might be overridden by a non-default skin; and within that skin, might further be overridden by a non-default language. Apparently, for each of those use cases, every individual developer approached it as an entirely new problem, solving it idiosyncratically for that one case. Not only is this a maintenance problem, but it rubs one's nose in the fact that most such solutions consider only a subset of the relevant skin directories. Richard and I evolved an API intended to address all such cases: a central LLDir method returning a list of relevant pathnames, from most general to most localized, filtered to present only existing files; plus a couple of convenience methods to specifically obtain the most general and most localized available file. There were several load-skinned-file methods (LLFloater::buildFromFile(), LLPanel::buildFromFile() and LLUICtrlFactory::createFromFile() -- apparently cloned-and-modified from each other) that contained funky bolted-on logic to output the loaded data to an optional passed LLXMLNodePtr param. The trouble is that passing that param forced each of these methods to subvert its normal search: specifically for that case, it needed to find the baseline XML file instead of the localized one. Richard agreed that for the intended usage (reformatting XML files) we should use XML schema instead, and that the hacky functionality should be removed. Remove it. Also remove LLUICtrlFactory::getLocalizedXMLNode(), only used for those three special cases. Some callers explicitly passed the optional LLXMLNodePtr param as NULL. Remove that. Remove LLFloaterUIPreview::displayFloater(save) param, which relied on the optional output LLXMLNodePtr param. Make onClickSaveFloater() and onClickSaveAll() emit popupAndPrintWarning() about discontinued functionality. Recast LLFloater::buildFromFile(), LLPanel::buildFromFile(), LLUICtrlFactory::createFromFile(), LLNotifications::loadTemplates(), LLUI::locateSkin(), LLFontRegistry::parseFontInfo(), LLUIColorTable::loadFromSettings(), LLUICtrlFactory::loadWidgetTemplate(), LLUICtrlFactory::getLayeredXMLNode(), LLUIImageList::initFromFile(), LLAppViewer::launchUpdater() and LLMediaCtrl::navigateToLocalPage() to use findSkinnedFilenames(). (Is LLAppViewer::launchUpdater() ever called any more? Apparently so -- though the linux-updater.bin logic to process the relevant command-line switch has been disabled. Shrug.) (Is LLMediaCtrl::navigateToLocalPage() ever used?? If so, why?) Remove LLUI::setupPaths(), getXUIPaths(), getSkinPath() and getLocalizedSkinPath(). Remove the skins/paths.xml file read by setupPaths(). The only configuration it contained was the pair of partial paths "xui/en" and "xui/[LANGUAGE]" -- hardly likely to change. getSkinPath() specifically returned the first of these, while getLocalizedSkinPath() specifically returned the second. This knowledge is now embedded in findSkinnedFilenames(). Also remove paths.xml from viewer_manifest.py. Remove injected xui_paths from LLFontGL::initClass() and LLFontRegistry::LLFontRegistry(). These are no longer needed since LLFontRegistry can now directly consult LLDir for its path search. Stop passing LLUI::getXUIPaths() to LLFontGL::initClass() in LLViewerWindow's constructor and initFonts() method. Add LLDir::append() and add() methods for the simple task of combining two path components separated by getDirDelimiter() -- but only if they're both non-empty. Amazing how often that logic is replicated. Replace some existing concatenations with add() or append(). New LLDir::findSkinnedFilenames() method must know current language. Allow injecting current language by adding an LLDir::setSkinFolder(language) param, and pass it where LLAppViewer::init() and initConfiguration() currently call setSkinFolder(). Also add LLDir::getSkinFolder() and getLanguage() methods. Change LLFLoaterUIPreview's LLLocalizationResetForcer helper to "forcibly reset language" using LLDir::setSkinFolder() instead of LLUI::setupPaths(). Update LLDir stubs in lldir_stub.cpp and llupdaterservice_test.cpp. Add LLDir::getUserDefaultSkinDir() to obtain often-overlooked possible skin directory -- like getUserSkinDir() but with "default" in place of the current skin name as the last path component. (However, we hope findSkinnedFilenames() obviates most explicit use of such individual skin directory pathnames.) Add LLDir unit tests for new findSkinnedFilenames() and add() methods -- the latter exercises append() as well. Tweak indra/integration_tests/llui_libtest/llui_libtest.cpp for all the above. Notably, comment out its export_test_floaters() function, since the essential LLFloater::buildFromFile(optional LLXMLNodePtr) functionality has been removed. This may mean that llui_libtest.cpp has little remaining value, not sure. --- indra/llvfs/lldir.cpp | 458 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 351 insertions(+), 107 deletions(-) (limited to 'indra/llvfs/lldir.cpp') diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index 32d081d552..a7d12476a4 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -41,6 +41,12 @@ #include "lluuid.h" #include "lldiriterator.h" +#include "stringize.h" +#include +#include +#include +#include +#include #if LL_WINDOWS #include "lldir_win32.h" @@ -58,6 +64,14 @@ LLDir_Linux gDirUtil; LLDir *gDirUtilp = (LLDir *)&gDirUtil; +/// Values for findSkinnedFilenames(subdir) parameter +const char + *LLDir::XUI = "xui", + *LLDir::TEXTURES = "textures", + *LLDir::SKINBASE = ""; + +static const char* const empty = ""; + LLDir::LLDir() : mAppName(""), mExecutablePathAndName(""), @@ -70,7 +84,8 @@ LLDir::LLDir() mOSCacheDir(""), mCAFile(""), mTempDir(""), - mDirDelimiter("/") // fallback to forward slash if not overridden + mDirDelimiter("/"), // fallback to forward slash if not overridden + mLanguage("en") { } @@ -96,9 +111,7 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask) LLDirIterator iter(dirname, mask); while (iter.next(filename)) { - fullpath = dirname; - fullpath += getDirDelimiter(); - fullpath += filename; + fullpath = add(dirname, filename); if(LLFile::isdir(fullpath)) { @@ -270,12 +283,12 @@ std::string LLDir::buildSLOSCacheDir() const } else { - res = getOSUserAppDir() + mDirDelimiter + "cache"; + res = add(getOSUserAppDir(), "cache"); } } else { - res = getOSCacheDir() + mDirDelimiter + "SecondLife"; + res = add(getOSCacheDir(), "SecondLife"); } return res; } @@ -298,19 +311,24 @@ const std::string &LLDir::getDirDelimiter() const return mDirDelimiter; } +const std::string& LLDir::getDefaultSkinDir() const +{ + return mDefaultSkinDir; +} + const std::string &LLDir::getSkinDir() const { return mSkinDir; } -const std::string &LLDir::getUserSkinDir() const +const std::string &LLDir::getUserDefaultSkinDir() const { - return mUserSkinDir; + return mUserDefaultSkinDir; } -const std::string& LLDir::getDefaultSkinDir() const +const std::string &LLDir::getUserSkinDir() const { - return mDefaultSkinDir; + return mUserSkinDir; } const std::string LLDir::getSkinBaseDir() const @@ -323,6 +341,41 @@ const std::string &LLDir::getLLPluginDir() const return mLLPluginDir; } +static std::string ELLPathToString(ELLPath location) +{ + typedef std::map ELLPathMap; +#define ENT(symbol) ELLPathMap::value_type(symbol, #symbol) + static ELLPathMap::value_type init[] = + { + ENT(LL_PATH_NONE), + ENT(LL_PATH_USER_SETTINGS), + ENT(LL_PATH_APP_SETTINGS), + ENT(LL_PATH_PER_SL_ACCOUNT), // returns/expands to blank string if we don't know the account name yet + ENT(LL_PATH_CACHE), + ENT(LL_PATH_CHARACTER), + ENT(LL_PATH_HELP), + ENT(LL_PATH_LOGS), + ENT(LL_PATH_TEMP), + ENT(LL_PATH_SKINS), + ENT(LL_PATH_TOP_SKIN), + ENT(LL_PATH_CHAT_LOGS), + ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS), + ENT(LL_PATH_USER_SKIN), + ENT(LL_PATH_LOCAL_ASSETS), + ENT(LL_PATH_EXECUTABLE), + ENT(LL_PATH_DEFAULT_SKIN), + ENT(LL_PATH_FONTS), + ENT(LL_PATH_LAST) + }; +#undef ENT + static const ELLPathMap sMap(boost::begin(init), boost::end(init)); + + ELLPathMap::const_iterator found = sMap.find(location); + if (found != sMap.end()) + return found->second; + return STRINGIZE("Invalid ELLPath value " << location); +} + std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const { return getExpandedFilename(location, "", filename); @@ -343,15 +396,11 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_APP_SETTINGS: - prefix = getAppRODataDir(); - prefix += mDirDelimiter; - prefix += "app_settings"; + prefix = add(getAppRODataDir(), "app_settings"); break; case LL_PATH_CHARACTER: - prefix = getAppRODataDir(); - prefix += mDirDelimiter; - prefix += "character"; + prefix = add(getAppRODataDir(), "character"); break; case LL_PATH_HELP: @@ -363,16 +412,22 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_USER_SETTINGS: - prefix = getOSUserAppDir(); - prefix += mDirDelimiter; - prefix += "user_settings"; + prefix = add(getOSUserAppDir(), "user_settings"); break; case LL_PATH_PER_SL_ACCOUNT: prefix = getLindenUserDir(); if (prefix.empty()) { - // if we're asking for the per-SL-account directory but we haven't logged in yet (or otherwise don't know the account name from which to build this string), then intentionally return a blank string to the caller and skip the below warning about a blank prefix. + // if we're asking for the per-SL-account directory but we haven't + // logged in yet (or otherwise don't know the account name from + // which to build this string), then intentionally return a blank + // string to the caller and skip the below warning about a blank + // prefix. + LL_DEBUGS("LLDir") << "getLindenUserDir() not yet set: " + << ELLPathToString(location) + << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename + << "' => ''" << LL_ENDL; return std::string(); } break; @@ -386,9 +441,7 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_LOGS: - prefix = getOSUserAppDir(); - prefix += mDirDelimiter; - prefix += "logs"; + prefix = add(getOSUserAppDir(), "logs"); break; case LL_PATH_TEMP: @@ -412,9 +465,7 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_LOCAL_ASSETS: - prefix = getAppRODataDir(); - prefix += mDirDelimiter; - prefix += "local_assets"; + prefix = add(getAppRODataDir(), "local_assets"); break; case LL_PATH_EXECUTABLE: @@ -422,56 +473,36 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_FONTS: - prefix = getAppRODataDir(); - prefix += mDirDelimiter; - prefix += "fonts"; + prefix = add(getAppRODataDir(), "fonts"); break; default: llassert(0); } - std::string filename = in_filename; - if (!subdir2.empty()) - { - filename = subdir2 + mDirDelimiter + filename; - } - - if (!subdir1.empty()) - { - filename = subdir1 + mDirDelimiter + filename; - } - if (prefix.empty()) { - llwarns << "prefix is empty, possible bad filename" << llendl; - } - - std::string expanded_filename; - if (!filename.empty()) - { - if (!prefix.empty()) - { - expanded_filename += prefix; - expanded_filename += mDirDelimiter; - expanded_filename += filename; - } - else - { - expanded_filename = filename; - } - } - else if (!prefix.empty()) - { - // Directory only, no file name. - expanded_filename = prefix; + llwarns << ELLPathToString(location) + << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename + << "': prefix is empty, possible bad filename" << llendl; } - else + + std::string expanded_filename = add(add(prefix, subdir1), subdir2); + if (expanded_filename.empty() && in_filename.empty()) { - expanded_filename.assign(""); + return ""; } - - //llinfos << "*** EXPANDED FILENAME: <" << expanded_filename << ">" << llendl; + // Use explicit concatenation here instead of another add() call. Callers + // passing in_filename as "" expect to obtain a pathname ending with + // mDirSeparator so they can later directly concatenate with a specific + // filename. A caller using add() doesn't care, but there's still code + // loose in the system that uses std::string::operator+(). + expanded_filename += mDirDelimiter; + expanded_filename += in_filename; + + LL_DEBUGS("LLDir") << ELLPathToString(location) + << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename + << "' => '" << expanded_filename << "'" << LL_ENDL; return expanded_filename; } @@ -511,31 +542,168 @@ std::string LLDir::getExtension(const std::string& filepath) const return exten; } -std::string LLDir::findSkinnedFilename(const std::string &filename) const +std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir, + const std::string &filename, + bool merge) const { - return findSkinnedFilename("", "", filename); + // This implementation is basically just as described in the declaration comments. + std::vector found(findSkinnedFilenames(subdir, filename, merge)); + if (found.empty()) + { + return ""; + } + return found.front(); } -std::string LLDir::findSkinnedFilename(const std::string &subdir, const std::string &filename) const +std::string LLDir::findSkinnedFilename(const std::string &subdir, + const std::string &filename, + bool merge) const { - return findSkinnedFilename("", subdir, filename); + // This implementation is basically just as described in the declaration comments. + std::vector found(findSkinnedFilenames(subdir, filename, merge)); + if (found.empty()) + { + return ""; + } + return found.back(); } -std::string LLDir::findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const +std::vector LLDir::findSkinnedFilenames(const std::string& subdir, + const std::string& filename, + bool merge) const { - // generate subdirectory path fragment, e.g. "/foo/bar", "/foo", "" - std::string subdirs = ((subdir1.empty() ? "" : mDirDelimiter) + subdir1) - + ((subdir2.empty() ? "" : mDirDelimiter) + subdir2); + // Recognize subdirs that have no localization. + static const char* sUnlocalizedData[] = + { + "", // top-level directory not localized + "textures" // textures not localized + }; + static const std::set sUnlocalized(boost::begin(sUnlocalizedData), + boost::end(sUnlocalizedData)); + + LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename + << "', merge " << std::boolalpha << merge << LL_ENDL; + + // Cache the default language directory for each subdir we've encountered. + // A cache entry whose value is the empty string means "not localized, + // don't bother checking again." + typedef std::map LocalizedMap; + static LocalizedMap sLocalized; + + // Check whether we've already discovered if this subdir is localized. + LocalizedMap::const_iterator found = sLocalized.find(subdir); + if (found == sLocalized.end()) + { + // We have not yet determined that. Is it one of the subdirs "known" + // to be unlocalized? + if (sUnlocalized.find(subdir) != sUnlocalized.end()) + { + // This subdir is known to be unlocalized. Remember that. + found = sLocalized.insert(LocalizedMap::value_type(subdir, "")).first; + } + else + { + // We do not recognize this subdir. Investigate. + std::string subdir_path(add(getDefaultSkinDir(), subdir)); + if (fileExists(add(subdir_path, "en"))) + { + // defaultSkinDir/subdir contains subdir "en". That's our + // default language; this subdir is localized. + found = sLocalized.insert(LocalizedMap::value_type(subdir, "en")).first; + } + else if (fileExists(add(subdir_path, "en-us"))) + { + // defaultSkinDir/subdir contains subdir "en-us" but not "en". + // Set as default language; this subdir is localized. + found = sLocalized.insert(LocalizedMap::value_type(subdir, "en-us")).first; + } + else + { + // defaultSkinDir/subdir contains neither "en" nor "en-us". + // Assume it's not localized. Remember that assumption. + found = sLocalized.insert(LocalizedMap::value_type(subdir, "")).first; + } + } + } + // Every code path above should have resulted in 'found' becoming a valid + // iterator to an entry in sLocalized. + llassert(found != sLocalized.end()); + + // Now -- is this subdir localized, or not? The answer determines what + // subdirectories we check (under subdir) for the requested filename. + std::vector subsubdirs; + if (found->second.empty()) + { + // subdir is not localized. filename should be located directly within it. + subsubdirs.push_back(""); + } + else + { + // subdir is localized, and found->second is the default language + // directory within it. Check both the default language and the + // current language -- if it differs from the default, of course. + subsubdirs.push_back(found->second); + if (mLanguage != found->second) + { + subsubdirs.push_back(mLanguage); + } + } + // Code below relies on subsubdirs not being empty: more specifically, on + // front() being valid. There may or may not be additional entries, but we + // have at least one. For an unlocalized subdir, it's the only one; for a + // localized subdir, it's the default one. + llassert(! subsubdirs.empty()); + + // Build results vector. + std::vector results; + BOOST_FOREACH(std::string skindir, mSearchSkinDirs) + { + std::string subdir_path(add(skindir, subdir)); + // Does subdir_path/subsubdirs[0]/filename exist? If there's more than + // one entry in subsubdirs, the first is the default language ("en"), + // the second is the current language. A skin that contains + // subdir/language/filename without also containing subdir/en/filename + // is ill-formed: skip any such skin. So to decide whether to keep + // this skin dir or skip it, we need only check for the existence of + // the first subsubdir entry ("en" or only). + std::string subsubdir_path(add(add(subdir_path, subsubdirs.front()), filename)); + if (! fileExists(subsubdir_path)) + continue; - std::vector search_paths; - - search_paths.push_back(getUserSkinDir() + subdirs); // first look in user skin override - search_paths.push_back(getSkinDir() + subdirs); // then in current skin - search_paths.push_back(getDefaultSkinDir() + subdirs); // then default skin - search_paths.push_back(getCacheDir() + subdirs); // and last in preload directory + // Here the desired filename exists in the first subsubdir. That means + // this is a skindir we want to record in results. But if the caller + // passed merge=false, we must discard all previous skindirs. + if (! merge) + { + results.clear(); + } + + // Now add every subsubdir in which filename exists. We already know + // it exists in the first one. + results.push_back(subsubdir_path); + + // Append all remaining subsubdirs in which filename exists. + for (std::vector::const_iterator ssdi(subsubdirs.begin() + 1), ssdend(subsubdirs.end()); + ssdi != ssdend; ++ssdi) + { + subsubdir_path = add(add(subdir_path, *ssdi), filename); + if (fileExists(subsubdir_path)) + { + results.push_back(subsubdir_path); + } + } + } - std::string found_file = findFile(filename, search_paths); - return found_file; + LL_DEBUGS("LLDir") << empty; + const char* comma = ""; + BOOST_FOREACH(std::string path, results) + { + LL_CONT << comma << "'" << path << "'"; + comma = ", "; + } + LL_CONT << LL_ENDL; + + return results; } std::string LLDir::getTempFilename() const @@ -546,12 +714,7 @@ std::string LLDir::getTempFilename() const random_uuid.generate(); random_uuid.toString(uuid_str); - std::string temp_filename = getTempDir(); - temp_filename += mDirDelimiter; - temp_filename += uuid_str; - temp_filename += ".tmp"; - - return temp_filename; + return add(getTempDir(), uuid_str + ".tmp"); } // static @@ -587,9 +750,7 @@ void LLDir::setLindenUserDir(const std::string &username) std::string userlower(username); LLStringUtil::toLower(userlower); LLStringUtil::replaceChar(userlower, ' ', '_'); - mLindenUserDir = getOSUserAppDir(); - mLindenUserDir += mDirDelimiter; - mLindenUserDir += userlower; + mLindenUserDir = add(getOSUserAppDir(), userlower); } else { @@ -621,9 +782,7 @@ void LLDir::setPerAccountChatLogsDir(const std::string &username) std::string userlower(username); LLStringUtil::toLower(userlower); LLStringUtil::replaceChar(userlower, ' ', '_'); - mPerAccountChatLogsDir = getChatLogsDir(); - mPerAccountChatLogsDir += mDirDelimiter; - mPerAccountChatLogsDir += userlower; + mPerAccountChatLogsDir = add(getChatLogsDir(), userlower); } else { @@ -632,25 +791,59 @@ void LLDir::setPerAccountChatLogsDir(const std::string &username) } -void LLDir::setSkinFolder(const std::string &skin_folder) +void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language) { - mSkinDir = getSkinBaseDir(); - mSkinDir += mDirDelimiter; - mSkinDir += skin_folder; + LL_DEBUGS("LLDir") << "Setting skin '" << skin_folder << "', language '" << language << "'" + << LL_ENDL; + mSkinName = skin_folder; + mLanguage = language; - // user modifications to current skin - // e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle - mUserSkinDir = getOSUserAppDir(); - mUserSkinDir += mDirDelimiter; - mUserSkinDir += "skins"; - mUserSkinDir += mDirDelimiter; - mUserSkinDir += skin_folder; + // This method is called multiple times during viewer initialization. Each + // time it's called, reset mSearchSkinDirs. + mSearchSkinDirs.clear(); // base skin which is used as fallback for all skinned files // e.g. c:\program files\secondlife\skins\default mDefaultSkinDir = getSkinBaseDir(); - mDefaultSkinDir += mDirDelimiter; - mDefaultSkinDir += "default"; + append(mDefaultSkinDir, "default"); + // This is always the most general of the search skin directories. + addSearchSkinDir(mDefaultSkinDir); + + mSkinDir = getSkinBaseDir(); + append(mSkinDir, skin_folder); + // Next level of generality is a skin installed with the viewer. + addSearchSkinDir(mSkinDir); + + // user modifications to skins, current and default + // e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle + mUserSkinDir = getOSUserAppDir(); + append(mUserSkinDir, "skins"); + mUserDefaultSkinDir = mUserSkinDir; + append(mUserDefaultSkinDir, "default"); + append(mUserSkinDir, skin_folder); + // Next level of generality is user modifications to default skin... + addSearchSkinDir(mUserDefaultSkinDir); + // then user-defined skins. + addSearchSkinDir(mUserSkinDir); +} + +void LLDir::addSearchSkinDir(const std::string& skindir) +{ + if (std::find(mSearchSkinDirs.begin(), mSearchSkinDirs.end(), skindir) == mSearchSkinDirs.end()) + { + LL_DEBUGS("LLDir") << "search skin: '" << skindir << "'" << LL_ENDL; + mSearchSkinDirs.push_back(skindir); + } +} + +std::string LLDir::getSkinFolder() const +{ + return mSkinName; +} + +std::string LLDir::getLanguage() const +{ + return mLanguage; } bool LLDir::setCacheDir(const std::string &path) @@ -664,7 +857,7 @@ bool LLDir::setCacheDir(const std::string &path) else { LLFile::mkdir(path); - std::string tempname = path + mDirDelimiter + "temp"; + std::string tempname = add(path, "temp"); LLFILE* file = LLFile::fopen(tempname,"wt"); if (file) { @@ -697,6 +890,57 @@ void LLDir::dumpCurrentDirectories() LL_DEBUGS2("AppInit","Directories") << " SkinDir: " << getSkinDir() << LL_ENDL; } +std::string LLDir::add(const std::string& path, const std::string& name) const +{ + std::string destpath(path); + append(destpath, name); + return destpath; +} + +void LLDir::append(std::string& destpath, const std::string& name) const +{ + // Delegate question of whether we need a separator to helper method. + SepOff sepoff(needSep(destpath, name)); + if (sepoff.first) // do we need a separator? + { + destpath += mDirDelimiter; + } + // If destpath ends with a separator, AND name starts with one, skip + // name's leading separator. + destpath += name.substr(sepoff.second); +} + +LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) const +{ + if (path.empty() || name.empty()) + { + // If either path or name are empty, we do not need a separator + // between them. + return SepOff(false, 0); + } + // Here we know path and name are both non-empty. But if path already ends + // with a separator, or if name already starts with a separator, we need + // not add one. + std::string::size_type seplen(mDirDelimiter.length()); + bool path_ends_sep(path.substr(path.length() - seplen) == mDirDelimiter); + bool name_starts_sep(name.substr(0, seplen) == mDirDelimiter); + if ((! path_ends_sep) && (! name_starts_sep)) + { + // If neither path nor name brings a separator to the junction, then + // we need one. + return SepOff(true, 0); + } + if (path_ends_sep && name_starts_sep) + { + // But if BOTH path and name bring a separator, we need not add one. + // Moreover, we should actually skip the leading separator of 'name'. + return SepOff(false, seplen); + } + // Here we know that either path_ends_sep or name_starts_sep is true -- + // but not both. So don't add a separator, and don't skip any characters: + // simple concatenation will do the trick. + return SepOff(false, 0); +} void dir_exists_or_crash(const std::string &dir_name) { -- cgit v1.2.3 From 730d13a76a4b06e6dbdd4bd5829a90bcb98b52b3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 11 Oct 2012 19:51:07 -0400 Subject: Change LLDir::findSkinnedFilenames() to use enum instead of bool. At Richard's suggestion, changed the bool merge parameter to new enum ESkinConstraint with values CURRENT_SKIN and ALL_SKINS. This clarifies what we're requesting at the point of the call. --- indra/llvfs/lldir.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'indra/llvfs/lldir.cpp') diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index a7d12476a4..2d0adf14e6 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -46,7 +46,6 @@ #include #include #include -#include #if LL_WINDOWS #include "lldir_win32.h" @@ -544,10 +543,10 @@ std::string LLDir::getExtension(const std::string& filepath) const std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir, const std::string &filename, - bool merge) const + ESkinConstraint constraint) const { // This implementation is basically just as described in the declaration comments. - std::vector found(findSkinnedFilenames(subdir, filename, merge)); + std::vector found(findSkinnedFilenames(subdir, filename, constraint)); if (found.empty()) { return ""; @@ -557,10 +556,10 @@ std::string LLDir::findSkinnedFilenameBaseLang(const std::string &subdir, std::string LLDir::findSkinnedFilename(const std::string &subdir, const std::string &filename, - bool merge) const + ESkinConstraint constraint) const { // This implementation is basically just as described in the declaration comments. - std::vector found(findSkinnedFilenames(subdir, filename, merge)); + std::vector found(findSkinnedFilenames(subdir, filename, constraint)); if (found.empty()) { return ""; @@ -570,7 +569,7 @@ std::string LLDir::findSkinnedFilename(const std::string &subdir, std::vector LLDir::findSkinnedFilenames(const std::string& subdir, const std::string& filename, - bool merge) const + ESkinConstraint constraint) const { // Recognize subdirs that have no localization. static const char* sUnlocalizedData[] = @@ -582,7 +581,9 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, boost::end(sUnlocalizedData)); LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename - << "', merge " << std::boolalpha << merge << LL_ENDL; + << "', constraint " + << ((constraint == CURRENT_SKIN)? "CURRENT_SKIN" : "ALL_SKINS") + << LL_ENDL; // Cache the default language directory for each subdir we've encountered. // A cache entry whose value is the empty string means "not localized, @@ -672,8 +673,8 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, // Here the desired filename exists in the first subsubdir. That means // this is a skindir we want to record in results. But if the caller - // passed merge=false, we must discard all previous skindirs. - if (! merge) + // passed constraint=CURRENT_SKIN, we must discard all previous skindirs. + if (constraint == CURRENT_SKIN) { results.clear(); } -- cgit v1.2.3 From 543b7ee9f7eb104497e971961fc0c39ed32ef54d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 12 Oct 2012 11:09:25 -0400 Subject: Fix LLUI::locateSkin() failure case; clarify lldir.cpp static init. Per code review: Previous refactoring of LLUI::locateSkin() preserved odd failure behavior: it would return last-considered pathname, whether or not it exists. Changed to emit LL_WARNS log message and return empty string. Use Boost.Assign to simplify initialization of a couple static containers in lldir.cpp. --- indra/llvfs/lldir.cpp | 71 +++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 36 deletions(-) (limited to 'indra/llvfs/lldir.cpp') diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index 2d0adf14e6..4c0978b39e 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -45,8 +45,12 @@ #include #include #include +#include #include +using boost::assign::list_of; +using boost::assign::map_list_of; + #if LL_WINDOWS #include "lldir_win32.h" LLDir_Win32 gDirUtil; @@ -342,37 +346,35 @@ const std::string &LLDir::getLLPluginDir() const static std::string ELLPathToString(ELLPath location) { - typedef std::map ELLPathMap; -#define ENT(symbol) ELLPathMap::value_type(symbol, #symbol) - static ELLPathMap::value_type init[] = - { - ENT(LL_PATH_NONE), - ENT(LL_PATH_USER_SETTINGS), - ENT(LL_PATH_APP_SETTINGS), - ENT(LL_PATH_PER_SL_ACCOUNT), // returns/expands to blank string if we don't know the account name yet - ENT(LL_PATH_CACHE), - ENT(LL_PATH_CHARACTER), - ENT(LL_PATH_HELP), - ENT(LL_PATH_LOGS), - ENT(LL_PATH_TEMP), - ENT(LL_PATH_SKINS), - ENT(LL_PATH_TOP_SKIN), - ENT(LL_PATH_CHAT_LOGS), - ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS), - ENT(LL_PATH_USER_SKIN), - ENT(LL_PATH_LOCAL_ASSETS), - ENT(LL_PATH_EXECUTABLE), - ENT(LL_PATH_DEFAULT_SKIN), - ENT(LL_PATH_FONTS), - ENT(LL_PATH_LAST) - }; + typedef std::map ELLPathMap; +#define ENT(symbol) (symbol, #symbol) + static const ELLPathMap sMap = map_list_of + ENT(LL_PATH_NONE) + ENT(LL_PATH_USER_SETTINGS) + ENT(LL_PATH_APP_SETTINGS) + ENT(LL_PATH_PER_SL_ACCOUNT) // returns/expands to blank string if we don't know the account name yet + ENT(LL_PATH_CACHE) + ENT(LL_PATH_CHARACTER) + ENT(LL_PATH_HELP) + ENT(LL_PATH_LOGS) + ENT(LL_PATH_TEMP) + ENT(LL_PATH_SKINS) + ENT(LL_PATH_TOP_SKIN) + ENT(LL_PATH_CHAT_LOGS) + ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS) + ENT(LL_PATH_USER_SKIN) + ENT(LL_PATH_LOCAL_ASSETS) + ENT(LL_PATH_EXECUTABLE) + ENT(LL_PATH_DEFAULT_SKIN) + ENT(LL_PATH_FONTS) + ENT(LL_PATH_LAST) + ; #undef ENT - static const ELLPathMap sMap(boost::begin(init), boost::end(init)); - ELLPathMap::const_iterator found = sMap.find(location); - if (found != sMap.end()) - return found->second; - return STRINGIZE("Invalid ELLPath value " << location); + ELLPathMap::const_iterator found = sMap.find(location); + if (found != sMap.end()) + return found->second; + return STRINGIZE("Invalid ELLPath value " << location); } std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const @@ -572,13 +574,10 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, ESkinConstraint constraint) const { // Recognize subdirs that have no localization. - static const char* sUnlocalizedData[] = - { - "", // top-level directory not localized - "textures" // textures not localized - }; - static const std::set sUnlocalized(boost::begin(sUnlocalizedData), - boost::end(sUnlocalizedData)); + static const std::set sUnlocalized = list_of + ("") // top-level directory not localized + ("textures") // textures not localized + ; LL_DEBUGS("LLDir") << "subdir '" << subdir << "', filename '" << filename << "', constraint " -- cgit v1.2.3 From a9ea41003587e2411c7247f84ea04df2a686552b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 23 Oct 2012 16:06:03 -0400 Subject: Allow user to override either English, or localized, skinned file. Original LLDir::findSkinnedFilenames() implementation used a tricky rule: a given skin directory was only considered if it provided a default-language override for the sought filename, regardless of whether it also provided localizations for that filename. Discussion with Richard clarifies that we want to allow the user to override neither, either or both. Change findSkinnedFilenames() accordingly; update unit tests to verify new semantics. --- indra/llvfs/lldir.cpp | 132 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 45 deletions(-) (limited to 'indra/llvfs/lldir.cpp') diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index 4c0978b39e..5e5aeefba1 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -46,6 +46,8 @@ #include #include #include +#include +#include #include using boost::assign::list_of; @@ -569,6 +571,43 @@ std::string LLDir::findSkinnedFilename(const std::string &subdir, return found.back(); } +// This method exists because the two code paths for +// findSkinnedFilenames(ALL_SKINS) and findSkinnedFilenames(CURRENT_SKIN) must +// generate the list of candidate pathnames in identical ways. The only +// difference is in the body of the inner loop. +template +void LLDir::walkSearchSkinDirs(const std::string& subdir, + const std::vector& subsubdirs, + const std::string& filename, + const FUNCTION& function) const +{ + BOOST_FOREACH(std::string skindir, mSearchSkinDirs) + { + std::string subdir_path(add(skindir, subdir)); + BOOST_FOREACH(std::string subsubdir, subsubdirs) + { + std::string full_path(add(add(subdir_path, subsubdir), filename)); + if (fileExists(full_path)) + { + function(subsubdir, full_path); + } + } + } +} + +// ridiculous little helper function that should go away when we can use lambda +inline void push_back(std::vector& vector, const std::string& value) +{ + vector.push_back(value); +} + +typedef std::map StringMap; +// ridiculous little helper function that should go away when we can use lambda +inline void store_in_map(StringMap& map, const std::string& key, const std::string& value) +{ + map[key] = value; +} + std::vector LLDir::findSkinnedFilenames(const std::string& subdir, const std::string& filename, ESkinConstraint constraint) const @@ -587,11 +626,10 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, // Cache the default language directory for each subdir we've encountered. // A cache entry whose value is the empty string means "not localized, // don't bother checking again." - typedef std::map LocalizedMap; - static LocalizedMap sLocalized; + static StringMap sLocalized; // Check whether we've already discovered if this subdir is localized. - LocalizedMap::const_iterator found = sLocalized.find(subdir); + StringMap::const_iterator found = sLocalized.find(subdir); if (found == sLocalized.end()) { // We have not yet determined that. Is it one of the subdirs "known" @@ -599,7 +637,7 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, if (sUnlocalized.find(subdir) != sUnlocalized.end()) { // This subdir is known to be unlocalized. Remember that. - found = sLocalized.insert(LocalizedMap::value_type(subdir, "")).first; + found = sLocalized.insert(StringMap::value_type(subdir, "")).first; } else { @@ -608,20 +646,20 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, if (fileExists(add(subdir_path, "en"))) { // defaultSkinDir/subdir contains subdir "en". That's our - // default language; this subdir is localized. - found = sLocalized.insert(LocalizedMap::value_type(subdir, "en")).first; + // default language; this subdir is localized. + found = sLocalized.insert(StringMap::value_type(subdir, "en")).first; } else if (fileExists(add(subdir_path, "en-us"))) { // defaultSkinDir/subdir contains subdir "en-us" but not "en". // Set as default language; this subdir is localized. - found = sLocalized.insert(LocalizedMap::value_type(subdir, "en-us")).first; + found = sLocalized.insert(StringMap::value_type(subdir, "en-us")).first; } else { // defaultSkinDir/subdir contains neither "en" nor "en-us". // Assume it's not localized. Remember that assumption. - found = sLocalized.insert(LocalizedMap::value_type(subdir, "")).first; + found = sLocalized.insert(StringMap::value_type(subdir, "")).first; } } } @@ -648,48 +686,52 @@ std::vector LLDir::findSkinnedFilenames(const std::string& subdir, subsubdirs.push_back(mLanguage); } } - // Code below relies on subsubdirs not being empty: more specifically, on - // front() being valid. There may or may not be additional entries, but we - // have at least one. For an unlocalized subdir, it's the only one; for a - // localized subdir, it's the default one. - llassert(! subsubdirs.empty()); // Build results vector. std::vector results; - BOOST_FOREACH(std::string skindir, mSearchSkinDirs) - { - std::string subdir_path(add(skindir, subdir)); - // Does subdir_path/subsubdirs[0]/filename exist? If there's more than - // one entry in subsubdirs, the first is the default language ("en"), - // the second is the current language. A skin that contains - // subdir/language/filename without also containing subdir/en/filename - // is ill-formed: skip any such skin. So to decide whether to keep - // this skin dir or skip it, we need only check for the existence of - // the first subsubdir entry ("en" or only). - std::string subsubdir_path(add(add(subdir_path, subsubdirs.front()), filename)); - if (! fileExists(subsubdir_path)) - continue; - - // Here the desired filename exists in the first subsubdir. That means - // this is a skindir we want to record in results. But if the caller - // passed constraint=CURRENT_SKIN, we must discard all previous skindirs. - if (constraint == CURRENT_SKIN) - { - results.clear(); - } - - // Now add every subsubdir in which filename exists. We already know - // it exists in the first one. - results.push_back(subsubdir_path); - - // Append all remaining subsubdirs in which filename exists. - for (std::vector::const_iterator ssdi(subsubdirs.begin() + 1), ssdend(subsubdirs.end()); - ssdi != ssdend; ++ssdi) + // The process we use depends on 'constraint'. + if (constraint != CURRENT_SKIN) // meaning ALL_SKINS + { + // ALL_SKINS is simpler: just return every pathname generated by + // walkSearchSkinDirs(). Tricky bit: walkSearchSkinDirs() passes its + // FUNCTION the subsubdir as well as the full pathname. We just want + // the full pathname. + walkSearchSkinDirs(subdir, subsubdirs, filename, + boost::bind(push_back, boost::ref(results), _2)); + } + else // CURRENT_SKIN + { + // CURRENT_SKIN turns out to be a bit of a misnomer because we might + // still return files from two different skins. In any case, this + // value of 'constraint' means we will return at most two paths: one + // for the default language, one for the current language (supposing + // those differ). + // It is important to allow a user to override only the localization + // for a particular file, for all viewer installs, without also + // overriding the default-language file. + // It is important to allow a user to override only the default- + // language file, for all viewer installs, without also overriding the + // applicable localization of that file. + // Therefore, treat the default language and the current language as + // two separate cases. For each, capture the most-specialized file + // that exists. + // Use a map keyed by subsubdir (i.e. language code). This allows us + // to handle the case of a single subsubdirs entry with the same logic + // that handles two. For every real file path generated by + // walkSearchSkinDirs(), update the map entry for its subsubdir. + StringMap path_for; + walkSearchSkinDirs(subdir, subsubdirs, filename, + boost::bind(store_in_map, boost::ref(path_for), _1, _2)); + // Now that we have a path for each of the default language and the + // current language, copy them -- in proper order -- into results. + // Don't drive this by walking the map itself: it matters that we + // generate results in the same order as subsubdirs. + BOOST_FOREACH(std::string subsubdir, subsubdirs) { - subsubdir_path = add(add(subdir_path, *ssdi), filename); - if (fileExists(subsubdir_path)) + StringMap::const_iterator found(path_for.find(subsubdir)); + if (found != path_for.end()) { - results.push_back(subsubdir_path); + results.push_back(found->second); } } } -- cgit v1.2.3