/** * @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 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); }