Use XDG Base Directories compliant dirs on Unix for new installations

Signed-off-by: Stanisław Wysocki <garethel@protonmail.com>
This commit is contained in:
Stanisław Wysocki 2022-04-04 17:44:32 +02:00 committed by Thomas Lauf
parent 598ddb24c8
commit 04822aa195
9 changed files with 230 additions and 86 deletions

View file

@ -34,6 +34,7 @@ set (timew_SRCS AtomicFile.cpp AtomicFile.h
dom.cpp
init.cpp
helper.cpp
paths.cpp paths.h
log.cpp
util.cpp
validate.cpp)

View file

@ -120,7 +120,7 @@ private:
void initializeTagDatabase ();
private:
std::string _location {"~/.timewarrior/data"};
std::string _location {};
std::vector <Datafile> _files {};
TagInfoDatabase _tagInfoDatabase {};
Journal* _journal {};

View file

@ -53,7 +53,7 @@ public:
private:
void recordUndoAction (const std::string &, const std::string &, const std::string &);
std::string _location {"~/.timewarrior/data/undo.data"};
std::string _location {};
std::shared_ptr <Transaction> _currentTransaction = nullptr;
int _size {0};
};

View file

@ -29,6 +29,7 @@
#include <timew.h>
#include <iostream>
#include <shared.h>
#include <paths.h>
#ifdef HAVE_COMMIT
#include <commit.h>
@ -163,15 +164,15 @@ int CmdDiagnostics (
<< (env ? env : "-")
<< '\n';
File cfg (rules.get ("temp.db") + "/timewarrior.cfg");
File cfg (paths::configFile ());
out << " Cfg: " << describeFile (cfg) << '\n';
Directory db (rules.get ("temp.db"));
Directory db (paths::dbDir ());
out << " Database: " << describeFile (db) << '\n';
for (auto& file : database.files ())
{
File df (rules.get ("temp.db") + "/data");
File df (paths::dbDataDir ());
df += file;
out << " " << describeFile (df) << '\n';
}
@ -205,8 +206,7 @@ int CmdDiagnostics (
out << '\n';
// Display extensions.
Directory extDir (rules.get ("temp.db"));
extDir += "extensions";
Directory extDir (paths::extensionsDir ());
out << "Extensions\n"
<< " Location: " << describeFile (extDir) << '\n';

View file

@ -27,12 +27,11 @@
#include <commands.h>
#include <Table.h>
#include <iostream>
#include <paths.h>
////////////////////////////////////////////////////////////////////////////////
// Enumerate all extensions.
int CmdExtensions (
Rules& rules,
const Extensions& extensions)
int CmdExtensions (const Extensions& extensions)
{
Table t;
t.width (1024);
@ -59,8 +58,7 @@ int CmdExtensions (
t.set (row, 1, perms);
}
Directory extDir (rules.get ("temp.db"));
extDir += "extensions";
Directory extDir (paths::extensionsDir ());
std::cout << '\n'
<< "Extensions located in:\n"

View file

@ -41,7 +41,7 @@ int CmdDefault ( Rules&, Database&
int CmdDelete (const CLI&, Rules&, Database&, Journal& );
int CmdDiagnostics ( Rules&, Database&, const Extensions&);
int CmdExport (const CLI&, Rules&, Database& );
int CmdExtensions ( Rules&, const Extensions&);
int CmdExtensions ( const Extensions&);
int CmdFill (const CLI&, Rules&, Database&, Journal& );
int CmdGaps (const CLI&, Rules&, Database& );
int CmdGet (const CLI&, Rules&, Database& );

View file

@ -32,6 +32,7 @@
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <paths.h>
////////////////////////////////////////////////////////////////////////////////
bool lightweightVersionCheck (int argc, const char** argv)
@ -151,74 +152,7 @@ void initializeDataJournalAndRules (
}
enableDebugMode (rules.getBoolean ("debug"));
// The $TIMEWARRIORDB environment variable overrides the default value of ~/.timewarrior
Directory dbLocation;
char* override = getenv ("TIMEWARRIORDB");
dbLocation = Directory (override ? override : "~/.timewarrior");
// If dbLocation exists, but is not readable/writable/executable, error.
if (dbLocation.exists () &&
(! dbLocation.readable () ||
! dbLocation.writable () ||
! dbLocation.executable ()))
{
throw format ("Database is not readable at '{1}'", dbLocation._data);
}
// If dbLocation does not exist, ask whether it should be created.
bool shinyNewDatabase = false;
if (! dbLocation.exists ())
{
if ((cli.getHint ("yes", false) || confirm ("Create new database in " + dbLocation._data + "?")))
{
dbLocation.create (0700);
shinyNewDatabase = true;
}
else
{
throw std::string("Initial setup aborted by user");
}
}
// Create extensions subdirectory if necessary.
Directory extensions (dbLocation);
extensions += "extensions";
if (! extensions.exists ())
{
extensions.create (0700);
}
// Create data subdirectory if necessary.
Directory data (dbLocation);
data += "data";
if (! data.exists ())
{
data.create (0700);
}
// Load the configuration data.
Path configFile (dbLocation);
configFile += "timewarrior.cfg";
if (! configFile.exists ())
{
File (configFile).create (0600);
}
rules.load (configFile._data);
// This value is not written out to disk, as there would be no point.
// Having located the config file, the 'db' location is already known.
// This is just for subsequent internal use.
rules.set ("temp.db", dbLocation._data);
// Perhaps some subsequent code would like to know this is a new db and possibly a first run.
if (shinyNewDatabase)
rules.set ("temp.shiny", 1);
paths::initializeDirs (cli, rules);
if (rules.has ("debug.indicator"))
setDebugIndicator (rules.get ("debug.indicator"));
@ -236,9 +170,10 @@ void initializeDataJournalAndRules (
}
}
journal.initialize (data._data + "/undo.data", rules.getInteger ("journal.size"));
std::string dbDataDir = paths::dbDataDir ();
journal.initialize (dbDataDir + "/undo.data", rules.getInteger ("journal.size"));
// Initialize the database (no data read), but files are enumerated.
database.initialize (data._data, journal);
database.initialize (dbDataDir, journal);
}
////////////////////////////////////////////////////////////////////////////////
@ -247,8 +182,7 @@ void initializeExtensions (
const Rules& rules,
Extensions& extensions)
{
Directory extDir (rules.get ("temp.db"));
extDir += "extensions";
Directory extDir (paths::extensionsDir ());
extensions.initialize (extDir._data);
@ -289,7 +223,7 @@ int dispatchCommand (
else if (command == "delete") status = CmdDelete (cli, rules, database, journal );
else if (command == "diagnostics") status = CmdDiagnostics ( rules, database, extensions);
else if (command == "export") status = CmdExport (cli, rules, database );
else if (command == "extensions") status = CmdExtensions ( rules, extensions);
else if (command == "extensions") status = CmdExtensions ( extensions);
/*
else if (command == "fill") status = CmdFill (cli, rules, database, journal );
//*/

169
src/paths.cpp Normal file
View file

@ -0,0 +1,169 @@
#include <FS.h>
#include <format.h>
#include <paths.h>
#include <shared.h>
namespace paths
{
static const char *legacy_config_dir = "~/.timewarrior";
static const bool uses_legacy_config = Directory (legacy_config_dir).exists ();
const char *timewarriordb = getenv ("TIMEWARRIORDB");
#ifdef __unix__
std::string getPath (const char *xdg_path)
{
if (timewarriordb != nullptr)
{
return timewarriordb;
}
else if (uses_legacy_config)
{
return legacy_config_dir;
}
else
{
return std::string (xdg_path) + "/timewarrior";
}
}
const char *getenv_default (const char *env, const char *default_value)
{
const char *value = getenv (env);
if (value == nullptr)
{
return default_value;
}
return value;
}
static std::string conf_dir = getPath (getenv_default ("XDG_CONFIG_HOME", "~/.config"));
static std::string data_dir = getPath (getenv_default ("XDG_DATA_HOME", "~/.local/share"));
std::string configDir () { return conf_dir; }
std::string dbDir () { return data_dir; }
#else
std::string getPath ()
{
if (timewarriordb != nullptr)
{
return timewarriordb;
}
else
{
return legacy_config_dir;
}
}
static std::string path = getPath ();
std::string configDir ()
{
return path;
}
std::string dbDir ()
{
return path;
}
#endif
std::string configFile () { return configDir () + "/timewarrior.cfg"; }
std::string dbDataDir () { return dbDir () + "/data"; }
std::string extensionsDir () { return configDir () + "/extensions"; }
void initializeDirs (const CLI &cli, Rules &rules)
{
Directory configLocation = Directory (configDir ());
bool configDirExists = configLocation.exists ();
if (configDirExists &&
(!configLocation.readable () ||
!configLocation.writable () ||
!configLocation.executable ()))
{
throw format ("Config is not readable at '{1}'", configLocation._data);
}
Directory dbLocation = Directory (dbDir ());
bool dataLocationExists = dbLocation.exists ();
if (dataLocationExists &&
(!dbLocation.readable () ||
!dbLocation.writable () ||
!dbLocation.executable ()))
{
throw format ("Database is not readable at '{1}'", dbLocation._data);
}
std::string question = "";
if (!configDirExists)
{
question += "Create new config in " + configLocation._data + "?";
}
if (!dataLocationExists && configLocation._data != dbLocation._data)
{
if (question != "") {
question += "\n";
}
question += "Create new database in " + dbLocation._data + "?";
}
if (!configDirExists || !dataLocationExists)
{
if (cli.getHint ("yes", false) || confirm (question))
{
if (!configDirExists)
{
configLocation.create (0700);
}
if (!dataLocationExists)
{
dbLocation.create (0700);
}
}
else
{
throw std::string("Initial setup aborted by user");
}
}
// Create extensions subdirectory if necessary.
Directory extensionsLocation (extensionsDir ());
if (!extensionsLocation.exists ())
{
extensionsLocation.create (0700);
}
// Create data subdirectory if necessary.
Directory dbDataLocation (dbDataDir ());
if (!dbDataLocation.exists ())
{
dbDataLocation.create (0700);
}
Path configFileLocation (configFile ());
if (!configFileLocation.exists ())
{
File (configFileLocation).create (0600);
}
// Load the configuration data.
rules.load (configFileLocation);
// This value is not written out to disk, as there would be no point.
// Having located the config file, the 'db' location is already known.
// This is just for subsequent internal use.
rules.set ("temp.db", dbLocation);
rules.set ("temp.extensions", extensionsLocation);
rules.set ("temp.config", configFileLocation);
// Perhaps some subsequent code would like to know this is a new db and possibly a first run.
if (!dataLocationExists)
rules.set ("temp.shiny", 1);
}
} // namespace paths

42
src/paths.h Normal file
View file

@ -0,0 +1,42 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 - 2021, Thomas Lauf, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_PATH_RESOLVER
#define INCLUDED_PATH_RESOLVER
#include <string>
#include <Rules.h>
#include <CLI.h>
#include <Rules.h>
namespace paths {
void initializeDirs (const CLI&, Rules&);
std::string configDir ();
std::string configFile ();
std::string dbDir ();
std::string dbDataDir ();
std::string extensionsDir ();
}
#endif