Use a static table of tokens for agi::Path

The set of possible tokens is fixed, so using std::map is a bunch of
pointless overhead (that turns out to not even really simplify the
code).
This commit is contained in:
Thomas Goyne 2014-07-04 12:54:28 -07:00 committed by Thomas Goyne
parent 6fab17d860
commit 93522e30a8
3 changed files with 56 additions and 61 deletions

View file

@ -14,10 +14,6 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file path.cpp
/// @brief Platform-independent path code
/// @ingroup libaegisub
#include "libaegisub/path.h"
#include "libaegisub/fs.h"
@ -26,44 +22,56 @@
#include <boost/range/distance.hpp>
namespace {
template<class T, class U>
typename T::const_iterator last_less_than(T const& cont, U const& value) {
auto it = cont.upper_bound(value);
if (it != cont.begin())
--it;
return it;
static const char *tokens[] = {
"?audio",
"?data",
"?dictionary",
"?local",
"?script",
"?temp",
"?user",
"?video"
};
int find_token(const char *str, size_t len) {
if (len < 5 || str[0] != '?') return -1;
int idx;
switch (str[1] + str[4]) {
case 'a' + 'i': idx = 0; break;
case 'd' + 'a': idx = 1; break;
case 'd' + 't': idx = 2; break;
case 'l' + 'a': idx = 3; break;
case 's' + 'i': idx = 4; break;
case 't' + 'p': idx = 5; break;
case 'u' + 'r': idx = 6; break;
case 'v' + 'e': idx = 7; break;
default: return -1;
}
return strncmp(str, tokens[idx], strlen(tokens[idx])) == 0 ? idx : -1;
}
}
namespace agi {
Path::Path() {
tokens["?user"];
tokens["?local"];
tokens["?data"];
tokens["?temp"];
tokens["?dictionary"];
static_assert(sizeof(paths) / sizeof(paths[0]) == sizeof(tokens) / sizeof(tokens[0]),
"Token and path arrays need to be the same size");
FillPlatformSpecificPaths();
tokens["?audio"];
tokens["?script"];
tokens["?video"];
}
fs::path Path::Decode(std::string const& path) const {
const auto it = last_less_than(tokens, path);
if (!it->second.empty() && boost::starts_with(path, it->first))
return (it->second/path.substr(it->first.size())).make_preferred();
return fs::path(path).make_preferred();
int idx = find_token(path.c_str(), path.size());
if (idx == -1 || paths[idx].empty())
return fs::path(path).make_preferred();
return (paths[idx]/path.substr(strlen(tokens[idx]))).make_preferred();
}
fs::path Path::MakeRelative(fs::path const& path, std::string const& token) const {
const auto it = tokens.find(token);
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token);
int idx = find_token(token.c_str(), token.size());
if (idx == -1) throw agi::InternalError("Bad token: " + token);
return MakeRelative(path, it->second);
return MakeRelative(path, paths[idx]);
}
fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const {
@ -92,53 +100,47 @@ fs::path Path::MakeRelative(fs::path const& path, fs::path const& base) const {
fs::path Path::MakeAbsolute(fs::path path, std::string const& token) const {
if (path.empty()) return path;
const auto it = tokens.find(token);
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token);
int idx = find_token(token.c_str(), token.size());
if (idx == -1) throw agi::InternalError("Bad token: " + token);
path.make_preferred();
const auto str = path.string();
if (boost::starts_with(str, "?dummy") || boost::starts_with(str, "dummy-audio:"))
return path;
return (it->second.empty() || path.is_absolute()) ? path : it->second/path;
return (paths[idx].empty() || path.is_absolute()) ? path : paths[idx]/path;
}
std::string Path::Encode(fs::path const& path) const {
// Find the shortest encoding of path made relative to each token
std::string shortest = path.string();
size_t length = boost::distance(path);
for (auto const& tok : tokens) {
if (tok.second.empty()) continue;
for (size_t i = 0; i < paths.size(); ++i) {
if (paths[i].empty()) continue;
const auto p = MakeRelative(path, tok.first);
const auto p = MakeRelative(path, tokens[i]);
const size_t d = boost::distance(p);
if (d < length) {
length = d;
shortest = (tok.first/p).string();
shortest = (tokens[i]/p).string();
}
}
return shortest;
}
void Path::SetToken(std::string const& token_name, fs::path const& token_value) {
const auto it = tokens.find(token_name);
if (it == tokens.end()) throw agi::InternalError("Bad token: " + token_name);
void Path::SetToken(const char *token_name, fs::path const& token_value) {
int idx = find_token(token_name, strlen(token_name));
if (idx == -1) throw agi::InternalError("Bad token: " + std::string(token_name));
if (token_value.empty())
it->second = token_value;
paths[idx] = token_value;
else if (!token_value.is_absolute())
it->second.clear();
paths[idx].clear();
else {
it->second = token_value;
it->second.make_preferred();
if (fs::FileExists(it->second))
it->second = it->second.parent_path();
}
paths.clear();
for (auto const& tok : tokens) {
if (!tok.second.empty())
paths[tok.second] = tok.first;
paths[idx] = token_value;
paths[idx].make_preferred();
if (fs::FileExists(paths[idx]))
paths[idx] = paths[idx].parent_path();
}
}

View file

@ -14,23 +14,16 @@
//
// Aegisub Project http://www.aegisub.org/
/// @file path.h
/// @brief Common paths.
/// @ingroup libaegisub
#include <libaegisub/fs_fwd.h>
#include <array>
#include <boost/filesystem/path.hpp>
#include <map>
namespace agi {
/// Class for handling everything path-related in Aegisub
class Path {
/// Token -> Path map
std::map<std::string, fs::path> tokens;
/// Path -> Token map
std::map<fs::path, std::string> paths;
std::array<fs::path, 8> paths;
/// Platform-specific code to fill in the default paths, called in the constructor
void FillPlatformSpecificPaths();
@ -74,7 +67,7 @@ public:
/// @param token_name A single word token beginning with '?'
/// @param token_value An absolute path to a directory or file
/// @throws InternalError if `token` is not a valid token name
void SetToken(std::string const& token_name, fs::path const& token_value);
void SetToken(const char *token_name, fs::path const& token_value);
};
}

View file

@ -40,7 +40,7 @@ agi::fs::path WinGetFolderPath(int folder) {
namespace agi {
void Path::FillPlatformSpecificPaths() {
tokens["?temp"] = boost::filesystem::temp_directory_path();
SetToken("?temp", boost::filesystem::temp_directory_path());
SetToken("?user", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub");
SetToken("?local", WinGetFolderPath(CSIDL_LOCAL_APPDATA)/"Aegisub");