mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-27 19:17:19 +02:00
[BREAKING] Disable the 'sync' command
This will require support from TaskChampion.
This commit is contained in:
parent
16c35a41d3
commit
d47c70579f
1 changed files with 3 additions and 333 deletions
|
@ -53,340 +53,10 @@ CmdSync::CmdSync ()
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int CmdSync::execute (std::string& output)
|
int CmdSync::execute (std::string&)
|
||||||
{
|
{
|
||||||
int status = 0;
|
throw std::string ("Sync support is disabled during transition to TaskChampion.");
|
||||||
#ifdef HAVE_LIBGNUTLS
|
return 0;
|
||||||
std::stringstream out;
|
|
||||||
|
|
||||||
Filter filter;
|
|
||||||
if (filter.hasFilter ())
|
|
||||||
throw std::string ("Command line filters are not supported by this command.");
|
|
||||||
|
|
||||||
// Loog for the 'init' keyword to indicate one-time pending.data upload.
|
|
||||||
bool first_time_init = false;
|
|
||||||
std::vector <std::string> words = Context::getContext ().cli2.getWords ();
|
|
||||||
for (auto& word : words)
|
|
||||||
{
|
|
||||||
if (closeEnough ("initialize", word, 4))
|
|
||||||
{
|
|
||||||
if (!Context::getContext ().config.getBoolean ("confirmation") ||
|
|
||||||
confirm ("Please confirm that you wish to upload all your tasks to the Taskserver"))
|
|
||||||
first_time_init = true;
|
|
||||||
else
|
|
||||||
throw std::string ("Taskwarrior will not proceed with first-time sync initialization.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no server is set up, quit.
|
|
||||||
std::string connection = Context::getContext ().config.get ("taskd.server");
|
|
||||||
if (connection == "" ||
|
|
||||||
connection.rfind (':') == std::string::npos)
|
|
||||||
throw std::string ("Taskserver is not configured.");
|
|
||||||
|
|
||||||
// Obtain credentials.
|
|
||||||
std::string credentials_string = Context::getContext ().config.get ("taskd.credentials");
|
|
||||||
if (credentials_string == "")
|
|
||||||
throw std::string ("Taskserver credentials malformed.");
|
|
||||||
|
|
||||||
auto credentials = split (credentials_string, '/');
|
|
||||||
if (credentials.size () != 3)
|
|
||||||
throw std::string ("Taskserver credentials malformed.");
|
|
||||||
|
|
||||||
// This was a Boolean value in 2.3.0, and is a tri-state since 2.4.0.
|
|
||||||
std::string trust_value = Context::getContext ().config.get ("taskd.trust");
|
|
||||||
if (trust_value != "strict" &&
|
|
||||||
trust_value != "ignore hostname" &&
|
|
||||||
trust_value != "allow all")
|
|
||||||
throw std::string ("The 'taskd.trust' settings may now only contain a value of 'strict', 'ignore hostname' or 'allow all'.");
|
|
||||||
|
|
||||||
enum TLSClient::trust_level trust = TLSClient::strict;
|
|
||||||
if (trust_value == "allow all")
|
|
||||||
trust = TLSClient::allow_all;
|
|
||||||
else if (trust_value == "ignore hostname")
|
|
||||||
trust = TLSClient::ignore_hostname;
|
|
||||||
|
|
||||||
// CA must exist, if provided.
|
|
||||||
File ca (Context::getContext ().config.get ("taskd.ca"));
|
|
||||||
if (ca._data != "" && ! ca.exists ())
|
|
||||||
throw std::string ("CA certificate not found.");
|
|
||||||
|
|
||||||
if (trust == TLSClient::allow_all && ca._data != "")
|
|
||||||
throw std::string ("You should either provide a CA certificate or override verification, but not both.");
|
|
||||||
|
|
||||||
File certificate (Context::getContext ().config.get ("taskd.certificate"));
|
|
||||||
if (! certificate.exists ())
|
|
||||||
throw std::string ("Taskserver certificate missing.");
|
|
||||||
|
|
||||||
File key (Context::getContext ().config.get ("taskd.key"));
|
|
||||||
if (! key.exists ())
|
|
||||||
throw std::string ("Taskserver key missing.");
|
|
||||||
|
|
||||||
// If this is a first-time initialization, send pending.data and
|
|
||||||
// completed.data, but not backlog.data.
|
|
||||||
std::string payload = "";
|
|
||||||
int upload_count = 0;
|
|
||||||
if (first_time_init)
|
|
||||||
{
|
|
||||||
// Delete backlog.data. Because if we're uploading everything, the list of
|
|
||||||
// deltas is meaningless.
|
|
||||||
Context::getContext ().tdb2.backlog._file.truncate ();
|
|
||||||
|
|
||||||
auto all_tasks = Context::getContext ().tdb2.all_tasks ();
|
|
||||||
for (auto& i : all_tasks)
|
|
||||||
{
|
|
||||||
payload += i.composeJSON () + '\n';
|
|
||||||
++upload_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::vector <std::string> lines = Context::getContext ().tdb2.backlog.get_lines ();
|
|
||||||
for (auto& i : lines)
|
|
||||||
{
|
|
||||||
if (i[0] == '{')
|
|
||||||
++upload_count;
|
|
||||||
|
|
||||||
payload += i + '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 'sync' + payload.
|
|
||||||
Msg request;
|
|
||||||
request.set ("client", PACKAGE_STRING);
|
|
||||||
request.set ("protocol", "v1");
|
|
||||||
request.set ("type", "sync");
|
|
||||||
request.set ("org", credentials[0]);
|
|
||||||
request.set ("user", credentials[1]);
|
|
||||||
request.set ("key", credentials[2]);
|
|
||||||
|
|
||||||
// Tell the server that this is a full upload, allowing it to perhaps compact
|
|
||||||
// its data.
|
|
||||||
if (first_time_init)
|
|
||||||
request.set ("subtype", "init");
|
|
||||||
|
|
||||||
request.setPayload (payload);
|
|
||||||
|
|
||||||
if (Context::getContext ().verbose ("sync"))
|
|
||||||
out << format ("Syncing with {1}", connection)
|
|
||||||
<< '\n';
|
|
||||||
|
|
||||||
// Ignore harmful signals.
|
|
||||||
signal (SIGHUP, SIG_IGN);
|
|
||||||
signal (SIGINT, SIG_IGN);
|
|
||||||
signal (SIGPIPE, SIG_IGN);
|
|
||||||
signal (SIGTERM, SIG_IGN);
|
|
||||||
signal (SIGUSR1, SIG_IGN);
|
|
||||||
signal (SIGUSR2, SIG_IGN);
|
|
||||||
|
|
||||||
Msg response;
|
|
||||||
if (send (connection, ca._data, certificate._data, key._data, trust, request, response))
|
|
||||||
{
|
|
||||||
std::string code = response.get ("code");
|
|
||||||
if (code == "200")
|
|
||||||
{
|
|
||||||
Color colorAdded;
|
|
||||||
Color colorChanged;
|
|
||||||
if (Context::getContext ().color ())
|
|
||||||
{
|
|
||||||
colorAdded = Color (Context::getContext ().config.get ("color.sync.added"));
|
|
||||||
colorChanged = Color (Context::getContext ().config.get ("color.sync.changed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
int download_count = 0;
|
|
||||||
payload = response.getPayload ();
|
|
||||||
auto lines = split (payload, '\n');
|
|
||||||
|
|
||||||
// Load all tasks, but only if necessary. There is always a sync key in
|
|
||||||
// the payload, so if there are two or more lines, then we have merging
|
|
||||||
// to perform, otherwise it's just a backlog.data update.
|
|
||||||
if (lines.size () > 1)
|
|
||||||
Context::getContext ().tdb2.all_tasks ();
|
|
||||||
|
|
||||||
std::string sync_key = "";
|
|
||||||
for (auto& line : lines)
|
|
||||||
{
|
|
||||||
if (line[0] == '{')
|
|
||||||
{
|
|
||||||
++download_count;
|
|
||||||
|
|
||||||
Task from_server (line);
|
|
||||||
std::string uuid = from_server.get ("uuid");
|
|
||||||
|
|
||||||
// Is it a new task from the server, or an update to an existing one?
|
|
||||||
Task dummy;
|
|
||||||
if (Context::getContext ().tdb2.get (uuid, dummy))
|
|
||||||
{
|
|
||||||
if (Context::getContext ().verbose ("sync"))
|
|
||||||
out << " "
|
|
||||||
<< colorChanged.colorize (
|
|
||||||
format ("modify {1} '{2}'",
|
|
||||||
uuid,
|
|
||||||
from_server.get ("description")))
|
|
||||||
<< '\n';
|
|
||||||
Context::getContext ().tdb2.modify (from_server, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Context::getContext ().verbose ("sync"))
|
|
||||||
out << " "
|
|
||||||
<< colorAdded.colorize (
|
|
||||||
format (" add {1} '{2}'",
|
|
||||||
uuid,
|
|
||||||
from_server.get ("description")))
|
|
||||||
<< '\n';
|
|
||||||
Context::getContext ().tdb2.add (from_server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (line != "")
|
|
||||||
{
|
|
||||||
sync_key = line;
|
|
||||||
Context::getContext ().debug ("Sync key " + sync_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise line is blank, so ignore it.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only update everything if there is a new sync_key. No sync_key means
|
|
||||||
// something horrible happened on the other end of the wire.
|
|
||||||
if (sync_key != "")
|
|
||||||
{
|
|
||||||
// Truncate backlog.data, save new sync_key.
|
|
||||||
Context::getContext ().tdb2.backlog._file.truncate ();
|
|
||||||
Context::getContext ().tdb2.backlog.clear_tasks ();
|
|
||||||
Context::getContext ().tdb2.backlog.clear_lines ();
|
|
||||||
Context::getContext ().tdb2.backlog.add_line (sync_key + '\n');
|
|
||||||
|
|
||||||
// Present a clear status message.
|
|
||||||
if (upload_count == 0 && download_count == 0)
|
|
||||||
// Note: should not happen - expect code 201 instead.
|
|
||||||
Context::getContext ().footnote ("Sync successful.");
|
|
||||||
else if (upload_count == 0 && download_count > 0)
|
|
||||||
Context::getContext ().footnote (format ("Sync successful. {1} changes downloaded.", download_count));
|
|
||||||
else if (upload_count > 0 && download_count == 0)
|
|
||||||
Context::getContext ().footnote (format ("Sync successful. {1} changes uploaded.", upload_count));
|
|
||||||
else if (upload_count > 0 && download_count > 0)
|
|
||||||
Context::getContext ().footnote (format ("Sync successful. {1} changes uploaded, {2} changes downloaded.", upload_count, download_count));
|
|
||||||
}
|
|
||||||
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
else if (code == "201")
|
|
||||||
{
|
|
||||||
Context::getContext ().footnote ("Sync successful. No changes.");
|
|
||||||
status = 0;
|
|
||||||
}
|
|
||||||
else if (code == "301")
|
|
||||||
{
|
|
||||||
std::string new_server = response.get ("info");
|
|
||||||
Context::getContext ().config.set ("taskd.server", new_server);
|
|
||||||
Context::getContext ().error ("The server account has been relocated. Please update your configuration using:");
|
|
||||||
Context::getContext ().error (" " + format ("task config taskd.server {1}", new_server));
|
|
||||||
status = 2;
|
|
||||||
}
|
|
||||||
else if (code == "430")
|
|
||||||
{
|
|
||||||
Context::getContext ().error ("Sync failed. Either your credentials are incorrect, or your account doesn't exist on the Taskserver.");
|
|
||||||
status = 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Context::getContext ().error (format ("Sync failed. The Taskserver returned error: {1} {2}",
|
|
||||||
code,
|
|
||||||
response.get ("status")));
|
|
||||||
status = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display all errors returned. This is recommended by the server protocol.
|
|
||||||
std::string to_be_displayed = response.get ("messages");
|
|
||||||
if (to_be_displayed != "")
|
|
||||||
{
|
|
||||||
if (Context::getContext ().verbose ("footnote"))
|
|
||||||
Context::getContext ().footnote (to_be_displayed);
|
|
||||||
else
|
|
||||||
Context::getContext ().debug (to_be_displayed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some kind of low-level error:
|
|
||||||
// - Server down
|
|
||||||
// - Wrong address
|
|
||||||
// - Wrong port
|
|
||||||
// - Firewall
|
|
||||||
// - Network error
|
|
||||||
// - No signal/cable
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Context::getContext ().error ("Sync failed. Could not connect to the Taskserver.");
|
|
||||||
status = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Context::getContext ().verbose ("sync"))
|
|
||||||
out << '\n';
|
|
||||||
output = out.str ();
|
|
||||||
|
|
||||||
// Restore signal handling.
|
|
||||||
signal (SIGHUP, SIG_DFL);
|
|
||||||
signal (SIGINT, SIG_DFL);
|
|
||||||
signal (SIGPIPE, SIG_DFL);
|
|
||||||
signal (SIGTERM, SIG_DFL);
|
|
||||||
signal (SIGUSR1, SIG_DFL);
|
|
||||||
signal (SIGUSR2, SIG_DFL);
|
|
||||||
|
|
||||||
#else
|
|
||||||
// Without GnuTLS found at compile time, there is no working sync command.
|
|
||||||
throw std::string ("Taskwarrior was built without GnuTLS support. Sync is not available.");
|
|
||||||
#endif
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_LIBGNUTLS
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
bool CmdSync::send (
|
|
||||||
const std::string& to,
|
|
||||||
const std::string& ca,
|
|
||||||
const std::string& certificate,
|
|
||||||
const std::string& key,
|
|
||||||
const enum TLSClient::trust_level trust,
|
|
||||||
const Msg& request,
|
|
||||||
Msg& response)
|
|
||||||
{
|
|
||||||
// It is important that the ':' be the *last* colon, in order to support
|
|
||||||
// IPv6 addresses.
|
|
||||||
auto colon = to.rfind (':');
|
|
||||||
if (colon == std::string::npos)
|
|
||||||
throw format ("Sync failed. Malformed configuration setting '{1}'", to);
|
|
||||||
|
|
||||||
std::string server = to.substr (0, colon);
|
|
||||||
std::string port = to.substr (colon + 1);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
TLSClient client;
|
|
||||||
client.debug (Context::getContext ().config.getInteger ("debug.tls"));
|
|
||||||
|
|
||||||
client.trust (trust);
|
|
||||||
client.ciphers (Context::getContext ().config.get ("taskd.ciphers"));
|
|
||||||
client.init (ca, certificate, key);
|
|
||||||
client.connect (server, port);
|
|
||||||
client.send (request.serialize () + '\n');
|
|
||||||
|
|
||||||
std::string incoming;
|
|
||||||
client.recv (incoming);
|
|
||||||
client.bye ();
|
|
||||||
|
|
||||||
response.parse (incoming);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (std::string& error)
|
|
||||||
{
|
|
||||||
Context::getContext ().error (error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indicate message failed.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue