Add support for cloud sync, specifically GCP (#3223)

* Add support for cloud sync, specifically GCP

This adds generic support for sync to cloud services, with specific
spuport for GCP. Adding others -- so long as they support a
compare-and-set operation -- should be comparatively straightforward.

The cloud support includes cleanup of unnecessary data, and should keep
total space usage roughly proportional to the number of tasks.

Co-authored-by: ryneeverett <ryneeverett@gmail.com>
This commit is contained in:
Dustin J. Mitchell 2024-01-21 12:36:37 -05:00 committed by GitHub
parent 6f1c16fecd
commit 9566c929e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 4012 additions and 401 deletions

View file

@ -56,9 +56,10 @@ target_link_libraries (task_executable task tc tc-rust commands tc columns libsh
target_link_libraries (calc_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc tc-rust commands tc columns libshared task libshared ${TASK_LIBRARIES})
if (DARWIN)
target_link_libraries (task_executable "-framework CoreFoundation -framework Security")
target_link_libraries (calc_executable "-framework CoreFoundation -framework Security")
target_link_libraries (lex_executable "-framework CoreFoundation -framework Security")
# SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration.
target_link_libraries (task_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
target_link_libraries (calc_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
target_link_libraries (lex_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
endif (DARWIN)
set_property (TARGET task_executable PROPERTY OUTPUT_NAME "task")
@ -71,4 +72,4 @@ set_property (TARGET lex_executable PROPERTY OUTPUT_NAME "lex")
#SET(CMAKE_BUILD_TYPE gcov)
#SET(CMAKE_CXX_FLAGS_GCOV "--coverage")
#SET(CMAKE_C_FLAGS_GCOV "--coverage")
#SET(CMAKE_EXE_LINKER_FLAGS_GCOV "--coverage")
#SET(CMAKE_EXE_LINKER_FLAGS_GCOV "--coverage")

View file

@ -283,6 +283,7 @@ std::string configurationDefaults =
"#sync.server.encryption_secret # Encryption secret for sync to a server\n"
"#sync.server.origin # Origin of the sync server\n"
"#sync.local.server_dir # Directory for local sync\n"
"#sync.gcp.bucket # Bucket for sync to GCP\n"
"\n"
"# Aliases - alternate names for commands\n"
"alias.rm=delete # Alias for the delete command\n"

View file

@ -193,6 +193,7 @@ int CmdShow::execute (std::string& output)
" sugar"
" summary.all.projects"
" sync.local.server_dir"
" sync.gcp.bucket"
" sync.server.client_id"
" sync.server.encryption_secret"
" sync.server.origin"

View file

@ -63,17 +63,32 @@ int CmdSync::execute (std::string& output)
// If no server is set up, quit.
std::string origin = Context::getContext ().config.get ("sync.server.origin");
std::string client_id = Context::getContext ().config.get ("sync.server.client_id");
std::string encryption_secret = Context::getContext ().config.get ("sync.server.encryption_secret");
std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir");
std::string gcp_bucket = Context::getContext ().config.get ("sync.gcp.bucket");
if (server_dir != "") {
server = tc::Server (server_dir);
server = tc::Server::new_local (server_dir);
server_ident = server_dir;
} else if (origin != "" && client_id != "" && encryption_secret != "") {
server = tc::Server (origin, client_id, encryption_secret);
server_ident = origin;
} else if (gcp_bucket != "") {
std::string encryption_secret = Context::getContext ().config.get ("sync.gcp.encryption_secret");
if (encryption_secret == "") {
throw std::string ("sync.gcp.encryption_secret is required");
}
server = tc::Server::new_gcp (gcp_bucket, encryption_secret);
std::ostringstream os;
os << "GCP bucket " << gcp_bucket;
server_ident = os.str();
} else if (origin != "") {
std::string client_id = Context::getContext ().config.get ("sync.server.client_id");
std::string encryption_secret = Context::getContext ().config.get ("sync.server.encryption_secret");
if (client_id == "" || encryption_secret == "") {
throw std::string ("sync.server.client_id and encryption_secret are required");
}
server = tc::Server::new_sync (origin, client_id, encryption_secret);
std::ostringstream os;
os << "Sync server at " << origin;
server_ident = os.str();
} else {
throw std::string ("Neither sync.server nor sync.local are configured.");
throw std::string ("No sync.* settings are configured.");
}
std::stringstream out;

View file

@ -32,7 +32,8 @@
using namespace tc::ffi;
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (const std::string &server_dir)
tc::Server
tc::Server::new_local (const std::string &server_dir)
{
TCString tc_server_dir = tc_string_borrow (server_dir.c_str ());
TCString error;
@ -43,18 +44,17 @@ tc::Server::Server (const std::string &server_dir)
tc_string_free (&error);
throw errmsg;
}
inner = unique_tcserver_ptr (
return Server (unique_tcserver_ptr (
tcserver,
[](TCServer* rep) { tc_server_free (rep); });
[](TCServer* rep) { tc_server_free (rep); }));
}
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (const std::string &origin, const std::string &client_id, const std::string &encryption_secret)
tc::Server
tc::Server::new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret)
{
TCString tc_origin = tc_string_borrow (origin.c_str ());
TCString tc_client_id = tc_string_borrow (client_id.c_str ());
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
TCUuid tc_client_uuid;
@ -65,16 +65,36 @@ tc::Server::Server (const std::string &origin, const std::string &client_id, con
}
TCString error;
auto tcserver = tc_server_new_remote (tc_origin, tc_client_uuid, tc_encryption_secret, &error);
auto tcserver = tc_server_new_sync (tc_origin, tc_client_uuid, tc_encryption_secret, &error);
if (!tcserver) {
auto errmsg = format ("Could not configure connection to server at {1}: {2}",
origin, tc_string_content (&error));
tc_string_free (&error);
throw errmsg;
}
inner = unique_tcserver_ptr (
return Server (unique_tcserver_ptr (
tcserver,
[](TCServer* rep) { tc_server_free (rep); });
[](TCServer* rep) { tc_server_free (rep); }));
}
////////////////////////////////////////////////////////////////////////////////
tc::Server
tc::Server::new_gcp (const std::string &bucket, const std::string &encryption_secret)
{
TCString tc_bucket = tc_string_borrow (bucket.c_str ());
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
TCString error;
auto tcserver = tc_server_new_gcp (tc_bucket, tc_encryption_secret, &error);
if (!tcserver) {
auto errmsg = format ("Could not configure connection to GCP bucket {1}: {2}",
bucket, tc_string_content (&error));
tc_string_free (&error);
throw errmsg;
}
return Server (unique_tcserver_ptr (
tcserver,
[](TCServer* rep) { tc_server_free (rep); }));
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -43,7 +43,7 @@ namespace tc {
// Server wraps the TCServer type, managing its memory, errors, and so on.
//
// Except as noted, method names match the suffix to `tc_replica_..`.
// Except as noted, method names match the suffix to `tc_server_..`.
class Server
{
public:
@ -51,10 +51,13 @@ namespace tc {
Server () = default;
// Construct a local server (tc_server_new_local).
Server (const std::string& server_dir);
static Server new_local (const std::string& server_dir);
// Construct a remote server (tc_server_new_remote).
Server (const std::string &origin, const std::string &client_id, const std::string &encryption_secret);
// Construct a remote server (tc_server_new_sync).
static Server new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret);
// Construct a GCP server (tc_server_new_gcp).
static Server new_gcp (const std::string &bucket, const std::string &encryption_secret);
// This object "owns" inner, so copy is not allowed.
Server (const Server &) = delete;
@ -65,6 +68,8 @@ namespace tc {
Server &operator=(Server &&) noexcept;
protected:
Server (unique_tcserver_ptr inner) : inner(std::move(inner)) {};
unique_tcserver_ptr inner;
// Replica accesses the inner pointer to call tc_replica_sync

1299
src/tc/rust/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,4 +6,5 @@ version = "0.1.0"
crate-type = ["staticlib"]
[dependencies]
taskchampion-lib = {path = "../../../taskchampion/lib"}
taskchampion = { path = "../../../taskchampion/taskchampion", features = ["server-gcp", "server-sync"] }
taskchampion-lib = { path = "../../../taskchampion/lib" }