diff --git a/degesch.c b/degesch.c index d0b0a83..193b898 100644 --- a/degesch.c +++ b/degesch.c @@ -1018,6 +1018,8 @@ struct buffer unsigned unseen_messages_count; ///< # messages since last visited bool highlighted; ///< We've been highlighted + FILE *log_file; ///< Log file + // Origin information: struct server *server; ///< Reference to server @@ -1041,6 +1043,8 @@ buffer_destroy (struct buffer *self) input_buffer_destroy (self->input_data); LIST_FOR_EACH (struct buffer_line, iter, self->lines) buffer_line_destroy (iter); + if (self->log_file) + (void) fclose (self->log_file); if (self->user) user_unref (self->user); if (self->channel) @@ -1550,6 +1554,10 @@ static struct config_schema g_config_behaviour[] = .comment = "Beep when highlighted or on a new invisible PM", .type = CONFIG_ITEM_BOOLEAN, .default_ = "on" }, + { .name = "logging", + .comment = "Log buffer contents to file", + .type = CONFIG_ITEM_BOOLEAN, + .default_ = "off" }, {} }; @@ -2482,6 +2490,20 @@ buffer_update_time (struct app_context *ctx, time_t now) // Else the buffer was too small, which is pretty weird } +static void +buffer_line_flush (struct buffer_line *line, struct formatter *f, FILE *output) +{ + int flags = line->flags; + if (flags & BUFFER_LINE_INDENT) formatter_add (f, " "); + if (flags & BUFFER_LINE_STATUS) formatter_add (f, " - "); + if (flags & BUFFER_LINE_ERROR) formatter_add (f, "#a=!=#r ", ATTR_ERROR); + + formatter_add_from (f, line->formatter); + formatter_add (f, "\n"); + formatter_flush (f, output); + formatter_free (f); +} + static void buffer_line_display (struct app_context *ctx, struct buffer_line *line, bool is_external) @@ -2494,12 +2516,13 @@ buffer_line_display (struct app_context *ctx, formatter_init (&f, ctx, NULL); struct tm current; + char buf[9]; if (!localtime_r (&line->when, ¤t)) print_error ("%s: %s", "localtime_r", strerror (errno)); + else if (!strftime (buf, sizeof buf, "%T", ¤t)) + print_error ("%s: %s", "strftime", "buffer too small"); else - formatter_add (&f, "#a#&s#r ", ATTR_TIMESTAMP, - xstrdup_printf ("%02d:%02d:%02d", - current.tm_hour, current.tm_min, current.tm_sec)); + formatter_add (&f, "#a#s#r ", ATTR_TIMESTAMP, buf); // Ignore all formatting for messages coming from other buffers, that is // either from the global or server buffer. Instead print them in grey. @@ -2509,26 +2532,33 @@ buffer_line_display (struct app_context *ctx, FORMATTER_ADD_ITEM (&f, IGNORE_ATTR, .attribute = 1); } - if (line->flags & BUFFER_LINE_INDENT) - formatter_add (&f, " "); - if (line->flags & BUFFER_LINE_STATUS) - formatter_add (&f, " - "); - if (line->flags & BUFFER_LINE_ERROR) - formatter_add (&f, "#a=!=#r ", ATTR_ERROR); - formatter_add_from (&f, line->formatter); - input_hide (&ctx->input); - - // TODO: write the line to a log file; note that the global and server - // buffers musn't collide with filenames - - formatter_add (&f, "\n"); - formatter_flush (&f, stdout); - formatter_free (&f); - + buffer_line_flush (line, &f, stdout); input_show (&ctx->input); } +static void +buffer_line_write_to_log (struct app_context *ctx, + struct buffer_line *line, FILE *log_file) +{ + if (line->flags & BUFFER_LINE_SKIP_FILE) + return; + + struct formatter f; + formatter_init (&f, ctx, NULL); + + struct tm current; + char buf[20]; + if (!gmtime_r (&line->when, ¤t)) + print_error ("%s: %s", "gmtime_r", strerror (errno)); + else if (!strftime (buf, sizeof buf, "%F %T", ¤t)) + print_error ("%s: %s", "strftime", "buffer too small"); + else + formatter_add (&f, "#s ", buf); + + buffer_line_flush (line, &f, log_file); +} + static void log_formatter (struct app_context *ctx, struct buffer *buffer, int flags, struct formatter *f) @@ -2547,6 +2577,9 @@ log_formatter (struct app_context *ctx, LIST_APPEND_WITH_TAIL (buffer->lines, buffer->lines_tail, line); buffer->lines_count++; + if (buffer->log_file) + buffer_line_write_to_log (ctx, line, buffer->log_file); + if (get_config_boolean (ctx->config.root, "behaviour.beep_on_highlight")) if ((flags & BUFFER_LINE_HIGHLIGHT) || (buffer->type == BUFFER_PM && buffer != ctx->current_buffer)) @@ -2634,6 +2667,41 @@ log_full (struct app_context *ctx, struct server *s, struct buffer *buffer, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static void +make_log_filename (const char *filename, struct str *output) +{ + for (const char *p = filename; *p; p++) + // XXX: anything more to replace? + if (strchr ("/\\ ", *p)) + str_append_c (output, '_'); + else + str_append_c (output, tolower_ascii (*p)); +} + +static void +buffer_open_log_file (struct app_context *ctx, struct buffer *buffer) +{ + if (!get_config_boolean (ctx->config.root, "behaviour.logging")) + return; + + struct str path; + str_init (&path); + get_xdg_home_dir (&path, "XDG_DATA_HOME", ".local/share"); + str_append_printf (&path, "/%s/%s", PROGRAM_NAME, "logs"); + + (void) mkdir_with_parents (path.str, NULL); + + // TODO: make sure global and server buffers don't collide with filenames + str_append_c (&path, '/'); + make_log_filename (buffer->name, &path); + str_append (&path, ".log"); + + if (!(buffer->log_file = fopen (path.str, "ab"))) + log_global_error (ctx, "Couldn't open log file `#s': #s", + path.str, strerror (errno)); + str_free (&path); +} + static struct buffer * buffer_by_name (struct app_context *ctx, const char *name) { @@ -2648,6 +2716,8 @@ buffer_add (struct app_context *ctx, struct buffer *buffer) str_map_set (&ctx->buffers_by_name, buffer->name, buffer); LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); + buffer_open_log_file (ctx, buffer); + // In theory this can't cause changes in the prompt refresh_prompt (ctx); } @@ -2776,6 +2846,13 @@ buffer_rename (struct app_context *ctx, str_map_set (&ctx->buffers_by_name, buffer->name, NULL); str_map_set (&ctx->buffers_by_name, new_name, buffer); + if (buffer->log_file) + { + (void) fclose (buffer->log_file); + buffer->log_file = NULL; + } + buffer_open_log_file (ctx, buffer); + free (buffer->name); buffer->name = xstrdup (new_name);