| 1 | ;;; erc-log.el --- Logging facilities for ERC. |
| 2 | |
| 3 | ;; Copyright (C) 2003-2013 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: Lawrence Mitchell <wence@gmx.li> |
| 6 | ;; Maintainer: FSF |
| 7 | ;; Keywords: IRC, chat, client, Internet, logging |
| 8 | |
| 9 | ;; Created 2003-04-26 |
| 10 | ;; Logging code taken from erc.el and modified to use markers. |
| 11 | |
| 12 | ;; This file is part of GNU Emacs. |
| 13 | |
| 14 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
| 15 | ;; it under the terms of the GNU General Public License as published by |
| 16 | ;; the Free Software Foundation, either version 3 of the License, or |
| 17 | ;; (at your option) any later version. |
| 18 | |
| 19 | ;; GNU Emacs is distributed in the hope that it will be useful, |
| 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 22 | ;; GNU General Public License for more details. |
| 23 | |
| 24 | ;; You should have received a copy of the GNU General Public License |
| 25 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
| 26 | |
| 27 | ;;; Commentary: |
| 28 | |
| 29 | ;; This file implements log file writing support for ERC. |
| 30 | |
| 31 | ;; Quick start: |
| 32 | ;; |
| 33 | ;; (require 'erc-log) |
| 34 | ;; (setq erc-log-channels-directory "/path/to/logfiles") ; must be writable |
| 35 | ;; (erc-log-enable) |
| 36 | ;; |
| 37 | ;; Or: |
| 38 | ;; |
| 39 | ;; M-x customize-variable erc-modules, and add "log". |
| 40 | ;; |
| 41 | ;; There are two ways to setup logging. The first (default) method |
| 42 | ;; will save buffers on /part, /quit, or killing the channel |
| 43 | ;; buffer. |
| 44 | ;; |
| 45 | ;; The second will write to the log files on each incoming or outgoing |
| 46 | ;; line - this may not be optimal on a laptop HDD. To use this |
| 47 | ;; method, add the following to the above instructions. |
| 48 | ;; |
| 49 | ;; (setq erc-save-buffer-on-part nil |
| 50 | ;; erc-save-queries-on-quit nil |
| 51 | ;; erc-log-write-after-send t |
| 52 | ;; erc-log-write-after-insert t) |
| 53 | ;; |
| 54 | ;; If you only want to save logs for some buffers, customize the |
| 55 | ;; variable `erc-enable-logging'. |
| 56 | |
| 57 | ;; How it works: |
| 58 | ;; |
| 59 | ;; If logging is enabled, at some point, `erc-save-buffer-in-logs' |
| 60 | ;; will be called. The "end" of the buffer is taken from |
| 61 | ;; `erc-insert-marker', while `erc-last-saved-position' holds the |
| 62 | ;; position the buffer was last saved at (as a marker, or if the |
| 63 | ;; buffer hasn't been saved before, as the number 1 (point-min)). |
| 64 | |
| 65 | ;; The region between `erc-last-saved-position' and |
| 66 | ;; `erc-insert-marker' is saved to the current buffer's logfile, and |
| 67 | ;; `erc-last-saved-position' is updated to reflect this. |
| 68 | |
| 69 | ;;; History: |
| 70 | ;; 2003-04-26: logging code pulled out of erc.el. Switched to using |
| 71 | ;; markers. |
| 72 | |
| 73 | ;;; TODO: |
| 74 | ;; |
| 75 | ;; * Really, we need to lock the logfiles somehow, so that if a user |
| 76 | ;; is running multiple emacsen and/or on the same channel as more |
| 77 | ;; than one user, only one process writes to the logfile. This is |
| 78 | ;; especially needed for those logfiles with no nick in them, as |
| 79 | ;; these would become corrupted. |
| 80 | ;; For a single emacs process, the problem could be solved using a |
| 81 | ;; variable which contained the names of buffers already being |
| 82 | ;; logged. This would require that logging be buffer-local, |
| 83 | ;; possibly not a bad thing anyway, since many people don't want to |
| 84 | ;; log the server buffer. |
| 85 | ;; For multiple emacsen the problem is trickier. On some systems, |
| 86 | ;; on could use the function `lock-buffer' and `unlock-buffer'. |
| 87 | ;; However, file locking isn't implemented on all platforms, for |
| 88 | ;; example, there is none on w32 systems. |
| 89 | ;; A third possibility might be to fake lockfiles. However, this |
| 90 | ;; might lead to problems if an emacs crashes, as the lockfile |
| 91 | ;; would be left lying around. |
| 92 | |
| 93 | ;;; Code: |
| 94 | |
| 95 | (require 'erc) |
| 96 | (eval-when-compile (require 'erc-networks)) |
| 97 | |
| 98 | (defgroup erc-log nil |
| 99 | "Logging facilities for ERC." |
| 100 | :group 'erc) |
| 101 | |
| 102 | (defcustom erc-generate-log-file-name-function 'erc-generate-log-file-name-long |
| 103 | "A function to generate a log filename. |
| 104 | The function must take five arguments: BUFFER, TARGET, NICK, SERVER and PORT. |
| 105 | BUFFER is the buffer to be saved, |
| 106 | TARGET is the name of the channel, or the target of the query, |
| 107 | NICK is the current nick, |
| 108 | SERVER and PORT are the parameters that were used to connect to BUFFERs |
| 109 | `erc-server-process'. |
| 110 | |
| 111 | If you want to write logs into different directories, make a |
| 112 | custom function which returns the directory part and set |
| 113 | `erc-log-channels-directory' to its name." |
| 114 | :group 'erc-log |
| 115 | :type '(choice (const :tag "#channel!nick@server:port.txt" |
| 116 | erc-generate-log-file-name-long) |
| 117 | (const :tag "#channel!nick@network.txt" |
| 118 | erc-generate-log-file-name-network) |
| 119 | (const :tag "#channel.txt" erc-generate-log-file-name-short) |
| 120 | (const :tag "#channel@date.txt" |
| 121 | erc-generate-log-file-name-with-date) |
| 122 | (function :tag "Other function"))) |
| 123 | |
| 124 | (defcustom erc-truncate-buffer-on-save nil |
| 125 | "Truncate any ERC (channel, query, server) buffer when it is saved." |
| 126 | :group 'erc-log |
| 127 | :type 'boolean) |
| 128 | |
| 129 | (defcustom erc-enable-logging t |
| 130 | "If non-nil, ERC will log IRC conversations. |
| 131 | This can either be a boolean value of nil or t, or a function. |
| 132 | If the value is a function, it will be called with one argument, the |
| 133 | name of the current ERC buffer. One possible function, which saves |
| 134 | all but server buffers is `erc-log-all-but-server-buffers'. |
| 135 | |
| 136 | This variable is buffer local. Setting it via \\[customize] sets the |
| 137 | default value. |
| 138 | |
| 139 | Log files are stored in `erc-log-channels-directory'." |
| 140 | :group 'erc-log |
| 141 | :type '(choice boolean |
| 142 | function)) |
| 143 | (make-variable-buffer-local 'erc-enable-logging) |
| 144 | |
| 145 | (defcustom erc-log-channels-directory "~/log" |
| 146 | "The directory to place log files for channels. |
| 147 | Leave blank to disable logging. If not nil, all the channel |
| 148 | buffers are logged in separate files in that directory. The |
| 149 | directory should not end with a trailing slash. |
| 150 | |
| 151 | If this is the name of a function, the function will be called |
| 152 | with the buffer, target, nick, server, and port arguments. See |
| 153 | `erc-generate-log-file-name-function' for a description of these |
| 154 | arguments." |
| 155 | :group 'erc-log |
| 156 | :type '(choice directory |
| 157 | (function "Function") |
| 158 | (const :tag "Disable logging" nil))) |
| 159 | |
| 160 | (defcustom erc-log-insert-log-on-open nil |
| 161 | "Insert log file contents into the buffer if a log file exists." |
| 162 | :group 'erc-log |
| 163 | :type 'boolean) |
| 164 | |
| 165 | (defcustom erc-save-buffer-on-part t |
| 166 | "Save the channel buffer content using `erc-save-buffer-in-logs' on PART. |
| 167 | |
| 168 | If you set this to nil, you may want to enable both |
| 169 | `erc-log-write-after-send' and `erc-log-write-after-insert'." |
| 170 | :group 'erc-log |
| 171 | :type 'boolean) |
| 172 | |
| 173 | (defcustom erc-save-queries-on-quit t |
| 174 | "Save all query (also channel) buffers of the server on QUIT. |
| 175 | |
| 176 | If you set this to nil, you may want to enable both |
| 177 | `erc-log-write-after-send' and `erc-log-write-after-insert'." |
| 178 | :group 'erc-log |
| 179 | :type 'boolean) |
| 180 | |
| 181 | (defcustom erc-log-write-after-send nil |
| 182 | "If non-nil, write to log file after every message you send. |
| 183 | |
| 184 | If you set this to nil, you may want to enable both |
| 185 | `erc-save-buffer-on-part' and `erc-save-queries-on-quit'." |
| 186 | :group 'erc-log |
| 187 | :type 'boolean) |
| 188 | |
| 189 | (defcustom erc-log-write-after-insert nil |
| 190 | "If non-nil, write to log file when new text is added to a |
| 191 | logged ERC buffer. |
| 192 | |
| 193 | If you set this to nil, you may want to enable both |
| 194 | `erc-save-buffer-on-part' and `erc-save-queries-on-quit'." |
| 195 | :group 'erc-log |
| 196 | :type 'boolean) |
| 197 | |
| 198 | (defcustom erc-log-file-coding-system (if (featurep 'xemacs) |
| 199 | 'binary |
| 200 | 'emacs-mule) |
| 201 | "The coding system ERC should use for writing log files. |
| 202 | |
| 203 | This should ideally, be a \"catch-all\" coding system, like |
| 204 | `emacs-mule', or `iso-2022-7bit'." |
| 205 | :group 'erc-log) |
| 206 | |
| 207 | (defcustom erc-log-filter-function nil |
| 208 | "If non-nil, pass text through the given function before writing it to |
| 209 | a log file. |
| 210 | |
| 211 | The function should take one argument, which is the text to filter." |
| 212 | :group 'erc-log |
| 213 | :type '(choice (function "Function") |
| 214 | (const :tag "No filtering" nil))) |
| 215 | |
| 216 | |
| 217 | ;;;###autoload (autoload 'erc-log-mode "erc-log" nil t) |
| 218 | (define-erc-module log nil |
| 219 | "Automatically logs things you receive on IRC into files. |
| 220 | Files are stored in `erc-log-channels-directory'; file name |
| 221 | format is defined through a formatting function on |
| 222 | `erc-generate-log-file-name-function'. |
| 223 | |
| 224 | Since automatic logging is not always a Good Thing (especially if |
| 225 | people say things in different coding systems), you can turn logging |
| 226 | behavior on and off with the variable `erc-enable-logging', which can |
| 227 | also be a predicate function. To only log when you are not set away, use: |
| 228 | |
| 229 | \(setq erc-enable-logging |
| 230 | (lambda (buffer) |
| 231 | (with-current-buffer buffer |
| 232 | (null (erc-away-time)))))" |
| 233 | ;; enable |
| 234 | ((when erc-log-write-after-insert |
| 235 | (add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)) |
| 236 | (when erc-log-write-after-send |
| 237 | (add-hook 'erc-send-post-hook 'erc-save-buffer-in-logs)) |
| 238 | (add-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs) |
| 239 | (add-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs) |
| 240 | (add-hook 'kill-emacs-hook 'erc-log-save-all-buffers) |
| 241 | (add-hook 'erc-quit-hook 'erc-conditional-save-queries) |
| 242 | (add-hook 'erc-part-hook 'erc-conditional-save-buffer) |
| 243 | ;; append, so that 'erc-initialize-log-marker runs first |
| 244 | (add-hook 'erc-connect-pre-hook 'erc-log-setup-logging 'append) |
| 245 | (dolist (buffer (erc-buffer-list)) |
| 246 | (erc-log-setup-logging buffer))) |
| 247 | ;; disable |
| 248 | ((remove-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs) |
| 249 | (remove-hook 'erc-send-post-hook 'erc-save-buffer-in-logs) |
| 250 | (remove-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs) |
| 251 | (remove-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs) |
| 252 | (remove-hook 'kill-emacs-hook 'erc-log-save-all-buffers) |
| 253 | (remove-hook 'erc-quit-hook 'erc-conditional-save-queries) |
| 254 | (remove-hook 'erc-part-hook 'erc-conditional-save-buffer) |
| 255 | (remove-hook 'erc-connect-pre-hook 'erc-log-setup-logging) |
| 256 | (dolist (buffer (erc-buffer-list)) |
| 257 | (erc-log-disable-logging buffer)))) |
| 258 | |
| 259 | (define-key erc-mode-map "\C-c\C-l" 'erc-save-buffer-in-logs) |
| 260 | |
| 261 | ;;; functionality referenced from erc.el |
| 262 | (defun erc-log-setup-logging (buffer) |
| 263 | "Setup the buffer-local logging variables in the current buffer. |
| 264 | This function is destined to be run from `erc-connect-pre-hook'. |
| 265 | The current buffer is given by BUFFER." |
| 266 | (when (erc-logging-enabled buffer) |
| 267 | (with-current-buffer buffer |
| 268 | (auto-save-mode -1) |
| 269 | (setq buffer-file-name nil) |
| 270 | (erc-set-write-file-functions '(erc-save-buffer-in-logs)) |
| 271 | (when erc-log-insert-log-on-open |
| 272 | (ignore-errors (insert-file-contents (erc-current-logfile)) |
| 273 | (move-marker erc-last-saved-position |
| 274 | (1- (point-max)))))))) |
| 275 | |
| 276 | (defun erc-log-disable-logging (buffer) |
| 277 | "Disable logging in BUFFER." |
| 278 | (when (erc-logging-enabled buffer) |
| 279 | (with-current-buffer buffer |
| 280 | (setq buffer-offer-save nil |
| 281 | erc-enable-logging nil)))) |
| 282 | |
| 283 | (defun erc-log-all-but-server-buffers (buffer) |
| 284 | "Returns t if logging should be enabled in BUFFER. |
| 285 | Returns nil if `erc-server-buffer-p' returns t." |
| 286 | (save-excursion |
| 287 | (save-window-excursion |
| 288 | (set-buffer buffer) |
| 289 | (not (erc-server-buffer-p))))) |
| 290 | |
| 291 | (defun erc-save-query-buffers (process) |
| 292 | "Save all buffers of the given PROCESS." |
| 293 | (erc-with-all-buffers-of-server process |
| 294 | nil |
| 295 | (erc-save-buffer-in-logs))) |
| 296 | |
| 297 | (defun erc-conditional-save-buffer (buffer) |
| 298 | "Save Query BUFFER if `erc-save-queries-on-quit' is t." |
| 299 | (when erc-save-buffer-on-part |
| 300 | (erc-save-buffer-in-logs buffer))) |
| 301 | |
| 302 | (defun erc-conditional-save-queries (process) |
| 303 | "Save Query buffers of PROCESS if `erc-save-queries-on-quit' is t." |
| 304 | (when erc-save-queries-on-quit |
| 305 | (erc-save-query-buffers process))) |
| 306 | |
| 307 | ;; Make sure that logs get saved, even if someone overrides the active |
| 308 | ;; process prompt for a quick exit from Emacs |
| 309 | (defun erc-log-save-all-buffers () |
| 310 | (dolist (buffer (erc-buffer-list)) |
| 311 | (erc-save-buffer-in-logs buffer))) |
| 312 | |
| 313 | ;;;###autoload |
| 314 | (defun erc-logging-enabled (&optional buffer) |
| 315 | "Return non-nil if logging is enabled for BUFFER. |
| 316 | If BUFFER is nil, the value of `current-buffer' is used. |
| 317 | Logging is enabled if `erc-log-channels-directory' is non-nil, the directory |
| 318 | is writable (it will be created as necessary) and |
| 319 | `erc-enable-logging' returns a non-nil value." |
| 320 | (and erc-log-channels-directory |
| 321 | (or (functionp erc-log-channels-directory) |
| 322 | (erc-directory-writable-p erc-log-channels-directory)) |
| 323 | (if (functionp erc-enable-logging) |
| 324 | (funcall erc-enable-logging (or buffer (current-buffer))) |
| 325 | erc-enable-logging))) |
| 326 | |
| 327 | (defun erc-log-standardize-name (filename) |
| 328 | "Make FILENAME safe to use as the name of an ERC log. |
| 329 | This will not work with full paths, only names. |
| 330 | |
| 331 | Any unsafe characters in the name are replaced with \"!\". The |
| 332 | filename is downcased." |
| 333 | (downcase (erc-replace-regexp-in-string |
| 334 | "[/\\]" "!" (convert-standard-filename filename)))) |
| 335 | |
| 336 | (defun erc-current-logfile (&optional buffer) |
| 337 | "Return the logfile to use for BUFFER. |
| 338 | If BUFFER is nil, the value of `current-buffer' is used. |
| 339 | This is determined by `erc-generate-log-file-name-function'. |
| 340 | The result is converted to lowercase, as IRC is case-insensitive" |
| 341 | (unless buffer (setq buffer (current-buffer))) |
| 342 | (let ((target (or (buffer-name buffer) (erc-default-target))) |
| 343 | (nick (erc-current-nick)) |
| 344 | (server erc-session-server) |
| 345 | (port erc-session-port)) |
| 346 | (expand-file-name |
| 347 | (erc-log-standardize-name |
| 348 | (funcall erc-generate-log-file-name-function |
| 349 | buffer target nick server port)) |
| 350 | (if (functionp erc-log-channels-directory) |
| 351 | (funcall erc-log-channels-directory |
| 352 | buffer target nick server port) |
| 353 | erc-log-channels-directory)))) |
| 354 | |
| 355 | (defun erc-generate-log-file-name-with-date (buffer &rest ignore) |
| 356 | "This function computes a short log file name. |
| 357 | The name of the log file is composed of BUFFER and the current date. |
| 358 | This function is a possible value for `erc-generate-log-file-name-function'." |
| 359 | (concat (buffer-name buffer) "-" (format-time-string "%Y-%m-%d") ".txt")) |
| 360 | |
| 361 | (defun erc-generate-log-file-name-short (buffer &rest ignore) |
| 362 | "This function computes a short log file name. |
| 363 | In fact, it only uses the buffer name of the BUFFER argument, so |
| 364 | you can affect that using `rename-buffer' and the-like. This |
| 365 | function is a possible value for |
| 366 | `erc-generate-log-file-name-function'." |
| 367 | (concat (buffer-name buffer) ".txt")) |
| 368 | |
| 369 | (defun erc-generate-log-file-name-long (buffer target nick server port) |
| 370 | "Generates a log-file name in the way ERC always did it. |
| 371 | This results in a file name of the form #channel!nick@server:port.txt. |
| 372 | This function is a possible value for `erc-generate-log-file-name-function'." |
| 373 | (let ((file (concat |
| 374 | (if target (concat target "!")) |
| 375 | nick "@" server ":" (cond ((stringp port) port) |
| 376 | ((numberp port) |
| 377 | (number-to-string port))) ".txt"))) |
| 378 | ;; we need a make-safe-file-name function. |
| 379 | (convert-standard-filename file))) |
| 380 | |
| 381 | (defun erc-generate-log-file-name-network (buffer target nick server port) |
| 382 | "Generates a log-file name using the network name rather than server name. |
| 383 | This results in a file name of the form #channel!nick@network.txt. |
| 384 | This function is a possible value for `erc-generate-log-file-name-function'." |
| 385 | (require 'erc-networks) |
| 386 | (let ((file (concat |
| 387 | (if target (concat target "!")) |
| 388 | nick "@" |
| 389 | (or (with-current-buffer buffer (erc-network-name)) server) |
| 390 | ".txt"))) |
| 391 | ;; we need a make-safe-file-name function. |
| 392 | (convert-standard-filename file))) |
| 393 | |
| 394 | ;;;###autoload |
| 395 | (defun erc-save-buffer-in-logs (&optional buffer) |
| 396 | "Append BUFFER contents to the log file, if logging is enabled. |
| 397 | If BUFFER is not provided, current buffer is used. |
| 398 | Logging is enabled if `erc-logging-enabled' returns non-nil. |
| 399 | |
| 400 | This is normally done on exit, to save the unsaved portion of the |
| 401 | buffer, since only the text that runs off the buffer limit is logged |
| 402 | automatically. |
| 403 | |
| 404 | You can save every individual message by putting this function on |
| 405 | `erc-insert-post-hook'." |
| 406 | (interactive) |
| 407 | (or buffer (setq buffer (current-buffer))) |
| 408 | (when (erc-logging-enabled buffer) |
| 409 | (let ((file (erc-current-logfile buffer)) |
| 410 | (coding-system erc-log-file-coding-system) |
| 411 | (inhibit-clash-detection t)) ; needed for XEmacs |
| 412 | (save-excursion |
| 413 | (with-current-buffer buffer |
| 414 | (save-restriction |
| 415 | (widen) |
| 416 | ;; early on in the initialization, don't try and write the log out |
| 417 | (when (and (markerp erc-last-saved-position) |
| 418 | (> erc-insert-marker (1+ erc-last-saved-position))) |
| 419 | (let ((start (1+ (marker-position erc-last-saved-position))) |
| 420 | (end (marker-position erc-insert-marker))) |
| 421 | (if (functionp erc-log-filter-function) |
| 422 | (let ((text (buffer-substring start end))) |
| 423 | (with-temp-buffer |
| 424 | (insert (funcall erc-log-filter-function text)) |
| 425 | (let ((coding-system-for-write coding-system)) |
| 426 | (write-region (point-min) (point-max) |
| 427 | file t 'nomessage)))) |
| 428 | (let ((coding-system-for-write coding-system)) |
| 429 | (write-region start end file t 'nomessage)))) |
| 430 | (if (and erc-truncate-buffer-on-save |
| 431 | (called-interactively-p 'interactive)) |
| 432 | (progn |
| 433 | (let ((inhibit-read-only t)) (erase-buffer)) |
| 434 | (move-marker erc-last-saved-position (point-max)) |
| 435 | (erc-display-prompt)) |
| 436 | (move-marker erc-last-saved-position |
| 437 | ;; If we place erc-last-saved-position at |
| 438 | ;; erc-insert-marker, because text gets |
| 439 | ;; inserted /before/ erc-insert-marker, |
| 440 | ;; the log file will not be saved |
| 441 | ;; (erc-last-saved-position will always |
| 442 | ;; be equal to erc-insert-marker). |
| 443 | (1- (marker-position erc-insert-marker))))) |
| 444 | (set-buffer-modified-p nil)))))) |
| 445 | t) |
| 446 | |
| 447 | (provide 'erc-log) |
| 448 | |
| 449 | ;;; erc-log.el ends here |
| 450 | ;; |
| 451 | ;; Local Variables: |
| 452 | ;; indent-tabs-mode: t |
| 453 | ;; tab-width: 8 |
| 454 | ;; End: |