\tSet priority of all files in to \n";
td::TerminalIO::out() << "\tPriority is in [0..255], 0 - don't download\n";
td::TerminalIO::out() << "priority-idx \tSet priority of file # in to \n";
td::TerminalIO::out() << "\tPriority is in [0..255], 0 - don't download\n";
td::TerminalIO::out() << "priority-name \tSet priority of file in to \n";
td::TerminalIO::out() << "\tPriority is in [0..255], 0 - don't download\n";
td::TerminalIO::out() << "remove [--remove-files]\tRemove \n";
td::TerminalIO::out() << "\t--remove-files - also remove all files\n";
td::TerminalIO::out() << "load-from [--meta meta] [--files path]\tProvide meta and data for an existing "
"incomplete bag.\n";
td::TerminalIO::out() << "\t--meta meta\ttorrent info and header will be inited (if not ready) from meta file\n";
td::TerminalIO::out() << "\t--files path\tdata for files will be taken from here\n";
td::TerminalIO::out() << "get-speed-limits [--json]\tShow global limits for download and upload speed\n";
td::TerminalIO::out() << "\t--json\tOutput in json\n";
td::TerminalIO::out()
<< "set-speed-limits [--download x] [--upload x]\tSet global limits for download and upload speed\n";
td::TerminalIO::out() << "\t--download x\tDownload speed limit in bytes/s, or \"unlimited\"\n";
td::TerminalIO::out() << "\t--upload x\tUpload speed limit in bytes/s, or \"unlimited\"\n";
td::TerminalIO::out() << "new-contract-message [--query-id id] --provider \tCreate "
"\"new contract message\" for storage provider. Saves message body to .\n";
td::TerminalIO::out() << "\t\tAddress of storage provider account to take parameters from.\n";
td::TerminalIO::out() << "new-contract-message [--query-id id] --rate --max-span "
"\tSame thing, but parameters are not fetched automatically.\n";
td::TerminalIO::out() << "exit\tExit\n";
td::TerminalIO::out() << "quit\tExit\n";
td::TerminalIO::out() << "setverbosity \tSet vetbosity to in [0..10]\n";
td::TerminalIO::out() << "help\tPrint this help\n";
td::TerminalIO::out() << "help provider\tcommands for deploying and controling storage provider\n";
} else if (category == "provider") {
td::TerminalIO::out() << "\nStorage provider control:\n";
td::TerminalIO::out() << "import-pk \tImport private key from \n";
td::TerminalIO::out() << "deploy-provider\tInit storage provider by deploying a new provider smart contract\n";
td::TerminalIO::out()
<< "init-provider \tInit storage provider using the existing provider smart contract\n";
td::TerminalIO::out() << "remove-storage-provider\tRemove storage provider\n";
td::TerminalIO::out()
<< "\tSmart contracts in blockchain and bags will remain intact, but they will not be managed anymore\n";
td::TerminalIO::out() << "get-provider-params [address] [--json]\tPrint parameters of the smart contract\n";
td::TerminalIO::out()
<< "\taddress\tAddress of a smart contract. Default is the provider managed by this daemon.\n";
td::TerminalIO::out() << "\t--json\tOutput in json\n";
td::TerminalIO::out() << "set-provider-params [--accept x] [--rate x] [--max-span x] [--min-file-size x] "
"[--max-file-size x]\tSet parameters of the smart contract\n";
td::TerminalIO::out() << "\t--accept\tAccept new contracts: 0 (no) or 1 (yes)\n";
td::TerminalIO::out() << "\t--rate\tPrice of storage, nanoTON per MB*day\n";
td::TerminalIO::out() << "\t--max-span\n";
td::TerminalIO::out() << "\t--min-file-size\tMinimal total size of a bag of files (bytes)\n";
td::TerminalIO::out() << "\t--max-file-size\tMaximal total size of a bag of files (bytes)\n";
td::TerminalIO::out()
<< "get-provider-info [--balances] [--contracts] [--json]\tPrint information about storage provider\n";
td::TerminalIO::out() << "\t--contracts\tPrint list of storage contracts\n";
td::TerminalIO::out() << "\t--balances\tPrint balances of the main contract and storage contracts\n";
td::TerminalIO::out() << "\t--json\tOutput in json\n";
td::TerminalIO::out()
<< "set-provider-config [--max-contracts x] [--max-total-size x]\tSet configuration parameters\n";
td::TerminalIO::out() << "\t--max-contracts\tMaximal number of storage contracts\n";
td::TerminalIO::out() << "\t--max-total-size\tMaximal total size storage contracts (in bytes)\n";
td::TerminalIO::out() << "withdraw \tSend bounty from storage contract to the main contract\n";
td::TerminalIO::out()
<< "withdraw-all\tSend bounty from all storage contracts (where at least 1 TON is available) "
"to the main contract\n";
td::TerminalIO::out() << "send-coins [--message msg]\tSend nanoTON to from "
"the main contract\n";
td::TerminalIO::out()
<< "close-contract \tClose storage contract and delete bag (if possible)\n";
} else {
td::TerminalIO::out() << "Unknown command 'help " << category << "'\n";
}
command_finished(td::Status::OK());
return td::Status::OK();
}
td::Status execute_set_verbosity(int level) {
auto query = create_tl_object(level);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Success\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_create(std::string path, std::string description, bool upload, bool copy, bool json) {
TRY_RESULT_PREFIX_ASSIGN(path, td::realpath(path), "Invalid path: ");
auto query = create_tl_object(path, description, upload, copy, 0);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
td::TerminalIO::out() << "Bag created\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::print_torrent_full, R.move_as_ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_add_by_hash(td::Bits256 hash, std::string root_dir, bool paused, bool upload,
td::optional> partial, bool json) {
if (!root_dir.empty()) {
TRY_STATUS_PREFIX(td::mkpath(root_dir), "Failed to create directory: ");
TRY_STATUS_PREFIX(td::mkdir(root_dir), "Failed to create directory: ");
TRY_RESULT_PREFIX_ASSIGN(root_dir, td::realpath(root_dir), "Invalid path: ");
}
std::vector> priorities;
if (partial) {
priorities.push_back(create_tl_object(0));
for (std::string& f : partial.value()) {
priorities.push_back(create_tl_object(std::move(f), 1));
}
}
auto query = create_tl_object(hash, std::move(root_dir), !paused, upload,
std::move(priorities), 0);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
td::TerminalIO::out() << "Bag added\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::print_torrent_full, R.move_as_ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_add_by_meta(std::string meta_file, std::string root_dir, bool paused, bool upload,
td::optional> partial, bool json) {
TRY_RESULT_PREFIX(meta, td::read_file(meta_file), "Failed to read meta: ");
if (!root_dir.empty()) {
TRY_STATUS_PREFIX(td::mkpath(root_dir), "Failed to create directory: ");
TRY_STATUS_PREFIX(td::mkdir(root_dir), "Failed to create directory: ");
TRY_RESULT_PREFIX_ASSIGN(root_dir, td::realpath(root_dir), "Invalid path: ");
}
std::vector> priorities;
if (partial) {
priorities.push_back(create_tl_object(0));
for (std::string& f : partial.value()) {
priorities.push_back(create_tl_object(std::move(f), 1));
}
}
auto query = create_tl_object(std::move(meta), std::move(root_dir), !paused,
upload, std::move(priorities), 0);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
td::TerminalIO::out() << "Bag added\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::print_torrent_full, R.move_as_ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_list(bool with_hashes, bool json) {
auto query = create_tl_object(0);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
td::actor::send_closure(SelfId, &StorageDaemonCli::print_torrent_list, R.move_as_ok(), with_hashes);
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_get(td::Bits256 hash, bool json) {
auto query = create_tl_object(hash, 0);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
td::actor::send_closure(SelfId, &StorageDaemonCli::print_torrent_full, R.move_as_ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_get_meta(td::Bits256 hash, std::string meta_file) {
auto query = create_tl_object(hash, 0);
send_query(std::move(query),
[SelfId = actor_id(this), meta_file](td::Result> R) {
if (R.is_error()) {
return;
}
auto data = std::move(R.ok_ref()->meta_);
auto S = td::write_file(meta_file, data);
if (S.is_error()) {
td::actor::send_closure(
SelfId, &StorageDaemonCli::command_finished,
S.move_as_error_prefix(PSTRING() << "Failed to write meta (" << data.size() << " B): "));
return;
}
td::TerminalIO::out() << "Saved meta (" << data.size() << " B)\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_get_peers(td::Bits256 hash, bool json) {
auto query = create_tl_object(hash, 0);
send_query(
std::move(query), [=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
auto obj = R.move_as_ok();
td::TerminalIO::out() << "BagID " << hash.to_hex() << "\n";
td::TerminalIO::out() << "Download speed: " << td::format::as_size((td::uint64)obj->download_speed_)
<< "/s\n";
td::TerminalIO::out() << "Upload speed: " << td::format::as_size((td::uint64)obj->upload_speed_) << "/s\n";
td::TerminalIO::out() << "Peers: " << obj->peers_.size() << "\n";
std::vector> table;
table.push_back({"ADNL id", "Address", "Download", "Upload", "Ready"});
for (auto& peer : obj->peers_) {
std::vector row;
row.push_back(PSTRING() << peer->adnl_id_);
row.push_back(peer->ip_str_);
row.push_back(PSTRING() << td::format::as_size((td::uint64)peer->download_speed_) << "/s");
row.push_back(PSTRING() << td::format::as_size((td::uint64)peer->upload_speed_) << "/s");
if (obj->total_parts_ > 0) {
char buf[10];
snprintf(buf, sizeof(buf), "%5.1f%%", (double)peer->ready_parts_ / (double)obj->total_parts_ * 100);
row.push_back(buf);
} else {
row.push_back("???");
}
table.push_back(std::move(row));
}
print_table(table);
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_get_pieces_info(td::Bits256 hash, bool files, td::uint64 offset,
td::optional max_pieces, bool json) {
auto query = create_tl_object(hash, files ? 1 : 0, offset,
max_pieces ? max_pieces.value() : -1);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
auto obj = R.move_as_ok();
td::TerminalIO::out() << "BagID " << hash.to_hex() << "\n";
td::TerminalIO::out() << "Total pieces: " << obj->total_pieces_ << ", piece size: " << obj->piece_size_
<< "\n";
if (files) {
if (obj->flags_ & 1) {
td::TerminalIO::out() << "Files:\n";
std::vector> table;
table.push_back({"#####", "Piece range", "Name"});
size_t i = 0;
for (const auto& f : obj->files_) {
table.push_back({i == 0 ? "" : td::to_string(i - 1),
PSTRING() << "[" << f->range_l_ << ".." << f->range_r_ << ")",
f->name_.empty() ? "[HEADER]" : f->name_});
++i;
}
print_table(table, {1, 2});
} else {
td::TerminalIO::out() << "Cannot show files: torrent header is not available\n";
}
}
td::uint64 l = obj->range_l_, r = obj->range_r_;
td::TerminalIO::out() << "Pieces [" << l << ".." << r << ")\n";
if (obj->range_l_ != obj->range_r_) {
std::vector> table;
td::uint64 i = l;
while (i < r) {
td::uint64 ir = std::min(i + 100, r);
std::string s = "[";
for (td::uint64 j = i; j < ir; ++j) {
s += (obj->piece_ready_bitset_[(j - l) / 8] & (1 << ((j - l) % 8)) ? '#' : '-');
}
s += ']';
table.push_back({std::to_string(i), s});
i = ir;
}
print_table(table, {1});
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_active_download(td::Bits256 hash, bool active) {
auto query = create_tl_object(hash, active);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Success\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_active_upload(td::Bits256 hash, bool active) {
auto query = create_tl_object(hash, active);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Success\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_priority_all(td::Bits256 hash, td::uint8 priority) {
auto query = create_tl_object(hash, priority);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (R.ok()->get_id() == ton_api::storage_daemon_prioritySet::ID) {
td::TerminalIO::out() << "Priority was set\n";
} else {
td::TerminalIO::out() << "Torrent header is not available, priority will be set later\n";
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_priority_idx(td::Bits256 hash, td::uint64 idx, td::uint8 priority) {
auto query = create_tl_object(hash, idx, priority);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (R.ok()->get_id() == ton_api::storage_daemon_prioritySet::ID) {
td::TerminalIO::out() << "Priority was set\n";
} else {
td::TerminalIO::out() << "Torrent header is not available, priority will be set later\n";
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_priority_name(td::Bits256 hash, std::string name, td::uint8 priority) {
auto query = create_tl_object(hash, std::move(name), priority);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (R.ok()->get_id() == ton_api::storage_daemon_prioritySet::ID) {
td::TerminalIO::out() << "Priority was set\n";
} else {
td::TerminalIO::out() << "Torrent header is not available, priority will be set later\n";
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_remove(td::Bits256 hash, bool remove_files) {
auto query = create_tl_object(hash, remove_files);
send_query(std::move(query),
[SelfId = actor_id(this), hash](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Success\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::delete_id, hash);
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_load_from(td::Bits256 hash, std::string meta, std::string path) {
if (meta.empty() && path.empty()) {
return td::Status::Error("Expected meta or files");
}
td::BufferSlice meta_data;
if (!meta.empty()) {
TRY_RESULT_PREFIX_ASSIGN(meta_data, td::read_file(meta), "Failed to read meta: ");
}
if (!path.empty()) {
TRY_RESULT_PREFIX_ASSIGN(path, td::realpath(path), "Invalid path: ");
}
auto query = create_tl_object(hash, std::move(meta_data), std::move(path), 0);
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
auto torrent = R.move_as_ok();
td::TerminalIO::out() << "Loaded data for bag " << torrent->hash_.to_hex() << "\n";
if (torrent->flags_ & 4) { // fatal error
td::TerminalIO::out() << "FATAL ERROR: " << torrent->fatal_error_ << "\n";
}
if (torrent->flags_ & 1) { // info ready
td::TerminalIO::out() << "Total size: " << td::format::as_size(torrent->total_size_) << "\n";
if (torrent->flags_ & 2) { // header ready
td::TerminalIO::out() << "Ready: " << td::format::as_size(torrent->downloaded_size_) << "/"
<< td::format::as_size(torrent->included_size_)
<< (torrent->completed_ ? " (completed)" : "") << "\n";
} else {
td::TerminalIO::out() << "Torrent header is not ready\n";
}
} else {
td::TerminalIO::out() << "Torrent info is not ready\n";
}
});
return td::Status::OK();
}
td::Status execute_get_speed_limits(bool json) {
auto query = create_tl_object(0);
send_query(std::move(query), [=, SelfId = actor_id(this)](
td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
auto obj = R.move_as_ok();
if (obj->download_ < 0.0) {
td::TerminalIO::out() << "Download speed limit: unlimited\n";
} else {
td::TerminalIO::out() << "Download speed limit: " << td::format::as_size((td::uint64)obj->download_) << "/s\n";
}
if (obj->upload_ < 0.0) {
td::TerminalIO::out() << "Upload speed limit: unlimited\n";
} else {
td::TerminalIO::out() << "Upload speed limit: " << td::format::as_size((td::uint64)obj->upload_) << "/s\n";
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_speed_limits(td::optional download, td::optional upload) {
if (!download && !upload) {
return td::Status::Error("No parameters are set");
}
auto query = create_tl_object();
query->flags_ = 0;
if (download) {
query->flags_ |= 1;
query->download_ = download.value();
}
if (upload) {
query->flags_ |= 2;
query->upload_ = upload.value();
}
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Speed limits were set\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_new_contract_message(td::Bits256 hash, std::string file, td::uint64 query_id,
td::optional provider_address, td::optional rate,
td::optional max_span) {
tl_object_ptr params;
if (provider_address) {
if (rate || max_span) {
return td::Status::Error("Incompatible flags");
}
params = create_tl_object(provider_address.unwrap());
} else {
if (!rate || !max_span) {
return td::Status::Error("No flags are set");
}
params = create_tl_object(rate.unwrap(), max_span.unwrap());
}
auto query = create_tl_object(hash, query_id, std::move(params));
send_query(std::move(query), [SelfId = actor_id(this),
file](td::Result> R) {
if (R.is_error()) {
return;
}
auto obj = R.move_as_ok();
auto S = td::write_file(file, obj->body_);
if (S.is_error()) {
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished,
S.move_as_error_prefix(PSTRING() << "Failed to write to file: "));
return;
}
td::TerminalIO::out() << "Saved message body to file\n";
td::TerminalIO::out() << "Rate (nanoTON per mb*day): " << obj->rate_ << "\n";
td::TerminalIO::out() << "Max span: " << obj->max_span_ << "\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_import_pk(std::string file) {
TRY_RESULT(data, td::read_file_secure(file));
TRY_RESULT(pk, ton::PrivateKey::import(data.as_slice()));
auto query = create_tl_object(pk.tl());
send_query(
std::move(query), [SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Imported private key. Public key hash: " << R.ok()->key_hash_.to_hex() << "\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_deploy_provider() {
auto query = create_tl_object();
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
auto obj = R.move_as_ok();
block::StdAddress std_address;
CHECK(std_address.parse_addr(obj->address_));
std_address.bounceable = false;
td::TerminalIO::out() << "Address: " << obj->address_ << "\n";
td::TerminalIO::out() << "Non-bounceable address: " << std_address.rserialize() << "\n";
td::TerminalIO::out()
<< "Send a non-bounceable message with 1 TON to this address to initialize smart contract.\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_init_provider(std::string address) {
auto query = create_tl_object(std::move(address));
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Address of the storage provider was set\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_remove_storage_provider() {
auto query = create_tl_object();
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Storage provider removed\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_get_provider_params(std::string address, bool json) {
auto query = create_tl_object(address);
send_query(std::move(query), [=, SelfId = actor_id(this)](
td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
auto params = R.move_as_ok();
td::TerminalIO::out() << "Storage provider parameters:\n";
td::TerminalIO::out() << "Accept new contracts: " << params->accept_new_contracts_ << "\n";
td::TerminalIO::out() << "Rate (nanoTON per day*MB): " << params->rate_per_mb_day_ << "\n";
td::TerminalIO::out() << "Max span: " << (td::uint32)params->max_span_ << "\n";
auto min_size = (td::uint64)params->minimal_file_size_, max_size = (td::uint64)params->maximal_file_size_;
td::TerminalIO::out() << "Min file size: " << td::format::as_size(min_size) << " (" << min_size << ")\n";
td::TerminalIO::out() << "Max file size: " << td::format::as_size(max_size) << " (" << max_size << ")\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_provider_params(OptionalProviderParams new_params) {
auto query_get = create_tl_object();
send_query(std::move(query_get), [SelfId = actor_id(this), new_params = std::move(new_params)](
td::Result> R) mutable {
if (R.is_error()) {
return;
}
td::actor::send_closure(SelfId, &StorageDaemonCli::execute_set_provider_params_cont, R.move_as_ok(),
std::move(new_params));
});
return td::Status::OK();
}
void execute_set_provider_params_cont(tl_object_ptr params,
OptionalProviderParams new_params) {
if (new_params.accept_new_contracts) {
params->accept_new_contracts_ = new_params.accept_new_contracts.unwrap();
}
if (new_params.rate_per_mb_day) {
params->rate_per_mb_day_ = new_params.rate_per_mb_day.unwrap();
}
if (new_params.max_span) {
params->max_span_ = new_params.max_span.unwrap();
}
if (new_params.minimal_file_size) {
params->minimal_file_size_ = new_params.minimal_file_size.unwrap();
}
if (new_params.maximal_file_size) {
params->maximal_file_size_ = new_params.maximal_file_size.unwrap();
}
td::TerminalIO::out() << "Sending external message to update provider parameters...\n";
auto query_set = create_tl_object(std::move(params));
send_query(std::move(query_set),
[SelfId = actor_id(this)](td::Result> R) mutable {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Storage provider parameters were updated\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
}
td::Status execute_get_provider_info(bool with_balances, bool with_contracts, bool json) {
auto query = create_tl_object(with_balances, with_contracts);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
if (json) {
print_json(R.ok());
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
return;
}
auto info = R.move_as_ok();
td::TerminalIO::out() << "Storage provider " << info->address_ << "\n";
td::TerminalIO::out() << "Storage contracts: " << (td::uint32)info->contracts_count_ << " / "
<< (td::uint32)info->config_->max_contracts_ << "\n";
td::TerminalIO::out() << "Total size: " << size_to_str(info->contracts_total_size_) << " / "
<< size_to_str(info->config_->max_total_size_) << "\n";
if (with_balances) {
td::TerminalIO::out() << "Main contract balance: " << coins_to_str(info->balance_) << " TON\n";
}
if (with_contracts) {
td::TerminalIO::out() << "Storage contracts: " << info->contracts_.size() << "\n";
std::vector> table;
table.push_back({"Address", "BagID", "Created at", "Size", "State"});
if (with_balances) {
table.back().push_back("Client$");
table.back().push_back("Contract$");
}
for (const auto& c : info->contracts_) {
table.emplace_back();
table.back().push_back(c->address_);
table.back().push_back(c->torrent_.to_hex());
table.back().push_back(time_to_str(c->created_time_));
table.back().push_back(size_to_str(c->file_size_));
// enum State { st_downloading = 0, st_downloaded = 1, st_active = 2, st_closing = 3 };
switch (c->state_) {
case 0:
table.back().push_back("Downloading (" + size_to_str(c->downloaded_size_) + ")");
break;
case 1:
table.back().push_back("Downloaded");
break;
case 2:
table.back().push_back("Active");
break;
case 3:
table.back().push_back("Closing");
break;
default:
table.back().push_back("???");
}
if (with_balances) {
table.back().push_back(coins_to_str(c->client_balance_));
table.back().push_back(coins_to_str(c->contract_balance_));
}
}
print_table(table);
}
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_set_provider_config(OptionalProviderConfig new_config) {
auto query_get = create_tl_object(false, false);
send_query(std::move(query_get), [SelfId = actor_id(this), new_config = std::move(new_config)](
td::Result> R) mutable {
if (R.is_error()) {
return;
}
auto info = R.move_as_ok();
td::actor::send_closure(SelfId, &StorageDaemonCli::execute_set_provider_config_cont, std::move(info->config_),
std::move(new_config));
});
return td::Status::OK();
}
void execute_set_provider_config_cont(tl_object_ptr config,
OptionalProviderConfig new_config) {
if (new_config.max_contracts) {
config->max_contracts_ = new_config.max_contracts.unwrap();
}
if (new_config.max_total_size) {
config->max_total_size_ = new_config.max_total_size.unwrap();
}
auto query_set = create_tl_object(std::move(config));
send_query(std::move(query_set),
[SelfId = actor_id(this)](td::Result> R) mutable {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Storage provider config was updated\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
}
td::Status execute_withdraw(std::string address) {
auto query = create_tl_object(std::move(address));
td::TerminalIO::out() << "Sending external message...\n";
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Bounty was withdrawn\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_withdraw_all() {
auto query = create_tl_object(true, true);
send_query(std::move(query),
[=, SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
auto info = R.move_as_ok();
std::vector addresses;
for (auto& contract : info->contracts_) {
if (contract->state_ != 2) {
continue;
}
td::RefInt256 remaining = td::dec_string_to_int256(contract->contract_balance_) -
td::dec_string_to_int256(contract->client_balance_);
if (remaining < td::make_refint(1'000'000'000)) {
continue;
}
td::TerminalIO::out() << "Withdrawing from " << contract->address_ << " (" << coins_to_str(remaining)
<< " TON)\n";
addresses.push_back(contract->address_);
}
if (addresses.empty()) {
td::TerminalIO::out() << "Nothing to withdraw\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
} else {
td::actor::send_closure(SelfId, &StorageDaemonCli::execute_withdraw_all_cont, std::move(addresses));
}
});
return td::Status::OK();
}
void execute_withdraw_all_cont(std::vector addresses) {
td::MultiPromise mp;
auto ig = mp.init_guard();
ig.add_promise([SelfId = actor_id(this), cnt = addresses.size()](td::Result R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, R.move_as_error());
return;
}
td::TerminalIO::out() << "Sent bounty from " << cnt << " contracts\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
for (auto& address : addresses) {
auto query = create_tl_object(std::move(address));
send_query(std::move(query),
ig.get_promise().wrap([](tl_object_ptr) { return td::Unit(); }));
}
}
td::Status execute_send_coins(std::string address, std::string amount, std::string message) {
auto query =
create_tl_object(std::move(address), std::move(amount), std::move(message));
td::TerminalIO::out() << "Sending external messages...\n";
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Internal message was sent\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
td::Status execute_close_contract(std::string address) {
auto query = create_tl_object(std::move(address));
send_query(std::move(query),
[SelfId = actor_id(this)](td::Result> R) {
if (R.is_error()) {
return;
}
td::TerminalIO::out() << "Closing storage contract\n";
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, td::Status::OK());
});
return td::Status::OK();
}
template
void send_query(tl_object_ptr query, td::Promise promise, bool process_error = true) {
td::actor::send_closure(
client_, &adnl::AdnlExtClient::send_query, "q", serialize_tl_object(query, true), td::Timestamp::in(1800.0),
[SelfId = actor_id(this), promise = std::move(promise), process_error](td::Result R) mutable {
td::Result result;
if (R.is_error()) {
if (R.error().message().empty() && R.error().code() == ErrorCode::cancelled) {
result = td::Status::Error("Query error: failed to connect");
} else {
result = R.move_as_error_prefix("Query error: ");
}
} else {
td::BufferSlice data = R.move_as_ok();
result = fetch_tl_object(data, true);
if (result.is_error()) {
auto R3 = fetch_tl_object(data, true);
if (R3.is_ok()) {
result = td::Status::Error("Query error: " + R3.ok()->message_);
}
}
}
if (result.is_ok()) {
promise.set_value(result.move_as_ok());
} else {
promise.set_error(result.error().clone());
if (process_error) {
td::actor::send_closure(SelfId, &StorageDaemonCli::command_finished, result.move_as_error());
}
}
});
}
void command_finished(td::Status S) {
if (S.is_error()) {
td::TerminalIO::out() << S.message() << "\n";
if (batch_mode_) {
std::exit(2);
}
} else if (batch_mode_) {
if (cur_command_ == commands_.size()) {
std::exit(0);
} else {
parse_line(td::BufferSlice(commands_[cur_command_++]));
}
}
}
private:
td::IPAddress server_ip_;
PrivateKey client_private_key_;
PublicKey server_public_key_;
std::vector commands_;
bool batch_mode_ = false;
bool batch_started_ = false;
size_t cur_command_ = 0;
td::actor::ActorOwn client_;
td::actor::ActorOwn io_;
std::map id_to_hash_;
std::map hash_to_id_;
td::uint32 cur_id_ = 0;
void add_id(td::Bits256 hash) {
if (hash_to_id_.emplace(hash, cur_id_).second) {
id_to_hash_[cur_id_++] = hash;
}
}
void delete_id(td::Bits256 hash) {
auto it = hash_to_id_.find(hash);
if (it != hash_to_id_.end()) {
id_to_hash_.erase(it->second);
hash_to_id_.erase(it);
}
}
void update_ids(std::vector hashes) {
for (const td::Bits256& hash : hashes) {
add_id(hash);
}
std::sort(hashes.begin(), hashes.end());
for (auto it = hash_to_id_.begin(); it != hash_to_id_.end();) {
if (std::binary_search(hashes.begin(), hashes.end(), it->first)) {
++it;
} else {
id_to_hash_.erase(it->second);
it = hash_to_id_.erase(it);
}
}
}
void print_torrent_full(tl_object_ptr ptr) {
auto& obj = *ptr;
add_id(obj.torrent_->hash_);
td::TerminalIO::out() << "BagID = " << obj.torrent_->hash_.to_hex() << "\n";
td::TerminalIO::out() << "Index = " << hash_to_id_[obj.torrent_->hash_] << "\n";
td::TerminalIO::out() << "Added: " << time_to_str(obj.torrent_->added_at_) << "\n";
if (obj.torrent_->flags_ & 4) { // fatal error
td::TerminalIO::out() << "FATAL ERROR: " << obj.torrent_->fatal_error_ << "\n";
}
if (obj.torrent_->flags_ & 1) { // info ready
if (!obj.torrent_->description_.empty()) {
td::TerminalIO::out() << "-----------------------------------\n";
td::TerminalIO::out() << obj.torrent_->description_ << "\n";
td::TerminalIO::out() << "-----------------------------------\n";
}
if (obj.torrent_->flags_ & 2) { // header ready
td::TerminalIO::out() << "Downloaded: " << td::format::as_size(obj.torrent_->downloaded_size_) << "/"
<< td::format::as_size(obj.torrent_->included_size_)
<< (obj.torrent_->completed_
? " (completed)"
: " (remaining " +
size_to_str(obj.torrent_->included_size_ - obj.torrent_->downloaded_size_) +
")")
<< "\n";
td::TerminalIO::out() << "Dir name: " << obj.torrent_->dir_name_ << "\n";
}
td::TerminalIO::out() << "Total size: " << td::format::as_size(obj.torrent_->total_size_) << "\n";
} else {
td::TerminalIO::out() << "Torrent info is not available\n";
}
if (obj.torrent_->completed_) {
} else if (obj.torrent_->active_download_) {
td::TerminalIO::out() << "Download speed: " << td::format::as_size((td::uint64)obj.torrent_->download_speed_)
<< "/s\n";
} else {
td::TerminalIO::out() << "Download paused\n";
}
if (obj.torrent_->active_upload_) {
td::TerminalIO::out() << "Upload speed: " << td::format::as_size((td::uint64)obj.torrent_->upload_speed_)
<< "/s\n";
} else {
td::TerminalIO::out() << "Upload paused\n";
}
td::TerminalIO::out() << "Root dir: " << obj.torrent_->root_dir_ << "\n";
if (obj.torrent_->flags_ & 2) { // header ready
td::TerminalIO::out() << obj.files_.size() << " files:\n";
td::TerminalIO::out() << "###### Prior Ready/Size Name\n";
td::uint32 i = 0;
for (const auto& f : obj.files_) {
char str[64];
char priority[4] = "---";
if (f->priority_ > 0) {
CHECK(f->priority_ <= 255);
snprintf(priority, sizeof(priority), "%03d", f->priority_);
}
snprintf(str, sizeof(str), "%6u: (%s) %7s/%-7s %s ", i, priority,
f->priority_ == 0 ? "---" : size_to_str(f->downloaded_size_).c_str(), size_to_str(f->size_).c_str(),
((f->downloaded_size_ == f->size_ && f->priority_ > 0) ? "+" : " "));
td::TerminalIO::out() << str << f->name_ << "\n";
++i;
}
} else {
td::TerminalIO::out() << "Torrent header is not available\n";
}
}
void print_torrent_list(tl_object_ptr ptr, bool with_hashes) {
auto& obj = *ptr;
std::vector hashes;
for (const auto& torrent : obj.torrents_) {
hashes.push_back(torrent->hash_);
}
update_ids(std::move(hashes));
std::sort(obj.torrents_.begin(), obj.torrents_.end(),
[&](const tl_object_ptr& a,
const tl_object_ptr& b) {
return hash_to_id_[a->hash_] < hash_to_id_[b->hash_];
});
td::TerminalIO::out() << obj.torrents_.size() << " bags\n";
std::vector> table;
table.push_back({"#####", "BagID", "Description", "Downloaded", "Total", "Download", "Upload"});
for (const auto& torrent : obj.torrents_) {
std::vector row;
row.push_back(std::to_string(hash_to_id_[torrent->hash_]));
std::string hash_str = torrent->hash_.to_hex();
if (!with_hashes) {
hash_str = hash_str.substr(0, 8) + "...";
}
row.push_back(hash_str);
std::string description = torrent->description_;
for (size_t i = 0; i < description.size(); ++i) {
if (!is_whitespace(description[i])) {
description.erase(description.begin(), description.begin() + i);
break;
}
}
for (size_t i = 0; i < description.size(); ++i) {
if (description[i] == '\n') {
description.resize(i);
break;
}
}
if (description.size() > 45) {
description.resize(42);
description += "...";
}
row.push_back(description);
bool info_ready = torrent->flags_ & 1;
bool header_ready = torrent->flags_ & 2;
std::string downloaded_size = size_to_str(torrent->downloaded_size_);
std::string included_size = header_ready ? size_to_str(torrent->included_size_) : "???";
std::string total_size = info_ready ? size_to_str(torrent->total_size_) : "???";
std::string status;
if (torrent->flags_ & 4) { // fatal error
status = "FATAL ERROR: " + torrent->fatal_error_;
} else {
status =
torrent->completed_
? "COMPLETED"
: (torrent->active_download_ ? size_to_str((td::uint64)torrent->download_speed_) + "/s" : "Paused");
}
row.push_back(downloaded_size.append("/").append(included_size));
row.push_back(total_size);
row.push_back(status);
row.push_back(torrent->active_upload_ ? size_to_str((td::uint64)torrent->upload_speed_) + "/s" : "Paused");
table.push_back(std::move(row));
}
print_table(table, {2});
}
};
} // namespace ton
int main(int argc, char* argv[]) {
SET_VERBOSITY_LEVEL(verbosity_INFO);
td::set_default_failure_signal_handler();
td::IPAddress ip_addr;
ton::PrivateKey client_private_key;
ton::PublicKey server_public_key;
std::vector commands;
td::OptionParser p;
p.set_description("command-line interface for storage-daemon");
p.add_option('h', "help", "prints_help", [&]() {
std::cout << (PSLICE() << p).c_str();
std::exit(2);
});
p.add_checked_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
auto verbosity = td::to_integer(arg);
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
return (verbosity >= 0 && verbosity <= 20) ? td::Status::OK() : td::Status::Error("verbosity must be 0..20");
});
p.add_option('V', "version", "shows storage-daemon-cli build information", [&]() {
std::cout << "storage-daemon-cli build information: [ Commit: " << GitMetadata::CommitSHA1()
<< ", Date: " << GitMetadata::CommitDate() << "]\n";
std::exit(0);
});
p.add_checked_option('I', "ip", "set ip:port of storage-daemon", [&](td::Slice arg) {
TRY_STATUS(ip_addr.init_host_port(arg.str()));
return td::Status::OK();
});
p.add_option('c', "cmd", "execute command", [&](td::Slice arg) { commands.push_back(arg.str()); });
p.add_checked_option('k', "key", "private key", [&](td::Slice arg) {
TRY_RESULT_PREFIX(data, td::read_file(arg.str()), "failed to read: ");
TRY_RESULT_ASSIGN(client_private_key, ton::PrivateKey::import(data));
return td::Status::OK();
});
p.add_checked_option('p', "pub", "server public key", [&](td::Slice arg) {
TRY_RESULT_PREFIX(data, td::read_file(arg.str()), "failed to read: ");
TRY_RESULT_ASSIGN(server_public_key, ton::PublicKey::import(data));
return td::Status::OK();
});
auto S = p.run(argc, argv);
if (S.is_error()) {
std::cerr << S.move_as_error().message().str() << std::endl;
std::_Exit(2);
}
LOG_IF(FATAL, client_private_key.empty()) << "Client private key is not set";
LOG_IF(FATAL, server_public_key.empty()) << "Server public key is not set";
td::actor::Scheduler scheduler({0});
scheduler.run_in_context([&] {
td::actor::create_actor("console", ip_addr, client_private_key, server_public_key,
std::move(commands))
.release();
});
scheduler.run();
return 0;
}