diff --git a/include/h.h b/include/h.h index faa5216c5..91464edec 100644 --- a/include/h.h +++ b/include/h.h @@ -944,8 +944,11 @@ extern void free_message_tags(MessageTag *m); extern time_t server_time_to_unix_time(const char *tbuf); extern int history_set_limit(char *object, int max_lines, long max_t); extern int history_add(char *object, MessageTag *mtags, char *line); -extern int history_request(Client *acptr, char *object, HistoryFilter *filter); +extern HistoryResult *history_request(char *object, HistoryFilter *filter); extern int history_destroy(char *object); +extern int can_receive_history(Client *client); +extern void history_send_result(Client *client, HistoryResult *r); +extern void free_history_result(HistoryResult *r); extern void special_delayed_unloading(void); extern int write_int64(FILE *fd, uint64_t t); extern int write_int32(FILE *fd, uint32_t t); diff --git a/include/modules.h b/include/modules.h index 30da89ee1..6d115de14 100644 --- a/include/modules.h +++ b/include/modules.h @@ -493,6 +493,22 @@ struct HistoryFilter { int last_seconds; }; +/** History log lines, used by HistoryResult among others */ +typedef struct HistoryLogLine HistoryLogLine; +struct HistoryLogLine { + HistoryLogLine *prev, *next; + time_t t; + MessageTag *mtags; + char line[1]; +}; + +typedef struct HistoryResult HistoryResult; +struct HistoryResult { + char *object; /**< Name of the history object, eg '#test' */ + HistoryLogLine *log; /**< The resulting log lines */ + HistoryLogLine *log_tail; /**< Last entry in the log lines */ +}; + /** History Backend */ typedef struct HistoryBackend HistoryBackend; struct HistoryBackend { @@ -500,7 +516,7 @@ struct HistoryBackend { char *name; /**< The name of the history backend (eg: "mem") */ int (*history_set_limit)(char *object, int max_lines, long max_time); /**< Impose a limit on a history object */ int (*history_add)(char *object, MessageTag *mtags, char *line); /**< Add to history */ - int (*history_request)(Client *acptr, char *object, HistoryFilter *filter); /**< Request history */ + HistoryResult *(*history_request)(char *object, HistoryFilter *filter); /**< Request history */ int (*history_destroy)(char *object); /**< Destroy history of this object completely */ Module *owner; /**< Module introducing this */ char unloaded; /**< Internal flag to indicate module is being unloaded */ @@ -513,7 +529,7 @@ typedef struct { char *name; int (*history_set_limit)(char *object, int max_lines, long max_time); int (*history_add)(char *object, MessageTag *mtags, char *line); - int (*history_request)(Client *acptr, char *object, HistoryFilter *filter); + HistoryResult *(*history_request)(char *object, HistoryFilter *filter); int (*history_destroy)(char *object); } HistoryBackendInfo; diff --git a/src/api-history-backend.c b/src/api-history-backend.c index f6eba608f..4d3bcd15b 100644 --- a/src/api-history-backend.c +++ b/src/api-history-backend.c @@ -166,14 +166,21 @@ int history_add(char *object, MessageTag *mtags, char *line) return 1; } -int history_request(Client *client, char *object, HistoryFilter *filter) +HistoryResult *history_request(char *object, HistoryFilter *filter) { - HistoryBackend *hb; + HistoryBackend *hb = historybackends; + HistoryResult *r; + HistoryLogLine *l; - for (hb = historybackends; hb; hb=hb->next) - hb->history_request(client, object, filter); + if (!hb) + return 0; /* no history backend loaded */ - return 1; + /* Right now we return whenever the first backend has a result. */ + for (hb = historybackends; hb; hb = hb->next) + if ((r = hb->history_request(object, filter))) + return r; + + return NULL; } int history_destroy(char *object) @@ -195,3 +202,74 @@ int history_set_limit(char *object, int max_lines, long max_t) return 1; } + +/** Free a HistoryResult object that was returned from request_result() earlier */ +void free_history_result(HistoryResult *r) +{ + HistoryLogLine *l, *l_next; + for (l = r->log; l; l = l_next) + { + l_next = l->next; + free_message_tags(l->mtags); + safe_free(l); + } + safe_free(r->object); + safe_free(r); +} + +/** Returns 1 if the client can receive channel history, 0 if not. + * @param client The client to check. + * @note It is recommend to call this function BEFORE trying to + * retrieve channel history via history_request(), + * as to not waste useless resources. + */ +int can_receive_history(Client *client) +{ + if (HasCapability(client, "server-time")) + return 1; + return 0; +} + +static void history_send_result_line(Client *client, HistoryLogLine *l, char *batchid) +{ + if (BadPtr(batchid)) + { + sendto_one(client, l->mtags, "%s", l->line); + } else { + MessageTag *m = safe_alloc(sizeof(MessageTag)); + m->name = "batch"; + m->value = batchid; + AddListItem(m, l->mtags); + sendto_one(client, l->mtags, "%s", l->line); + DelListItem(m, l->mtags); + safe_free(m); + } +} + +/** Send the result of a history_request() to the client. + * @param client The client to send to. + * @param r The history result retrieved via history_request(). + */ +void history_send_result(Client *client, HistoryResult *r) +{ + char batch[BATCHLEN+1]; + HistoryLogLine *l; + + if (!can_receive_history(client)) + return; + + batch[0] = '\0'; + if (HasCapability(client, "batch")) + { + /* Start a new batch */ + generate_batch_id(batch); + sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, r->object); + } + + for (l = r->log; l; l = l->next) + history_send_result_line(client, l, batch); + + /* End of batch */ + if (*batch) + sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch); +} diff --git a/src/modules/chanmodes/history.c b/src/modules/chanmodes/history.c index 747dcea69..bcf3a2e84 100644 --- a/src/modules/chanmodes/history.c +++ b/src/modules/chanmodes/history.c @@ -534,13 +534,19 @@ int history_join(Client *client, Channel *channel, MessageTag *mtags, char *parv if (!HistoryEnabled(channel)) return 0; - if (MyUser(client)) + if (MyUser(client) && can_receive_history(client)) { HistoryFilter filter; + HistoryResult *r; memset(&filter, 0, sizeof(filter)); filter.last_lines = cfg.playback_on_join.lines; filter.last_seconds = cfg.playback_on_join.time; - history_request(client, channel->chname, &filter); + r = history_request(channel->chname, &filter); + if (r) + { + history_send_result(client, r); + free_history_result(r); + } } return 0; diff --git a/src/modules/history.c b/src/modules/history.c index 31d31c1d2..07b34ec82 100644 --- a/src/modules/history.c +++ b/src/modules/history.c @@ -72,6 +72,7 @@ void history_usage(Client *client) CMD_FUNC(cmd_history) { HistoryFilter filter; + HistoryResult *r; Channel *channel; int lines = HISTORY_LINES_DEFAULT; @@ -122,5 +123,10 @@ CMD_FUNC(cmd_history) memset(&filter, 0, sizeof(filter)); filter.last_lines = lines; - history_request(client, channel->chname, &filter); + + if ((r = history_request(channel->chname, &filter))) + { + history_send_result(client, r); + free_history_result(r); + } } diff --git a/src/modules/history_backend_mem.c b/src/modules/history_backend_mem.c index ac0be8d8b..8366e541f 100644 --- a/src/modules/history_backend_mem.c +++ b/src/modules/history_backend_mem.c @@ -41,14 +41,6 @@ ModuleHeader MOD_HEADER #define HISTORY_TIMER_EVERY (HISTORY_MAX_OFF_SECS/HISTORY_SPREAD) /* Definitions (structs, etc.) */ -typedef struct HistoryLogLine HistoryLogLine; -struct HistoryLogLine { - HistoryLogLine *prev, *next; - time_t t; - MessageTag *mtags; - char line[1]; -}; - typedef struct HistoryLogObject HistoryLogObject; struct HistoryLogObject { HistoryLogObject *prev, *next; @@ -68,7 +60,7 @@ HistoryLogObject *history_hash_table[HISTORY_BACKEND_MEM_HASH_TABLE_SIZE]; /* Forward declarations */ int hbm_history_add(char *object, MessageTag *mtags, char *line); int hbm_history_cleanup(HistoryLogObject *h); -int hbm_history_request(Client *client, char *object, HistoryFilter *filter); +HistoryResult *hbm_history_request(char *object, HistoryFilter *filter); int hbm_history_destroy(char *object); int hbm_history_set_limit(char *object, int max_lines, long max_time); EVENT(history_mem_clean); @@ -263,53 +255,27 @@ int hbm_history_add(char *object, MessageTag *mtags, char *line) return 0; } -int can_receive_history(Client *client) +HistoryLogLine *duplicate_log_line(HistoryLogLine *l) { - if (HasCapability(client, "server-time")) - return 1; - return 0; + HistoryLogLine *n = safe_alloc(sizeof(HistoryLogLine) + strlen(l->line)); + strcpy(n->line, l->line); /* safe, see memory allocation above ^ */ + hbm_duplicate_mtags(n, l->mtags); + return n; } -void hbm_send_line(Client *client, HistoryLogLine *l, char *batchid) -{ - if (can_receive_history(client)) - { - if (BadPtr(batchid)) - { - sendto_one(client, l->mtags, "%s", l->line); - } else { - MessageTag *m = safe_alloc(sizeof(MessageTag)); - m->name = "batch"; - m->value = batchid; - AddListItem(m, l->mtags); - sendto_one(client, l->mtags, "%s", l->line); - DelListItem(m, l->mtags); - safe_free(m); - } - } else { - /* without server-time, log playback is a bit annoying, so skip it? */ - } -} - -int hbm_history_request(Client *client, char *object, HistoryFilter *filter) +HistoryResult *hbm_history_request(char *object, HistoryFilter *filter) { + HistoryResult *r; HistoryLogObject *h = hbm_find_object(object); HistoryLogLine *l; - char batch[BATCHLEN+1]; long redline; /* Imaginary timestamp. Before the red line, history is too old. */ int lines_sendable = 0, lines_to_skip = 0, cnt = 0; - if (!h || !can_receive_history(client)) - return 0; + if (!h) + return NULL; /* nothing found */ - batch[0] = '\0'; - - if (HasCapability(client, "batch")) - { - /* Start a new batch */ - generate_batch_id(batch); - sendto_one(client, NULL, ":%s BATCH +%s chathistory %s", me.name, batch, object); - } + r = safe_alloc(sizeof(HistoryResult)); + safe_strdup(r->object, object); /* Decide on red line, under this the history is too old. * Filter can be more strict than history object (but not the other way around): @@ -336,13 +302,24 @@ int hbm_history_request(Client *client, char *object, HistoryFilter *filter) * taken into account in hbm_history_add. */ if (l->t >= redline && (++cnt > lines_to_skip)) - hbm_send_line(client, l, batch); + { + /* Add to result */ + HistoryLogLine *n = duplicate_log_line(l); + if (!r->log) + { + /* First item */ + r->log = r->log_tail = n; + } else + { + /* Quick append to tail */ + r->log_tail->next = n; + n->prev = r->log_tail; + r->log_tail = n; /* we are the new tail */ + } + } } - /* End of batch */ - if (*batch) - sendto_one(client, NULL, ":%s BATCH -%s", me.name, batch); - return 1; + return r; } /** Clean up expired entries */ diff --git a/src/modules/history_backend_null.c b/src/modules/history_backend_null.c index 3f62371dc..c516ab270 100644 --- a/src/modules/history_backend_null.c +++ b/src/modules/history_backend_null.c @@ -22,7 +22,7 @@ ModuleHeader MOD_HEADER /* Forward declarations */ int hbn_history_set_limit(char *object, int max_lines, long max_time); int hbn_history_add(char *object, MessageTag *mtags, char *line); -int hbn_history_request(Client *client, char *object, HistoryFilter *filter); +HistoryResult *hbn_history_request(char *object, HistoryFilter *filter); int hbn_history_destroy(char *object); MOD_INIT() @@ -59,9 +59,9 @@ int hbn_history_add(char *object, MessageTag *mtags, char *line) return 1; } -int hbn_history_request(Client *client, char *object, HistoryFilter *filter) +HistoryResult *hbn_history_request(char *object, HistoryFilter *filter) { - return 0; + return NULL; } int hbn_history_set_limit(char *object, int max_lines, long max_time)