// velox — the command-line client for veloxd. // // velox add [--dir D] [--out NAME] [--segments N] [--json] // velox ls [--json] // velox pause ... velox resume ... // velox rm ... [--delete-file] // // Exit codes: 0 ok, 1 daemon returned an error, 2 usage error, 3 cannot reach the daemon. #include #include #include #include #include #include #include "client.hpp" namespace { using nlohmann::json; using velox::cli::CallError; using velox::cli::Client; constexpr int kOk = 0; constexpr int kRpcError = 1; constexpr int kUsage = 2; constexpr int kNoDaemon = 3; struct Args { std::vector positional; bool json = false; bool delete_file = false; std::string dir; std::string out; long segments = 0; }; [[noreturn]] void usage(int code) { std::fprintf(code == kOk ? stdout : stderr, "usage: velox [options]\n\n" " add [--dir DIR] [--out NAME] [--segments N]\n" " ls\n" " pause ...\n" " resume ...\n" " rm ... [--delete-file]\n\n" " --json print the raw JSON-RPC result\n"); std::exit(code); } Args parse_args(int argc, char** argv) { Args a; for (int i = 2; i < argc; ++i) { const std::string_view arg = argv[i]; if (arg == "--json") { a.json = true; } else if (arg == "--delete-file") { a.delete_file = true; } else if (arg == "--dir" && i + 1 < argc) { a.dir = argv[++i]; } else if (arg == "--out" && i + 1 < argc) { a.out = argv[++i]; } else if (arg == "--segments" && i + 1 < argc) { a.segments = std::strtol(argv[++i], nullptr, 10); } else if (arg == "-h" || arg == "--help") { usage(kOk); } else if (!arg.empty() && arg.front() == '-') { std::fprintf(stderr, "velox: unknown option %s\n", argv[i]); usage(kUsage); } else { a.positional.emplace_back(arg); } } return a; } int report_error(const CallError& e, bool as_json) { if (as_json) { json j = {{"error", {{"code", e.code}, {"message", e.message}}}}; if (!e.data.is_null()) j["error"]["data"] = e.data; std::printf("%s\n", j.dump(2).c_str()); } else { std::fprintf(stderr, "velox: %s\n", e.message.c_str()); } return e.code == CallError::kConnect ? kNoDaemon : kRpcError; } void print_task_table(const json& items) { std::printf("%-38s %-10s %-9s %s\n", "ID", "STATE", "PROGRESS", "NAME"); for (const auto& t : items) { const std::string id = t.value("taskId", ""); const std::string state = t.value("state", ""); const std::string name = t.value("filename", ""); const long long total = t.value("sizeBytes", 0LL); const long long done = t.value("downloadedBytes", 0LL); char pct[12] = "-"; if (total > 0) std::snprintf(pct, sizeof(pct), "%lld%%", done * 100 / total); std::printf("%-38s %-10s %-9s %s\n", id.c_str(), state.c_str(), pct, name.c_str()); } } int cmd_ls(Client& c, const Args& a) { auto r = c.call("download.list", json::object()); if (!r) return report_error(r.error(), a.json); if (a.json) { std::printf("%s\n", r->dump(2).c_str()); return kOk; } const auto& items = r->contains("items") ? r->at("items") : json::array(); if (items.empty()) { std::printf("no downloads\n"); return kOk; } print_task_table(items); return kOk; } int cmd_add(Client& c, const Args& a) { if (a.positional.empty()) { std::fprintf(stderr, "velox add: a URL is required\n"); return kUsage; } json params = {{"url", a.positional.front()}}; if (!a.dir.empty()) params["saveDir"] = a.dir; if (!a.out.empty()) params["filename"] = a.out; if (a.segments > 0) params["segments"] = a.segments; auto r = c.call("download.add", params); if (!r) return report_error(r.error(), a.json); if (a.json) { std::printf("%s\n", r->dump(2).c_str()); } else { std::printf("added %s\n", r->value("taskId", "?").c_str()); } return kOk; } int cmd_bulk(Client& c, const Args& a, const char* method) { if (a.positional.empty()) { std::fprintf(stderr, "velox: at least one task id is required\n"); return kUsage; } json params = {{"taskIds", a.positional}}; if (std::string_view(method) == "download.remove" && a.delete_file) params["deleteFile"] = true; auto r = c.call(method, params); if (!r) return report_error(r.error(), a.json); if (a.json) { std::printf("%s\n", r->dump(2).c_str()); } else { std::printf("ok\n"); } return kOk; } } // namespace int main(int argc, char** argv) { if (argc < 2) usage(kUsage); const std::string command = argv[1]; if (command == "-h" || command == "--help") usage(kOk); const Args args = parse_args(argc, argv); Client client; if (const auto err = client.connect()) { return report_error(*err, args.json); } if (command == "ls") return cmd_ls(client, args); if (command == "add") return cmd_add(client, args); if (command == "pause") return cmd_bulk(client, args, "download.pause"); if (command == "resume") return cmd_bulk(client, args, "download.resume"); if (command == "rm") return cmd_bulk(client, args, "download.remove"); std::fprintf(stderr, "velox: unknown command '%s'\n", command.c_str()); usage(kUsage); }