test: add platform tests (#3514)

* test: add tr_getDefaultDownloadDir() tests

this also indirectly tests xdg and homedir

* test: add PlatformTest.defaultConfigDirEnv

* test: add PlatformTest.defaultConfigDirXdgConfig

test: add PlatformTest.defaultConfigDirXdgConfigHome

* test: add PlatformTest.webClientDirEnvClutch

test: add PlatformTest.webClientDirEnvTr

test: add PlatformTest.webClientDirXdgDataHome

* fixup! test: add PlatformTest.webClientDirEnvClutch

fix: win32 breakage
This commit is contained in:
Charles Kerr 2022-07-22 20:10:02 -05:00 committed by GitHub
parent 9bf2918ad0
commit 445aad56a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 326 additions and 204 deletions

View File

@ -202,7 +202,6 @@ static char const* getConfigDir(int argc, char const** argv)
int tr_main(int argc, char* argv[])
{
tr_variant settings;
char const* configDir;
tr_formatter_mem_init(MemK, MemKStr, MemMStr, MemGStr, MemTStr);
tr_formatter_size_init(DiskK, DiskKStr, DiskMStr, DiskGStr, DiskTStr);
@ -219,7 +218,7 @@ int tr_main(int argc, char* argv[])
/* load the defaults from config file + libtransmission defaults */
tr_variantInitDict(&settings, 0);
configDir = getConfigDir(argc, (char const**)argv);
char const* const configDir = getConfigDir(argc, (char const**)argv);
tr_sessionLoadSettings(&settings, configDir, MyConfigName);
/* the command line overrides defaults */

View File

@ -11,6 +11,8 @@
#include <glibmm/i18n.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include <libtransmission/variant.h>
#include "Prefs.h"
@ -47,7 +49,9 @@ static void tr_prefs_init_defaults(tr_variant* d)
if (dir.empty())
{
dir = tr_getDefaultDownloadDir();
auto* const tmp = tr_getDefaultDownloadDir();
dir = tmp;
tr_free(tmp);
}
tr_variantDictReserve(d, 31);

View File

@ -94,9 +94,11 @@ int main(int argc, char** argv)
tr_formatter_speed_init(speed_K, _(speed_K_str), _(speed_M_str), _(speed_G_str), _(speed_T_str));
/* set up the config dir */
if (config_dir.empty())
if (std::empty(config_dir))
{
config_dir = tr_getDefaultConfigDir(AppConfigDirName);
auto* const default_config_dir = tr_getDefaultConfigDir(AppConfigDirName);
config_dir = default_config_dir;
tr_free(default_config_dir);
}
gtr_pref_init(config_dir);

View File

@ -4,11 +4,11 @@
// License text can be found in the licenses/ folder.
#include <algorithm>
#include <cstring>
#include <array>
#include <iterator>
#include <list>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#ifdef __HAIKU__
@ -51,65 +51,59 @@ using namespace std::literals;
#ifdef _WIN32
static char* win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags)
static std::string win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags)
{
char* ret = nullptr;
PWSTR path;
if (SHGetKnownFolderPath(folder_id, flags | KF_FLAG_DONT_UNEXPAND, nullptr, &path) == S_OK)
{
ret = tr_win32_native_to_utf8(path, -1);
auto* utf8_cstr = tr_win32_native_to_utf8(path, -1);
auto ret = std::string{ utf8_cstr };
tr_free(utf8_cstr);
CoTaskMemFree(path);
return ret;
}
return ret;
return {};
}
static char* win32_get_known_folder(REFKNOWNFOLDERID folder_id)
static auto win32_get_known_folder(REFKNOWNFOLDERID folder_id)
{
return win32_get_known_folder_ex(folder_id, KF_FLAG_DONT_VERIFY);
}
#endif
static char const* getHomeDir()
static std::string getHomeDir()
{
static char const* home = nullptr;
if (home == nullptr)
if (auto* const dir = tr_env_get_string("HOME", nullptr); dir != nullptr)
{
home = tr_env_get_string("HOME", nullptr);
auto ret = std::string{ dir };
tr_free(dir);
return ret;
}
#ifdef _WIN32
if (home == nullptr)
if (auto dir = win32_get_known_folder(FOLDERID_Profile); !std::empty(dir))
{
home = win32_get_known_folder(FOLDERID_Profile);
return dir;
}
#else
if (home == nullptr)
struct passwd pwent;
struct passwd* pw = nullptr;
char buf[4096];
getpwuid_r(getuid(), &pwent, buf, sizeof buf, &pw);
if (pw != nullptr)
{
struct passwd pwent;
struct passwd* pw = nullptr;
char buf[4096];
getpwuid_r(getuid(), &pwent, buf, sizeof buf, &pw);
if (pw != nullptr)
{
home = tr_strdup(pw->pw_dir);
}
return pw->pw_dir;
}
#endif
if (home == nullptr)
{
home = tr_strdup("");
}
return home;
return {};
}
static std::string xdgConfigHome()
@ -151,46 +145,38 @@ char const* tr_getTorrentDir(tr_session const* session)
return session->torrent_dir.c_str();
}
char const* tr_getDefaultConfigDir(char const* appname)
char* tr_getDefaultConfigDir(char const* appname)
{
static char const* s = nullptr;
if (auto* dir = tr_env_get_string("TRANSMISSION_HOME", nullptr); dir != nullptr)
{
return dir;
}
if (tr_str_is_empty(appname))
{
appname = "Transmission";
}
if (s == nullptr)
{
s = tr_env_get_string("TRANSMISSION_HOME", nullptr);
if (s == nullptr)
{
#ifdef __APPLE__
s = tr_strvDup(fmt::format("{:s}/Library/Application Support/{:s}"sv, getHomeDir(), appname));
return tr_strvDup(fmt::format("{:s}/Library/Application Support/{:s}"sv, getHomeDir(), appname));
#elif defined(_WIN32)
char* appdata = win32_get_known_folder(FOLDERID_LocalAppData);
s = tr_strvDup(fmt::format("{:s}/{:s}"sv, appdata, appname));
tr_free(appdata);
auto const appdata = win32_get_known_folder(FOLDERID_LocalAppData);
return tr_strvDup(fmt::format("{:s}/{:s}"sv, appdata, appname));
#elif defined(__HAIKU__)
char buf[PATH_MAX];
find_directory(B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof(buf));
s = tr_strvDup(fmt::format("{:s}/{:s}"sv, buf, appname);
char buf[PATH_MAX];
find_directory(B_USER_SETTINGS_DIRECTORY, -1, true, buf, sizeof(buf));
return tr_strvDup(fmt::format("{:s}/{:s}"sv, buf, appname);
#else
s = tr_strvDup(fmt::format("{:s}/{:s}"sv, xdgConfigHome(), appname));
return tr_strvDup(fmt::format("{:s}/{:s}"sv, xdgConfigHome(), appname));
#endif
}
}
return s;
}
static std::string getXdgEntryFromUserDirs(std::string_view key)
@ -221,41 +207,31 @@ static std::string getXdgEntryFromUserDirs(std::string_view key)
auto constexpr Home = "$HOME"sv;
if (auto const it = std::search(std::begin(val), std::end(val), std::begin(Home), std::end(Home)); it != std::end(val))
{
val.replace(it, it + std::size(Home), std::string_view{ getHomeDir() });
val.replace(it, it + std::size(Home), getHomeDir());
}
return val;
}
char const* tr_getDefaultDownloadDir()
char* tr_getDefaultDownloadDir()
{
static char const* user_dir = nullptr;
if (user_dir == nullptr)
if (auto const dir = getXdgEntryFromUserDirs("XDG_DOWNLOAD_DIR"sv); !std::empty(dir))
{
if (auto const xdg_user_dir = getXdgEntryFromUserDirs("XDG_DOWNLOAD_DIR"sv); !std::empty(xdg_user_dir))
{
user_dir = tr_strvDup(xdg_user_dir);
}
#ifdef _WIN32
if (user_dir == nullptr)
{
user_dir = win32_get_known_folder(FOLDERID_Downloads);
}
#endif
if (user_dir == nullptr)
{
#ifdef __HAIKU__
user_dir = tr_strvDup(fmt::format("{:s}/Desktop"sv, getHomeDir()));
#else
user_dir = tr_strvDup(fmt::format("{:s}/Downloads"sv, getHomeDir()));
#endif
}
return tr_strvDup(dir);
}
return user_dir;
#ifdef _WIN32
if (auto dir = win32_get_known_folder(FOLDERID_Downloads); !std::empty(dir))
{
return tr_strvDup(dir);
}
#endif
#ifdef __HAIKU__
return tr_strvDup(fmt::format("{:s}/Desktop"sv, getHomeDir()));
#endif
return tr_strvDup(fmt::format("{:s}/Downloads"sv, getHomeDir()));
}
/***
@ -270,144 +246,127 @@ static bool isWebClientDir(std::string_view path)
return found;
}
char const* tr_getWebClientDir([[maybe_unused]] tr_session const* session)
std::string tr_getWebClientDir([[maybe_unused]] tr_session const* session)
{
static char const* s = nullptr;
if (s == nullptr)
if (auto* const dir = tr_env_get_string("CLUTCH_HOME", nullptr); dir != nullptr)
{
s = tr_env_get_string("CLUTCH_HOME", nullptr);
auto ret = std::string{ dir };
tr_free(dir);
return ret;
}
if (s == nullptr)
if (auto* const dir = tr_env_get_string("TRANSMISSION_WEB_HOME", nullptr); dir != nullptr)
{
s = tr_env_get_string("TRANSMISSION_WEB_HOME", nullptr);
auto ret = std::string{ dir };
tr_free(dir);
return ret;
}
#ifdef BUILD_MAC_CLIENT
// look in the Application Support folder
if (s == nullptr)
if (auto path = tr_pathbuf{ session->config_dir, "/public_html"sv }; isWebClientDir(path))
{
if (auto path = tr_pathbuf{ session->config_dir, "/public_html"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
}
return std::string{ path };
}
// look in the resource bundle
if (s == nullptr)
auto app_url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
auto app_ref = CFURLCopyFileSystemPath(app_url, kCFURLPOSIXPathStyle);
auto const buflen = CFStringGetMaximumSizeOfFileSystemRepresentation(app_ref);
auto buf = std::vector<char>(buflen, '\0');
bool const success = CFStringGetFileSystemRepresentation(app_ref, std::data(buf), std::size(buf));
TR_ASSERT(success);
CFRelease(app_url);
CFRelease(app_ref);
if (auto const path = tr_pathbuf{ std::string_view{ std::data(buf) }, "/Contents/Resources/public_html"sv };
isWebClientDir(path))
{
auto app_url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
auto app_ref = CFURLCopyFileSystemPath(app_url, kCFURLPOSIXPathStyle);
auto const buflen = CFStringGetMaximumSizeOfFileSystemRepresentation(app_ref);
auto buf = std::vector<char>(buflen, '\0');
bool const success = CFStringGetFileSystemRepresentation(app_ref, std::data(buf), std::size(buf));
TR_ASSERT(success);
CFRelease(app_url);
CFRelease(app_ref);
if (auto const path = tr_pathbuf{ std::string_view{ std::data(buf) }, "/Contents/Resources/public_html"sv };
isWebClientDir(path))
{
s = tr_strvDup(path);
}
return std::string{ path };
}
#elif defined(_WIN32)
if (s == nullptr)
/* Generally, Web interface should be stored in a Web subdir of
* calling executable dir. */
static auto constexpr KnownFolderIds = std::array<KNOWNFOLDERID const* const, 3>{
&FOLDERID_LocalAppData,
&FOLDERID_RoamingAppData,
&FOLDERID_ProgramData,
};
for (auto const* const folder_id : KnownFolderIds)
{
/* Generally, Web interface should be stored in a Web subdir of
* calling executable dir. */
auto const dir = win32_get_known_folder(*folder_id);
static KNOWNFOLDERID const* const known_folder_ids[] = {
&FOLDERID_LocalAppData,
&FOLDERID_RoamingAppData,
&FOLDERID_ProgramData,
};
for (size_t i = 0; s == nullptr && i < TR_N_ELEMENTS(known_folder_ids); ++i)
if (auto const path = tr_pathbuf{ dir, "/Transmission/Web"sv }; isWebClientDir(path))
{
char* dir = win32_get_known_folder(*known_folder_ids[i]);
if (auto const path = tr_pathbuf{ std::string_view{ dir }, "/Transmission/Web"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
}
tr_free(dir);
return std::string{ path };
}
}
if (s == nullptr) /* check calling module place */
/* check calling module place */
wchar_t wide_module_path[MAX_PATH];
GetModuleFileNameW(nullptr, wide_module_path, TR_N_ELEMENTS(wide_module_path));
char* module_path = tr_win32_native_to_utf8(wide_module_path, -1);
if (auto const dir = tr_sys_path_dirname(module_path); !std::empty(dir))
{
wchar_t wide_module_path[MAX_PATH];
GetModuleFileNameW(nullptr, wide_module_path, TR_N_ELEMENTS(wide_module_path));
char* module_path = tr_win32_native_to_utf8(wide_module_path, -1);
if (auto const dir = tr_sys_path_dirname(module_path); !std::empty(dir))
if (auto const path = tr_pathbuf{ dir, "/Web"sv }; isWebClientDir(path))
{
if (auto const path = tr_pathbuf{ dir, "/Web"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
}
tr_free(module_path);
return std::string{ path };
}
tr_free(module_path);
}
tr_free(module_path);
#else // everyone else, follow the XDG spec
if (s == nullptr)
auto candidates = std::list<std::string>{};
/* XDG_DATA_HOME should be the first in the list of candidates */
char* tmp = tr_env_get_string("XDG_DATA_HOME", nullptr);
if (!tr_str_is_empty(tmp))
{
auto candidates = std::list<std::string>{};
candidates.emplace_back(tmp);
}
else
{
candidates.emplace_back(fmt::format("{:s}/.local/share"sv, getHomeDir()));
}
tr_free(tmp);
/* XDG_DATA_HOME should be the first in the list of candidates */
char* tmp = tr_env_get_string("XDG_DATA_HOME", nullptr);
if (!tr_str_is_empty(tmp))
{
candidates.emplace_back(tmp);
}
else
{
candidates.emplace_back(fmt::format("{:s}/.local/share"sv, getHomeDir()));
}
tr_free(tmp);
/* XDG_DATA_DIRS are the backup directories */
{
char const* const pkg = PACKAGE_DATA_DIR;
auto* xdg = tr_env_get_string("XDG_DATA_DIRS", "");
auto const buf = fmt::format(FMT_STRING("{:s}:{:s}:/usr/local/share:/usr/share"), pkg, xdg);
tr_free(xdg);
/* XDG_DATA_DIRS are the backup directories */
auto sv = std::string_view{ buf };
auto token = std::string_view{};
while (tr_strvSep(&sv, &token, ':'))
{
char const* const pkg = PACKAGE_DATA_DIR;
auto* xdg = tr_env_get_string("XDG_DATA_DIRS", "");
auto const buf = fmt::format(FMT_STRING("{:s}:{:s}:/usr/local/share:/usr/share"), pkg, xdg);
tr_free(xdg);
auto sv = std::string_view{ buf };
auto token = std::string_view{};
while (tr_strvSep(&sv, &token, ':'))
token = tr_strvStrip(token);
if (!std::empty(token))
{
token = tr_strvStrip(token);
if (!std::empty(token))
{
candidates.emplace_back(token);
}
candidates.emplace_back(token);
}
}
}
/* walk through the candidates & look for a match */
for (auto const& dir : candidates)
/* walk through the candidates & look for a match */
for (auto const& dir : candidates)
{
if (auto const path = tr_pathbuf{ dir, "/transmission/public_html"sv }; isWebClientDir(path))
{
if (auto const path = tr_pathbuf{ dir, "/transmission/public_html"sv }; isWebClientDir(path))
{
s = tr_strvDup(path);
break;
}
return std::string{ path };
}
}
#endif
return s;
return {};
}
std::string tr_getSessionIdDir()
@ -418,9 +377,8 @@ std::string tr_getSessionIdDir()
#else
char* program_data_dir = win32_get_known_folder_ex(FOLDERID_ProgramData, KF_FLAG_CREATE);
auto const program_data_dir = win32_get_known_folder_ex(FOLDERID_ProgramData, KF_FLAG_CREATE);
auto result = fmt::format("{:s}/Transmission"sv, program_data_dir);
tr_free(program_data_dir);
tr_sys_dir_create(result, 0, 0);
return result;

View File

@ -31,7 +31,7 @@ void tr_setConfigDir(tr_session* session, std::string_view config_dir);
char const* tr_getTorrentDir(tr_session const*);
/** @brief return the directory where the Web Client's web ui files are kept */
char const* tr_getWebClientDir(tr_session const*);
std::string tr_getWebClientDir(tr_session const*);
/** @brief return the directory where session id lock files are stored */
std::string tr_getSessionIdDir();

View File

@ -256,9 +256,7 @@ static void serve_file(struct evhttp_request* req, tr_rpc_server* server, std::s
static void handle_web_client(struct evhttp_request* req, tr_rpc_server* server)
{
char const* webClientDir = tr_getWebClientDir(server->session);
if (tr_str_is_empty(webClientDir))
if (std::empty(server->web_client_dir_))
{
send_simple_response(
req,
@ -287,7 +285,7 @@ static void handle_web_client(struct evhttp_request* req, tr_rpc_server* server)
}
else
{
auto const filename = tr_pathbuf{ webClientDir, "/"sv, tr_str_is_empty(subpath) ? "index.html" : subpath };
auto const filename = tr_pathbuf{ server->web_client_dir_, '/', tr_str_is_empty(subpath) ? "index.html" : subpath };
serve_file(req, server, filename.sv());
}
@ -940,6 +938,7 @@ static void missing_settings_key(tr_quark const q)
tr_rpc_server::tr_rpc_server(tr_session* session_in, tr_variant* settings)
: compressor{ libdeflate_alloc_compressor(DeflateLevel), libdeflate_free_compressor }
, web_client_dir_{ tr_getWebClientDir(session_in) }
, bindAddress(std::make_unique<struct tr_rpc_address>())
, session{ session_in }
{
@ -1145,10 +1144,9 @@ tr_rpc_server::tr_rpc_server(tr_session* session_in, tr_variant* settings)
}
}
char const* webClientDir = tr_getWebClientDir(this->session);
if (!tr_str_is_empty(webClientDir))
if (!std::empty(web_client_dir_))
{
tr_logAddInfo(fmt::format(_("Serving RPC and Web requests from '{path}'"), fmt::arg("path", webClientDir)));
tr_logAddInfo(fmt::format(_("Serving RPC and Web requests from '{path}'"), fmt::arg("path", web_client_dir_)));
}
}

View File

@ -122,6 +122,7 @@ public:
std::vector<std::string> hostWhitelist;
std::vector<std::string> whitelist_;
std::string const web_client_dir_;
std::string salted_password_;
std::string username_;
std::string whitelist_str_;

View File

@ -319,6 +319,7 @@ tr_address const* tr_sessionGetPublicAddress(tr_session const* session, int tr_a
void tr_sessionGetDefaultSettings(tr_variant* d)
{
auto* const download_dir = tr_getDefaultDownloadDir();
TR_ASSERT(tr_variantIsDict(d));
tr_variantDictReserve(d, 71);
@ -328,14 +329,14 @@ void tr_sessionGetDefaultSettings(tr_variant* d)
tr_variantDictAddBool(d, TR_KEY_dht_enabled, true);
tr_variantDictAddBool(d, TR_KEY_utp_enabled, true);
tr_variantDictAddBool(d, TR_KEY_lpd_enabled, false);
tr_variantDictAddStr(d, TR_KEY_download_dir, tr_getDefaultDownloadDir());
tr_variantDictAddStr(d, TR_KEY_download_dir, download_dir);
tr_variantDictAddStr(d, TR_KEY_default_trackers, "");
tr_variantDictAddInt(d, TR_KEY_speed_limit_down, 100);
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
tr_variantDictAddInt(d, TR_KEY_encryption, TR_DEFAULT_ENCRYPTION);
tr_variantDictAddInt(d, TR_KEY_idle_seeding_limit, 30);
tr_variantDictAddBool(d, TR_KEY_idle_seeding_limit_enabled, false);
tr_variantDictAddStr(d, TR_KEY_incomplete_dir, tr_getDefaultDownloadDir());
tr_variantDictAddStr(d, TR_KEY_incomplete_dir, download_dir);
tr_variantDictAddBool(d, TR_KEY_incomplete_dir_enabled, false);
tr_variantDictAddInt(d, TR_KEY_message_level, TR_LOG_INFO);
tr_variantDictAddInt(d, TR_KEY_download_queue_size, 5);
@ -397,6 +398,8 @@ void tr_sessionGetDefaultSettings(tr_variant* d)
tr_variantDictAddBool(d, TR_KEY_anti_brute_force_enabled, true);
tr_variantDictAddStrView(d, TR_KEY_announce_ip, "");
tr_variantDictAddBool(d, TR_KEY_announce_ip_enabled, false);
tr_free(download_dir);
}
void tr_sessionGetSettings(tr_session const* s, tr_variant* d)
@ -479,6 +482,19 @@ void tr_sessionGetSettings(tr_session const* s, tr_variant* d)
}
}
static void getSettingsFilename(tr_pathbuf& setme, char const* config_dir, char const* appname)
{
if (!tr_str_is_empty(config_dir))
{
setme.assign(std::string_view{ config_dir }, "/settings.json"sv);
return;
}
auto* const default_config_dir = tr_getDefaultConfigDir(appname);
setme.assign(std::string_view{ default_config_dir }, "/settings.json"sv);
tr_free(default_config_dir);
}
bool tr_sessionLoadSettings(tr_variant* dict, char const* config_dir, char const* appName)
{
TR_ASSERT(tr_variantIsDict(dict));
@ -491,17 +507,16 @@ bool tr_sessionLoadSettings(tr_variant* dict, char const* config_dir, char const
tr_variantMergeDicts(dict, &oldDict);
tr_variantFree(&oldDict);
/* if caller didn't specify a config dir, use the default */
if (tr_str_is_empty(config_dir))
{
config_dir = tr_getDefaultConfigDir(appName);
}
/* file settings override the defaults */
auto fileSettings = tr_variant{};
auto const filename = tr_pathbuf{ config_dir, "/settings.json"sv };
auto success = bool{};
if (tr_error* error = nullptr; tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename, &error))
auto filename = tr_pathbuf{};
getSettingsFilename(filename, config_dir, appName);
if (!tr_sys_path_exists(filename))
{
success = true;
}
else if (tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename))
{
tr_variantMergeDicts(dict, &fileSettings);
tr_variantFree(&fileSettings);
@ -509,8 +524,7 @@ bool tr_sessionLoadSettings(tr_variant* dict, char const* config_dir, char const
}
else
{
success = TR_ERROR_IS_ENOENT(error->code);
tr_error_free(error);
success = false;
}
/* cleanup */

View File

@ -99,6 +99,8 @@ enum tr_encryption_mode
/**
* @brief returns Transmission's default configuration file directory.
*
* Use tr_free() to free the string when done.
*
* The default configuration directory is determined this way:
* -# If the TRANSMISSION_HOME environment variable is set, its value is used.
* -# On Darwin, "${HOME}/Library/Application Support/${appname}" is used.
@ -106,17 +108,19 @@ enum tr_encryption_mode
* -# If XDG_CONFIG_HOME is set, "${XDG_CONFIG_HOME}/${appname}" is used.
* -# ${HOME}/.config/${appname}" is used as a last resort.
*/
char const* tr_getDefaultConfigDir(char const* appname);
char* tr_getDefaultConfigDir(char const* appname);
/**
* @brief returns Transmisson's default download directory.
*
* Use tr_free() to free the string when done.
*
* The default download directory is determined this way:
* -# If the HOME environment variable is set, "${HOME}/Downloads" is used.
* -# On Windows, "${CSIDL_MYDOCUMENTS}/Downloads" is used.
* -# Otherwise, getpwuid(getuid())->pw_dir + "/Downloads" is used.
*/
char const* tr_getDefaultDownloadDir(void);
char* tr_getDefaultDownloadDir();
#define TR_DEFAULT_BIND_ADDRESS_IPV4 "0.0.0.0"
#define TR_DEFAULT_BIND_ADDRESS_IPV6 "::"

View File

@ -508,11 +508,11 @@ static void removeKeRangerRansomware()
tr_formatter_mem_init(1000, kbString.UTF8String, mbString.UTF8String, gbString.UTF8String, tbString.UTF8String);
char const* configDir = tr_getDefaultConfigDir("Transmission");
_fLib = tr_sessionInit(configDir, YES, &settings);
char* const default_config_dir = tr_getDefaultConfigDir("Transmission");
_fLib = tr_sessionInit(default_config_dir, YES, &settings);
tr_variantFree(&settings);
_fConfigDirectory = @(configDir);
_fConfigDirectory = @(default_config_dir);
tr_free(default_config_dir);
NSApp.delegate = self;

View File

@ -218,7 +218,9 @@ Application::Application(int& argc, char** argv)
// set the fallback config dir
if (config_dir.isNull())
{
config_dir = QString::fromUtf8(tr_getDefaultConfigDir("transmission"));
auto* const default_config_dir = tr_getDefaultConfigDir("transmission");
config_dir = QString::fromUtf8(default_config_dir);
tr_free(default_config_dir);
}
// ensure our config directory exists

View File

@ -430,7 +430,7 @@ void Prefs::initDefaults(tr_variant* d) const
auto constexpr StatsMode = std::string_view{ "total-ratio" };
auto constexpr WindowLayout = std::string_view{ "menu,toolbar,filter,list,statusbar" };
auto const download_dir = std::string_view{ tr_getDefaultDownloadDir() };
auto* const download_dir = tr_getDefaultDownloadDir();
tr_variantDictReserve(d, 38);
dictAdd(d, TR_KEY_blocklist_updates_enabled, true);
@ -460,7 +460,7 @@ void Prefs::initDefaults(tr_variant* d) const
dictAdd(d, TR_KEY_main_window_x, 50);
dictAdd(d, TR_KEY_main_window_y, 50);
dictAdd(d, TR_KEY_remote_session_port, TR_DEFAULT_RPC_PORT);
dictAdd(d, TR_KEY_download_dir, download_dir);
dictAdd(d, TR_KEY_download_dir, std::string_view{ download_dir });
dictAdd(d, TR_KEY_filter_mode, FilterMode);
dictAdd(d, TR_KEY_main_window_layout_order, WindowLayout);
dictAdd(d, TR_KEY_open_dialog_dir, QDir::home().absolutePath());
@ -469,8 +469,10 @@ void Prefs::initDefaults(tr_variant* d) const
dictAdd(d, TR_KEY_remote_session_username, SessionUsername);
dictAdd(d, TR_KEY_sort_mode, SortMode);
dictAdd(d, TR_KEY_statusbar_stats, StatsMode);
dictAdd(d, TR_KEY_watch_dir, download_dir);
dictAdd(d, TR_KEY_watch_dir, std::string_view{ download_dir });
dictAdd(d, TR_KEY_read_clipboard, false);
tr_free(download_dir);
}
/***

View File

@ -24,6 +24,7 @@ add_executable(libtransmission-test
peer-mgr-active-requests-test.cc
peer-mgr-wishlist-test.cc
peer-msgs-test.cc
platform-test.cc
quark-test.cc
remove-test.cc
rename-test.cc

View File

@ -0,0 +1,15 @@
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run.
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="$HOME/Desktop"
XDG_DOWNLOAD_DIR="$HOME/UserDirsDownloads"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_PUBLICSHARE_DIR="$HOME/Public"
XDG_DOCUMENTS_DIR="$HOME/Documents"
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_VIDEOS_DIR="$HOME/Videos"

View File

@ -0,0 +1,122 @@
// This file Copyright (C) 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <cstdlib>
#include <string_view>
#include "transmission.h"
#include "file.h"
#include "tr-strbuf.h"
#include "test-fixtures.h"
using namespace std::literals;
using PlatformTest = ::libtransmission::test::SessionTest;
using ::libtransmission::test::makeString;
#ifdef _WIN32
#include <windows.h>
#define setenv(key, value, unused) SetEnvironmentVariableA(key, value)
#define unsetenv(key) SetEnvironmentVariableA(key, nullptr)
#endif
TEST_F(PlatformTest, defaultDownloadDirXdg)
{
setenv("HOME", sandboxDir().c_str(), 1);
setenv("XDG_CONFIG_HOME", LIBTRANSMISSION_TEST_ASSETS_DIR, 1);
auto actual = makeString(tr_getDefaultDownloadDir());
auto expected = fmt::format("{:s}/UserDirsDownloads"sv, sandboxDir());
EXPECT_EQ(expected, actual);
unsetenv("XDG_CONFIG_HOME");
unsetenv("HOME");
}
#if !defined(_WIN32) && !defined(__HAIKU__)
TEST_F(PlatformTest, defaultDownloadDir)
{
setenv("HOME", sandboxDir().c_str(), 1);
auto expected = fmt::format("{:s}/Downloads"sv, sandboxDir());
auto actual = makeString(tr_getDefaultDownloadDir());
EXPECT_EQ(expected, actual);
unsetenv("HOME");
}
#endif
TEST_F(PlatformTest, defaultConfigDirEnv)
{
setenv("TRANSMISSION_HOME", sandboxDir().c_str(), 1);
auto actual = makeString(tr_getDefaultConfigDir("appname"));
auto expected = sandboxDir();
EXPECT_EQ(expected, actual);
unsetenv("TRANSMISSION_HOME");
}
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__HAIKU__)
TEST_F(PlatformTest, defaultConfigDirXdgConfig)
{
setenv("XDG_CONFIG_HOME", sandboxDir().c_str(), 1);
auto expected = fmt::format("{:s}/appname", sandboxDir());
auto actual = makeString(tr_getDefaultConfigDir("appname"));
EXPECT_EQ(expected, actual);
unsetenv("XDG_CONFIG_HOME");
}
TEST_F(PlatformTest, defaultConfigDirXdgConfigHome)
{
auto const home = tr_pathbuf{ sandboxDir(), "/home/user" };
setenv("HOME", home, 1);
auto expected = fmt::format("{:s}/.config/appname", home.sv());
auto actual = makeString(tr_getDefaultConfigDir("appname"));
EXPECT_EQ(expected, actual);
unsetenv("HOME");
}
#endif
TEST_F(PlatformTest, webClientDirEnvClutch)
{
setenv("CLUTCH_HOME", sandboxDir().c_str(), 1);
EXPECT_EQ(sandboxDir(), tr_getWebClientDir(session_));
unsetenv("CLUTCH_HOME");
}
TEST_F(PlatformTest, webClientDirEnvTr)
{
setenv("TRANSMISSION_WEB_HOME", sandboxDir().c_str(), 1);
EXPECT_EQ(sandboxDir(), tr_getWebClientDir(session_));
unsetenv("TRANSMISSION_WEB_HOME");
}
#if !defined(BUILD_MAC_CLIENT) && !defined(_WIN32)
TEST_F(PlatformTest, webClientDirXdgDataHome)
{
setenv("XDG_DATA_HOME", sandboxDir().c_str(), 1);
auto const expected = tr_pathbuf{ sandboxDir(), "/transmission/public_html"sv };
auto const index_html = tr_pathbuf{ expected, "/index.html"sv };
EXPECT_TRUE(tr_sys_dir_create(expected, TR_SYS_DIR_CREATE_PARENTS, 0777));
EXPECT_TRUE(tr_saveFile(index_html, "<html></html>"sv));
EXPECT_EQ(expected, tr_getWebClientDir(session_));
unsetenv("XDG_DATA_HOME");
}
#endif