2025-12-05 17:08:50 +01:00
|
|
|
#ifndef TIKKER_STATS_H
|
|
|
|
|
#define TIKKER_STATS_H
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2025-12-05 18:09:44 +01:00
|
|
|
#include <dirent.h>
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <sys/stat.h>
|
2025-12-05 17:08:50 +01:00
|
|
|
#include "sormc.h"
|
|
|
|
|
#include "tikker_types.h"
|
|
|
|
|
#include "tikker_db.h"
|
|
|
|
|
#include "tikker_decode.h"
|
|
|
|
|
#include "tikker_words.h"
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_presses_today(int db) {
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT pressed FROM kevent_daily WHERE date = DATE('now')");
|
|
|
|
|
if (result) {
|
|
|
|
|
char *csv = (char *)result;
|
|
|
|
|
char *line = csv;
|
|
|
|
|
char *next = strchr(line, '\n');
|
|
|
|
|
if (next) line = next + 1;
|
|
|
|
|
char *end = strchr(line, ';');
|
|
|
|
|
if (end) *end = '\0';
|
|
|
|
|
end = strchr(line, '\n');
|
|
|
|
|
if (end) *end = '\0';
|
|
|
|
|
printf("%s\n", line);
|
|
|
|
|
free(result);
|
|
|
|
|
} else {
|
|
|
|
|
printf("0\n");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_daily(int db) {
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT date, pressed FROM kevent_daily ORDER BY date");
|
|
|
|
|
|
|
|
|
|
printf("date,count\n");
|
|
|
|
|
|
|
|
|
|
if (!result) return 0;
|
|
|
|
|
|
|
|
|
|
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
|
|
|
|
|
char *date, *count;
|
|
|
|
|
while (tikker_csv_iter_next(&iter, &date, &count)) {
|
|
|
|
|
if (date && count) {
|
|
|
|
|
printf("%s,%s\n", date, count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(result);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_hourly(int db, const char *date) {
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT SUBSTR(date_hour, 12, 2) as hour, pressed "
|
|
|
|
|
"FROM kevent_hourly WHERE date_hour LIKE %s || '.%%' "
|
|
|
|
|
"ORDER BY hour", date);
|
|
|
|
|
|
|
|
|
|
printf("hour,count\n");
|
|
|
|
|
|
|
|
|
|
int hour_counts[24] = {0};
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
|
|
|
|
|
char *hour_str, *count_str;
|
|
|
|
|
while (tikker_csv_iter_next(&iter, &hour_str, &count_str)) {
|
|
|
|
|
if (hour_str && count_str) {
|
|
|
|
|
int hour = atoi(hour_str);
|
|
|
|
|
if (hour >= 0 && hour < 24) {
|
|
|
|
|
hour_counts[hour] = atoi(count_str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int h = 0; h < 24; h++) {
|
|
|
|
|
printf("%02d,%d\n", h, hour_counts[h]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_weekly(int db) {
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT STRFTIME('%%Y-%%U', date) as week, SUM(pressed) as count "
|
|
|
|
|
"FROM kevent_daily GROUP BY week ORDER BY week");
|
|
|
|
|
|
|
|
|
|
printf("week,count\n");
|
|
|
|
|
|
|
|
|
|
if (!result) return 0;
|
|
|
|
|
|
|
|
|
|
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
|
|
|
|
|
char *week, *count;
|
|
|
|
|
while (tikker_csv_iter_next(&iter, &week, &count)) {
|
|
|
|
|
if (week && count) {
|
|
|
|
|
printf("%s,%s\n", week, count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(result);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_weekday(int db) {
|
|
|
|
|
static const char *weekday_names[] = {
|
|
|
|
|
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
|
|
|
"Thursday", "Friday", "Saturday"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT STRFTIME('%%w', date) as weekday, SUM(pressed) as count "
|
|
|
|
|
"FROM kevent_daily GROUP BY weekday ORDER BY weekday");
|
|
|
|
|
|
|
|
|
|
printf("weekday,name,count\n");
|
|
|
|
|
|
|
|
|
|
int weekday_counts[7] = {0};
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
|
|
|
|
|
char *wday_str, *count_str;
|
|
|
|
|
while (tikker_csv_iter_next(&iter, &wday_str, &count_str)) {
|
|
|
|
|
if (wday_str && count_str) {
|
|
|
|
|
int wday = atoi(wday_str);
|
|
|
|
|
if (wday >= 0 && wday < 7) {
|
|
|
|
|
weekday_counts[wday] = atoi(count_str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int d = 0; d < 7; d++) {
|
|
|
|
|
printf("%d,%s,%d\n", d, weekday_names[d], weekday_counts[d]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_top_keys(int db, int limit) {
|
|
|
|
|
char sql[512];
|
|
|
|
|
snprintf(sql, sizeof(sql),
|
|
|
|
|
"SELECT code, count FROM kevent_key_counts "
|
|
|
|
|
"ORDER BY count DESC LIMIT %d", limit);
|
|
|
|
|
|
|
|
|
|
sorm_ptr result = sormq(db, sql);
|
|
|
|
|
|
|
|
|
|
printf("code,name,count\n");
|
|
|
|
|
|
|
|
|
|
if (!result) return 0;
|
|
|
|
|
|
|
|
|
|
tikker_csv_iter_t iter = tikker_csv_iter_init((char *)result);
|
|
|
|
|
char *code_str, *count_str;
|
|
|
|
|
while (tikker_csv_iter_next(&iter, &code_str, &count_str)) {
|
|
|
|
|
if (code_str && count_str) {
|
|
|
|
|
int code = atoi(code_str);
|
|
|
|
|
const char *name = "UNKNOWN";
|
|
|
|
|
if (code < (int)(sizeof(tikker_keycode_to_name) / sizeof(tikker_keycode_to_name[0]))
|
|
|
|
|
&& tikker_keycode_to_name[code]) {
|
|
|
|
|
name = tikker_keycode_to_name[code];
|
|
|
|
|
}
|
|
|
|
|
printf("%s,%s,%s\n", code_str, name, count_str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(result);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_summary(int db) {
|
|
|
|
|
sorm_ptr result = sormq(db,
|
|
|
|
|
"SELECT "
|
|
|
|
|
"SUM(pressed), SUM(released), SUM(repeated), "
|
|
|
|
|
"MIN(date), MAX(date), COUNT(*) "
|
|
|
|
|
"FROM kevent_daily");
|
|
|
|
|
|
|
|
|
|
printf("total_pressed,total_released,total_repeated,first_event,last_event,days\n");
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
printf("0,0,0,N/A,N/A,0\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *csv = (char *)result;
|
|
|
|
|
char *line = csv;
|
|
|
|
|
char *next = strchr(line, '\n');
|
|
|
|
|
if (next) {
|
|
|
|
|
line = next + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char values[6][64] = {"0", "0", "0", "N/A", "N/A", "0"};
|
|
|
|
|
int idx = 0;
|
|
|
|
|
char *start = line;
|
|
|
|
|
|
|
|
|
|
while (*start && idx < 6) {
|
|
|
|
|
char *end = strchr(start, ';');
|
|
|
|
|
if (!end) end = start + strlen(start);
|
|
|
|
|
|
|
|
|
|
size_t len = end - start;
|
|
|
|
|
if (len >= sizeof(values[0])) len = sizeof(values[0]) - 1;
|
|
|
|
|
strncpy(values[idx], start, len);
|
|
|
|
|
values[idx][len] = '\0';
|
|
|
|
|
|
|
|
|
|
idx++;
|
|
|
|
|
if (*end) start = end + 1;
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("%s,%s,%s,%s,%s,%s\n", values[0], values[1], values[2], values[3], values[4], values[5]);
|
|
|
|
|
|
|
|
|
|
free(result);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
static inline int tikker_export_logs(int db) {
|
|
|
|
|
struct stat st = {0};
|
|
|
|
|
if (stat("logs_plain", &st) == -1) {
|
|
|
|
|
mkdir("logs_plain", 0755);
|
|
|
|
|
}
|
2025-12-05 17:08:50 +01:00
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
sorm_ptr hours_result = sormq(db,
|
|
|
|
|
"SELECT DISTINCT STRFTIME('%%Y-%%m-%%d.%%H', timestamp) as date_hour "
|
|
|
|
|
"FROM kevent WHERE event = 'PRESSED' ORDER BY date_hour");
|
2025-12-05 17:08:50 +01:00
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
if (!hours_result) {
|
|
|
|
|
printf("No data to export\n");
|
|
|
|
|
return 0;
|
2025-12-05 17:08:50 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
int files_written = 0;
|
|
|
|
|
char *csv = (char *)hours_result;
|
2025-12-05 17:08:50 +01:00
|
|
|
char *line = csv;
|
|
|
|
|
char *next;
|
|
|
|
|
|
|
|
|
|
while (line && *line) {
|
|
|
|
|
next = strchr(line, '\n');
|
|
|
|
|
if (next) *next = '\0';
|
|
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
if (tikker_csv_is_metadata(line) || strlen(line) < 10) {
|
|
|
|
|
if (next) line = next + 1;
|
|
|
|
|
else break;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *end = strchr(line, ';');
|
|
|
|
|
if (end) *end = '\0';
|
2025-12-05 17:08:50 +01:00
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
char date_hour[32];
|
|
|
|
|
strncpy(date_hour, line, sizeof(date_hour) - 1);
|
|
|
|
|
date_hour[sizeof(date_hour) - 1] = '\0';
|
|
|
|
|
|
|
|
|
|
char date_hour_space[32];
|
|
|
|
|
strncpy(date_hour_space, date_hour, sizeof(date_hour_space) - 1);
|
|
|
|
|
date_hour_space[sizeof(date_hour_space) - 1] = '\0';
|
|
|
|
|
char *dot = strchr(date_hour_space, '.');
|
|
|
|
|
if (dot) *dot = ' ';
|
|
|
|
|
|
|
|
|
|
char start_ts[32], end_ts[32];
|
|
|
|
|
snprintf(start_ts, sizeof(start_ts), "%s:00:00", date_hour_space);
|
|
|
|
|
snprintf(end_ts, sizeof(end_ts), "%s:59:59", date_hour_space);
|
|
|
|
|
|
|
|
|
|
char sql[512];
|
|
|
|
|
snprintf(sql, sizeof(sql),
|
|
|
|
|
"SELECT GROUP_CONCAT(char, '') FROM kevent "
|
|
|
|
|
"WHERE event = 'PRESSED' AND timestamp >= '%s' AND timestamp <= '%s'",
|
|
|
|
|
start_ts, end_ts);
|
|
|
|
|
|
|
|
|
|
sorm_ptr chars_result = sormq(db, sql);
|
|
|
|
|
if (chars_result) {
|
|
|
|
|
char *chars_csv = (char *)chars_result;
|
|
|
|
|
char *chars_line = strchr(chars_csv, '\n');
|
|
|
|
|
if (chars_line) chars_line++;
|
|
|
|
|
else chars_line = chars_csv;
|
|
|
|
|
|
|
|
|
|
char *chars_end = strchr(chars_line, ';');
|
|
|
|
|
if (chars_end) *chars_end = '\0';
|
|
|
|
|
chars_end = strchr(chars_line, '\n');
|
|
|
|
|
if (chars_end) *chars_end = '\0';
|
|
|
|
|
|
|
|
|
|
char filepath[256];
|
|
|
|
|
snprintf(filepath, sizeof(filepath), "logs_plain/%s.txt", date_hour);
|
|
|
|
|
|
|
|
|
|
FILE *f = fopen(filepath, "w");
|
|
|
|
|
if (f) {
|
|
|
|
|
fprintf(f, "**%s:00**: ```%s```\n", date_hour_space, chars_line);
|
|
|
|
|
fclose(f);
|
|
|
|
|
files_written++;
|
|
|
|
|
if (files_written % 100 == 0) {
|
|
|
|
|
printf("Exported %d files...\r", files_written);
|
|
|
|
|
fflush(stdout);
|
|
|
|
|
}
|
2025-12-05 17:08:50 +01:00
|
|
|
}
|
2025-12-05 18:09:44 +01:00
|
|
|
free(chars_result);
|
2025-12-05 17:08:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (next) line = next + 1;
|
|
|
|
|
else break;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
free(hours_result);
|
|
|
|
|
printf("Exported %d hourly log files to logs_plain/\n", files_written);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline int tikker_stats_top_words(int db, int limit) {
|
|
|
|
|
(void)db;
|
|
|
|
|
|
|
|
|
|
printf("word,count\n");
|
|
|
|
|
|
|
|
|
|
tikker_word_hash_t *hash = tikker_hash_create();
|
|
|
|
|
if (!hash) return 1;
|
|
|
|
|
|
|
|
|
|
DIR *dir = opendir("logs_plain");
|
|
|
|
|
if (!dir) {
|
|
|
|
|
fprintf(stderr, "Error: Cannot open logs_plain directory\n");
|
|
|
|
|
tikker_hash_free(hash);
|
2025-12-05 17:08:50 +01:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-05 18:09:44 +01:00
|
|
|
struct dirent *entry;
|
|
|
|
|
char filepath[512];
|
|
|
|
|
char buffer[65536];
|
|
|
|
|
|
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
|
|
|
if (entry->d_name[0] == '.') continue;
|
|
|
|
|
|
|
|
|
|
snprintf(filepath, sizeof(filepath), "logs_plain/%s", entry->d_name);
|
|
|
|
|
FILE *f = fopen(filepath, "r");
|
|
|
|
|
if (!f) continue;
|
|
|
|
|
|
|
|
|
|
while (fgets(buffer, sizeof(buffer), f)) {
|
|
|
|
|
char *p = buffer;
|
|
|
|
|
char word[TIKKER_MAX_WORD_LEN];
|
|
|
|
|
int word_len = 0;
|
|
|
|
|
|
|
|
|
|
while (*p) {
|
|
|
|
|
if (tikker_is_valid_word_char(*p)) {
|
|
|
|
|
if (word_len < TIKKER_MAX_WORD_LEN - 1) {
|
|
|
|
|
word[word_len++] = toupper((unsigned char)*p);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (word_len >= 2) {
|
|
|
|
|
word[word_len] = '\0';
|
|
|
|
|
tikker_hash_insert(hash, word);
|
|
|
|
|
}
|
|
|
|
|
word_len = 0;
|
|
|
|
|
}
|
|
|
|
|
p++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fclose(f);
|
|
|
|
|
}
|
|
|
|
|
closedir(dir);
|
|
|
|
|
|
|
|
|
|
int word_count;
|
|
|
|
|
tikker_word_count_t *words = tikker_hash_to_array(hash, &word_count);
|
|
|
|
|
tikker_hash_free(hash);
|
|
|
|
|
|
2025-12-05 17:08:50 +01:00
|
|
|
tikker_sort_words(words, word_count);
|
|
|
|
|
|
|
|
|
|
int output_count = (limit < word_count) ? limit : word_count;
|
|
|
|
|
for (int i = 0; i < output_count; i++) {
|
|
|
|
|
printf("%s,%d\n", words[i].word, words[i].count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(words);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|