|
|
|
|
@ -159,9 +159,10 @@ int chat_feedback_snapshot(struct UTUN_INSTANCE* inst, const char* channel, cons
|
|
|
|
|
poll.mode == CHAT_POLL_SINGLE ? "single" : "multiple", poll.allow_custom_answer ? "true" : "false") < 0 |
|
|
|
|
|| strbuf_add_json_string(&out, poll.question) < 0 || strbuf_addf(&out, ",\"options\":[") < 0) goto done; |
|
|
|
|
for (unsigned i = 0; i < poll.option_count; i++) { |
|
|
|
|
unsigned percent = voters ? (unsigned)((10000ULL * votes[i] + voters / 2) / voters) : 0; |
|
|
|
|
if (strbuf_addf(&out, "%s{\"id\":%u,\"text\":", i ? "," : "", i) < 0 |
|
|
|
|
|| strbuf_add_json_string(&out, poll.options[i]) < 0 |
|
|
|
|
|| strbuf_addf(&out, ",\"count\":%u,\"percent\":%.2f}", votes[i], voters ? 100.0 * votes[i] / voters : 0.0) < 0) goto done; |
|
|
|
|
|| strbuf_addf(&out, ",\"count\":%u,\"percent\":%u.%02u}", votes[i], percent / 100, percent % 100) < 0) goto done; |
|
|
|
|
} |
|
|
|
|
if (strbuf_addf(&out, "],\"voters\":%u,\"custom_count\":%u,\"ver\":%llu,\"mine\":", |
|
|
|
|
voters, custom_count, (unsigned long long)answer_ver) < 0 |
|
|
|
|
@ -179,10 +180,29 @@ done:
|
|
|
|
|
|
|
|
|
|
/* Сохраняем диагностику семантики при новом состоянии/получении исходного сообщения. */ |
|
|
|
|
void chat_feedback_check_message(struct UTUN_INSTANCE* inst, const char* channel, const uint8_t message_id[32]) { |
|
|
|
|
char* snapshot = NULL; |
|
|
|
|
if (chat_feedback_snapshot(inst, channel, message_id, &snapshot) == 0) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluated ch=%s state=%s", channel, snapshot); u_free(snapshot); |
|
|
|
|
struct chat_poll poll; int is_poll, target = poll_target(inst, channel, message_id, &poll, &is_poll); |
|
|
|
|
if (target < 0) return; |
|
|
|
|
if (target) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluation deferred ch=%s; source message missing", channel); return; |
|
|
|
|
} |
|
|
|
|
char sql[256]; snprintf(sql, sizeof(sql), |
|
|
|
|
"SELECT node_id,ver,data FROM feedback_%s WHERE message_id=? AND kind=2 AND length(data)>0", channel); |
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) { poll_db_error(inst, "evaluate query"); return; } |
|
|
|
|
sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC); int step; unsigned accepted = 0, rejected = 0; |
|
|
|
|
while ((step = sqlite3_step(st)) == SQLITE_ROW) { |
|
|
|
|
struct chat_poll_answer answer; |
|
|
|
|
if (chat_answer_decode(sqlite3_column_blob(st, 2), sqlite3_column_bytes(st, 2), &answer) < 0 |
|
|
|
|
|| !is_poll || !poll_answer_allowed(&poll, &answer)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "poll: answer excluded ch=%s node=%016llx ver=%llu poll=%d mode=%u options=%u custom=%u", |
|
|
|
|
channel, (unsigned long long)sqlite3_column_int64(st, 0), (unsigned long long)sqlite3_column_int64(st, 1), |
|
|
|
|
is_poll, is_poll ? poll.mode : 0, is_poll ? poll.option_count : 0, is_poll ? poll.allow_custom_answer : 0); |
|
|
|
|
rejected++; |
|
|
|
|
} else accepted++; |
|
|
|
|
} |
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
if (step != SQLITE_DONE) { poll_db_error(inst, "evaluate read"); return; } |
|
|
|
|
if (accepted || rejected) DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluated ch=%s valid=%u invalid=%u", channel, accepted, rejected); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Читает поле JSON с его типом. Отсутствующее поле имеет type=NULL. */ |
|
|
|
|
|