commits
tags
#include "torrent.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ---- small cJSON accessor helpers ---------------------------------- */
static int64_t get_i64(const cJSON *obj, const char *key, int64_t def)
{
const cJSON *v = cJSON_GetObjectItemCaseSensitive(obj, key);
if (!v || !cJSON_IsNumber(v))
return def;
return (int64_t)v->valuedouble;
}
static int get_int(const cJSON *obj, const char *key, int def)
{
return (int)get_i64(obj, key, def);
}
static double get_dbl(const cJSON *obj, const char *key, double def)
{
const cJSON *v = cJSON_GetObjectItemCaseSensitive(obj, key);
if (!v || !cJSON_IsNumber(v))
return def;
return v->valuedouble;
}
static void get_str(const cJSON *obj, const char *key, char *buf, size_t n)
{
const cJSON *v = cJSON_GetObjectItemCaseSensitive(obj, key);
if (v && cJSON_IsString(v))
snprintf(buf, n, "%s", v->valuestring);
else
buf[0] = '\0';
}
static cJSON *field_array(const char **names, size_t n)
{
cJSON *arr = cJSON_CreateArray();
for (size_t i = 0; i < n; i++)
cJSON_AddItemToArray(arr, cJSON_CreateString(names[i]));
return arr;
}
static cJSON *id_array(const int *ids, size_t n)
{
cJSON *arr = cJSON_CreateArray();
for (size_t i = 0; i < n; i++)
cJSON_AddItemToArray(arr, cJSON_CreateNumber(ids[i]));
return arr;
}
static void parse_summary(const cJSON *t, Torrent *out)
{
memset(out, 0, sizeof(*out));
out->id = get_int(t, "id", -1);
get_str(t, "hashString", out->hash, sizeof(out->hash));
get_str(t, "name", out->name, sizeof(out->name));
out->total_size = get_i64(t, "totalSize", 0);
out->left_until_done = get_i64(t, "leftUntilDone", 0);
out->percent_done = get_dbl(t, "percentDone", 0.0);
out->status = get_int(t, "status", TR_STATUS_STOPPED);
out->rate_download = get_i64(t, "rateDownload", 0);
out->rate_upload = get_i64(t, "rateUpload", 0);
out->eta = get_int(t, "eta", -1);
out->upload_ratio = get_dbl(t, "uploadRatio", 0.0);
out->error = get_int(t, "error", 0);
get_str(t, "errorString", out->error_string, sizeof(out->error_string));
get_str(t, "downloadDir", out->download_dir, sizeof(out->download_dir));
out->peers_connected = get_int(t, "peersConnected", 0);
out->peers_sending_to_us = get_int(t, "peersSendingToUs", 0);
out->peers_getting_from_us = get_int(t, "peersGettingFromUs", 0);
}
static const char *SUMMARY_FIELDS[] = {
"id", "name", "hashString", "totalSize", "leftUntilDone", "percentDone",
"status", "rateDownload", "rateUpload", "eta", "uploadRatio", "error",
"errorString", "downloadDir", "peersConnected", "peersSendingToUs",
"peersGettingFromUs",
};
void torrent_list_init(TorrentList *list)
{
memset(list, 0, sizeof(*list));
}
void torrent_list_free(TorrentList *list)
{
free(list->items);
memset(list, 0, sizeof(*list));
}
int torrent_list_refresh(RpcClient *rpc, TorrentList *list, char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
cJSON_AddItemToObject(args, "fields",
field_array(SUMMARY_FIELDS, sizeof(SUMMARY_FIELDS) / sizeof(SUMMARY_FIELDS[0])));
cJSON *out = NULL;
if (rpc_call(rpc, "torrent-get", args, &out, err, errlen) != 0)
return -1;
cJSON *torrents = out ? cJSON_GetObjectItemCaseSensitive(out, "torrents") : NULL;
if (!torrents || !cJSON_IsArray(torrents)) {
snprintf(err, errlen, "unexpected response from torrent-get");
cJSON_Delete(out);
return -1;
}
/* Remember which torrent ids were selected so we can restore the flag
* after repopulating the array (list order can shift between polls). */
size_t old_selected_count = 0;
for (size_t i = 0; i < list->count; i++)
if (list->items[i].selected)
old_selected_count++;
int *old_selected_ids = NULL;
if (old_selected_count) {
old_selected_ids = malloc(old_selected_count * sizeof(int));
size_t j = 0;
for (size_t i = 0; i < list->count; i++)
if (list->items[i].selected)
old_selected_ids[j++] = list->items[i].id;
}
int count = cJSON_GetArraySize(torrents);
if ((size_t)count > list->capacity) {
size_t ncap = (size_t)count;
Torrent *ni = realloc(list->items, ncap * sizeof(Torrent));
if (!ni) {
snprintf(err, errlen, "out of memory");
free(old_selected_ids);
cJSON_Delete(out);
return -1;
}
list->items = ni;
list->capacity = ncap;
}
for (int i = 0; i < count; i++)
parse_summary(cJSON_GetArrayItem(torrents, i), &list->items[i]);
list->count = (size_t)count;
for (size_t s = 0; s < old_selected_count; s++) {
for (size_t i = 0; i < list->count; i++) {
if (list->items[i].id == old_selected_ids[s]) {
list->items[i].selected = 1;
break;
}
}
}
free(old_selected_ids);
cJSON_Delete(out);
return 0;
}
void torrent_detail_free(TorrentDetail *d)
{
free(d->files);
free(d->peers);
free(d->trackers);
memset(d, 0, sizeof(*d));
}
int torrent_get_detail(RpcClient *rpc, int id, TorrentDetail *out, char *err, size_t errlen)
{
static const char *fields[] = {
"id", "name", "hashString", "totalSize", "leftUntilDone", "percentDone",
"status", "rateDownload", "rateUpload", "eta", "uploadRatio", "error",
"errorString", "downloadDir", "peersConnected", "peersSendingToUs",
"peersGettingFromUs", "dateAdded", "dateCreated", "doneDate",
"downloadLimit", "downloadLimited", "uploadLimit", "uploadLimited",
"files", "fileStats", "peers", "trackerStats",
};
cJSON *args = cJSON_CreateObject();
int idarr[1] = {id};
cJSON_AddItemToObject(args, "ids", id_array(idarr, 1));
cJSON_AddItemToObject(args, "fields", field_array(fields, sizeof(fields) / sizeof(fields[0])));
cJSON *resp = NULL;
if (rpc_call(rpc, "torrent-get", args, &resp, err, errlen) != 0)
return -1;
cJSON *torrents = resp ? cJSON_GetObjectItemCaseSensitive(resp, "torrents") : NULL;
if (!torrents || !cJSON_IsArray(torrents) || cJSON_GetArraySize(torrents) < 1) {
snprintf(err, errlen, "torrent not found");
cJSON_Delete(resp);
return -1;
}
cJSON *t = cJSON_GetArrayItem(torrents, 0);
memset(out, 0, sizeof(*out));
parse_summary(t, &out->info);
out->date_added = (long)get_i64(t, "dateAdded", 0);
out->date_created = (long)get_i64(t, "dateCreated", 0);
out->done_date = (long)get_i64(t, "doneDate", 0);
out->download_limit = get_i64(t, "downloadLimit", 0);
out->download_limited = get_int(t, "downloadLimited", 0);
out->upload_limit = get_i64(t, "uploadLimit", 0);
out->upload_limited = get_int(t, "uploadLimited", 0);
cJSON *files = cJSON_GetObjectItemCaseSensitive(t, "files");
cJSON *filestats = cJSON_GetObjectItemCaseSensitive(t, "fileStats");
if (files && cJSON_IsArray(files)) {
int n = cJSON_GetArraySize(files);
out->files = calloc((size_t)n, sizeof(TorrentFile));
out->file_count = (size_t)n;
for (int i = 0; i < n; i++) {
cJSON *f = cJSON_GetArrayItem(files, i);
TorrentFile *tf = &out->files[i];
get_str(f, "name", tf->name, sizeof(tf->name));
tf->length = get_i64(f, "length", 0);
tf->bytes_completed = get_i64(f, "bytesCompleted", 0);
tf->wanted = 1;
tf->priority = 0;
if (filestats && cJSON_IsArray(filestats) && i < cJSON_GetArraySize(filestats)) {
cJSON *fs = cJSON_GetArrayItem(filestats, i);
tf->wanted = get_int(fs, "wanted", 1);
tf->priority = get_int(fs, "priority", 0);
}
}
}
cJSON *peers = cJSON_GetObjectItemCaseSensitive(t, "peers");
if (peers && cJSON_IsArray(peers)) {
int n = cJSON_GetArraySize(peers);
out->peers = calloc((size_t)n, sizeof(TorrentPeer));
out->peer_count = (size_t)n;
for (int i = 0; i < n; i++) {
cJSON *p = cJSON_GetArrayItem(peers, i);
TorrentPeer *tp = &out->peers[i];
get_str(p, "address", tp->address, sizeof(tp->address));
get_str(p, "clientName", tp->client_name, sizeof(tp->client_name));
get_str(p, "flagStr", tp->flags, sizeof(tp->flags));
tp->progress = get_dbl(p, "progress", 0.0);
tp->rate_to_client = get_i64(p, "rateToClient", 0);
tp->rate_to_peer = get_i64(p, "rateToPeer", 0);
}
}
cJSON *trackers = cJSON_GetObjectItemCaseSensitive(t, "trackerStats");
if (trackers && cJSON_IsArray(trackers)) {
int n = cJSON_GetArraySize(trackers);
out->trackers = calloc((size_t)n, sizeof(TorrentTracker));
out->tracker_count = (size_t)n;
for (int i = 0; i < n; i++) {
cJSON *tr = cJSON_GetArrayItem(trackers, i);
TorrentTracker *tt = &out->trackers[i];
get_str(tr, "announce", tt->announce, sizeof(tt->announce));
tt->tier = get_int(tr, "tier", 0);
get_str(tr, "lastAnnounceResult", tt->last_announce_result, sizeof(tt->last_announce_result));
tt->seeder_count = get_int(tr, "seederCount", -1);
tt->leecher_count = get_int(tr, "leecherCount", -1);
}
}
cJSON_Delete(resp);
return 0;
}
int torrent_action(RpcClient *rpc, const char *method, const int *ids, size_t n, char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
cJSON_AddItemToObject(args, "ids", id_array(ids, n));
return rpc_call(rpc, method, args, NULL, err, errlen);
}
int torrent_remove(RpcClient *rpc, const int *ids, size_t n, int delete_data, char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
cJSON_AddItemToObject(args, "ids", id_array(ids, n));
cJSON_AddBoolToObject(args, "delete-local-data", delete_data);
return rpc_call(rpc, "torrent-remove", args, NULL, err, errlen);
}
int torrent_add(RpcClient *rpc, const char *source, const char *download_dir, char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
if (str_starts_with(source, "magnet:") || str_starts_with(source, "http://") ||
str_starts_with(source, "https://")) {
cJSON_AddStringToObject(args, "filename", source);
} else {
size_t flen = 0;
unsigned char *data = read_file_all(source, &flen);
if (!data) {
snprintf(err, errlen, "could not read file: %s", source);
cJSON_Delete(args);
return -1;
}
size_t b64size = base64_encoded_size(flen) + 1;
char *b64 = malloc(b64size);
if (!b64 || base64_encode(data, flen, b64, b64size) < 0) {
snprintf(err, errlen, "could not base64-encode the file");
free(data);
free(b64);
cJSON_Delete(args);
return -1;
}
free(data);
cJSON_AddStringToObject(args, "metainfo", b64);
free(b64);
}
if (download_dir && download_dir[0])
cJSON_AddStringToObject(args, "download-dir", download_dir);
return rpc_call(rpc, "torrent-add", args, NULL, err, errlen);
}
int torrent_set_speed_limit(RpcClient *rpc, const int *ids, size_t n,
int64_t down_kbps, int down_enabled,
int64_t up_kbps, int up_enabled,
char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
cJSON_AddItemToObject(args, "ids", id_array(ids, n));
cJSON_AddNumberToObject(args, "downloadLimit", (double)down_kbps);
cJSON_AddBoolToObject(args, "downloadLimited", down_enabled);
cJSON_AddNumberToObject(args, "uploadLimit", (double)up_kbps);
cJSON_AddBoolToObject(args, "uploadLimited", up_enabled);
return rpc_call(rpc, "torrent-set", args, NULL, err, errlen);
}
int torrent_set_file_wanted(RpcClient *rpc, int id, const int *file_indices, size_t n, int wanted,
char *err, size_t errlen)
{
cJSON *args = cJSON_CreateObject();
int idarr[1] = {id};
cJSON_AddItemToObject(args, "ids", id_array(idarr, 1));
cJSON_AddItemToObject(args, wanted ? "files-wanted" : "files-unwanted", id_array(file_indices, n));
return rpc_call(rpc, "torrent-set", args, NULL, err, errlen);
}
int torrent_set_file_priority(RpcClient *rpc, int id, const int *file_indices, size_t n, int priority,
char *err, size_t errlen)
{
const char *key = priority > 0 ? "priority-high" : (priority < 0 ? "priority-low" : "priority-normal");
cJSON *args = cJSON_CreateObject();
int idarr[1] = {id};
cJSON_AddItemToObject(args, "ids", id_array(idarr, 1));
cJSON_AddItemToObject(args, key, id_array(file_indices, n));
return rpc_call(rpc, "torrent-set", args, NULL, err, errlen);
}