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