Merge from erc--emacs--22
[bpt/emacs.git] / lisp / erc / erc-log.el
1 ;;; erc-log.el --- Logging facilities for ERC.
2
3 ;; Copyright (C) 2003, 2004, 2005, 2006 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 (require 'cl))
89
90 (defgroup erc-log nil
91 "Logging facilities for ERC."
92 :group 'erc)
93
94 (defcustom erc-generate-log-file-name-function 'erc-generate-log-file-name-long
95 "*A function to generate a log filename.
96 The function must take five arguments: BUFFER, TARGET, NICK, SERVER and PORT.
97 BUFFER is the buffer to be saved,
98 TARGET is the name of the channel, or the target of the query,
99 NICK is the current nick,
100 SERVER and PORT are the parameters used to connect BUFFERs
101 `erc-server-process'."
102 :group 'erc-log
103 :type '(choice (const erc-generate-log-file-name-long)
104 (const erc-generate-log-file-name-short)
105 (const erc-generate-log-file-name-with-date)
106 (symbol)))
107
108 (defcustom erc-truncate-buffer-on-save nil
109 "Truncate any ERC (channel, query, server) buffer when it is saved."
110 :group 'erc-log
111 :type 'boolean)
112
113 (defcustom erc-enable-logging t
114 "If non-nil, ERC will log IRC conversations.
115 This can either be a boolean value of nil or t, or a function.
116 If the value is a function, it will be called with one argument, the
117 name of the current ERC buffer. One possible function, which saves
118 all but server buffers is `erc-log-all-but-server-buffers'.
119
120 This variable is buffer local. Setting it via \\[customize] sets the
121 default value.
122
123 Log files are stored in `erc-log-channels-directory'."
124 :group 'erc-log
125 :type '(choice boolean
126 function))
127 (make-variable-buffer-local 'erc-enable-logging)
128
129 (defcustom erc-log-channels-directory "~/log"
130 "The directory to place log files for channels.
131 Leave blank to disable logging. If not nil, all the channel
132 buffers are logged in separate files in that directory. The
133 directory should not end with a trailing slash."
134 :group 'erc-log
135 :type '(choice directory
136 (const nil)))
137
138 (defcustom erc-log-insert-log-on-open nil
139 "*Insert log file contents into the buffer if a log file exists."
140 :group 'erc-log
141 :type 'boolean)
142
143 (defcustom erc-save-buffer-on-part t
144 "*Save the channel buffer content using `erc-save-buffer-in-logs' on PART.
145
146 If you set this to nil, you may want to enable both
147 `erc-log-write-after-send' and `erc-log-write-after-insert'."
148 :group 'erc-log
149 :type 'boolean)
150
151 (defcustom erc-save-queries-on-quit t
152 "*Save all query (also channel) buffers of the server on QUIT.
153
154 If you set this to nil, you may want to enable both
155 `erc-log-write-after-send' and `erc-log-write-after-insert'."
156 :group 'erc-log
157 :type 'boolean)
158
159 (defcustom erc-log-write-after-send nil
160 "*If non-nil, write to log file after every message you send.
161
162 If you set this to nil, you may want to enable both
163 `erc-save-buffer-on-part' and `erc-save-queries-on-quit'."
164 :group 'erc-log
165 :type 'boolean)
166
167 (defcustom erc-log-write-after-insert nil
168 "*If non-nil, write to log file when new text is added to a
169 logged ERC buffer.
170
171 If you set this to nil, you may want to enable both
172 `erc-save-buffer-on-part' and `erc-save-queries-on-quit'."
173 :group 'erc-log
174 :type 'boolean)
175
176 (defcustom erc-log-file-coding-system (if (featurep 'xemacs)
177 'binary
178 'emacs-mule)
179 "*The coding system ERC should use for writing log files.
180
181 This should ideally, be a \"catch-all\" coding system, like
182 `emacs-mule', or `iso-2022-7bit'."
183 :group 'erc-log)
184
185 ;;;###autoload (autoload 'erc-log-mode "erc-log" nil t)
186 (define-erc-module log nil
187 "Automatically logs things you receive on IRC into files.
188 Files are stored in `erc-log-channels-directory'; file name
189 format is defined through a formatting function on
190 `erc-generate-log-file-name-function'.
191
192 Since automatic logging is not always a Good Thing (especially if
193 people say things in different coding systems), you can turn logging
194 behaviour on and off with the variable `erc-enable-logging', which can
195 also be a predicate function. To only log when you are not set away, use:
196
197 \(setq erc-enable-logging
198 (lambda (buffer)
199 (with-current-buffer buffer
200 (not erc-away))))"
201 ;; enable
202 ((when erc-log-write-after-insert
203 (add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs))
204 (when erc-log-write-after-send
205 (add-hook 'erc-send-post-hook 'erc-save-buffer-in-logs))
206 (add-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs)
207 (add-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs)
208 (add-hook 'kill-emacs-hook 'erc-log-save-all-buffers)
209 (add-hook 'erc-quit-hook 'erc-conditional-save-queries)
210 (add-hook 'erc-part-hook 'erc-conditional-save-buffer)
211 ;; append, so that 'erc-initialize-log-marker runs first
212 (add-hook 'erc-connect-pre-hook 'erc-log-setup-logging 'append)
213 (dolist (buffer (erc-buffer-list))
214 (when (buffer-live-p buffer)
215 (with-current-buffer buffer (erc-log-setup-logging)))))
216 ;; disable
217 ((remove-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)
218 (remove-hook 'erc-send-post-hook 'erc-save-buffer-in-logs)
219 (remove-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs)
220 (remove-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs)
221 (remove-hook 'kill-emacs-hook 'erc-log-save-all-buffers)
222 (remove-hook 'erc-quit-hook 'erc-conditional-save-queries)
223 (remove-hook 'erc-part-hook 'erc-conditional-save-buffer)
224 (remove-hook 'erc-connect-pre-hook 'erc-log-setup-logging)
225 (dolist (buffer (erc-buffer-list))
226 (when (buffer-live-p buffer)
227 (with-current-buffer buffer (erc-log-disable-logging))))))
228
229 (define-key erc-mode-map "\C-c\C-l" 'erc-save-buffer-in-logs)
230
231 ;;; functionality referenced from erc.el
232 (defun erc-log-setup-logging ()
233 "Setup the buffer-local logging variables in the current buffer.
234 This function is destined to be run from `erc-connect-pre-hook'."
235 (when (erc-logging-enabled)
236 (auto-save-mode -1)
237 (setq buffer-file-name nil)
238 (set (make-local-variable 'write-file-functions)
239 '(erc-save-buffer-in-logs))
240 (when erc-log-insert-log-on-open
241 (ignore-errors (insert-file-contents (erc-current-logfile))
242 (move-marker erc-last-saved-position
243 (1- (point-max)))))))
244
245 (defun erc-log-disable-logging ()
246 "Disable logging in the current buffer."
247 (when (erc-logging-enabled)
248 (setq buffer-offer-save nil
249 erc-enable-logging nil)))
250
251 (defun erc-log-all-but-server-buffers (buffer)
252 "Returns t if logging should be enabled in BUFFER.
253 Returns nil iff `erc-server-buffer-p' returns t."
254 (save-excursion
255 (save-window-excursion
256 (set-buffer buffer)
257 (not (erc-server-buffer-p)))))
258
259 (defun erc-save-query-buffers (process)
260 "Save all buffers of the given PROCESS."
261 (erc-with-all-buffers-of-server process
262 nil
263 (erc-save-buffer-in-logs)))
264
265 (defun erc-conditional-save-buffer (buffer)
266 "Save Query BUFFER if `erc-save-queries-on-quit' is t."
267 (when erc-save-buffer-on-part
268 (erc-save-buffer-in-logs buffer)))
269
270 (defun erc-conditional-save-queries (process)
271 "Save Query buffers of PROCESS if `erc-save-queries-on-quit' is t."
272 (when erc-save-queries-on-quit
273 (erc-save-query-buffers process)))
274
275 ;; Make sure that logs get saved, even if someone overrides the active
276 ;; process prompt for a quick exit from Emacs
277 (defun erc-log-save-all-buffers ()
278 (dolist (buffer (erc-buffer-list))
279 (erc-save-buffer-in-logs buffer)))
280
281 ;;;###autoload
282 (defun erc-logging-enabled (&optional buffer)
283 "Return non-nil if logging is enabled for BUFFER.
284 If BUFFER is nil, the value of `current-buffer' is used.
285 Logging is enabled if `erc-log-channels-directory' is non-nil, the directory
286 is writeable (it will be created as necessary) and
287 `erc-enable-logging' returns a non-nil value."
288 (and erc-log-channels-directory
289 (erc-directory-writable-p erc-log-channels-directory)
290 (if (functionp erc-enable-logging)
291 (funcall erc-enable-logging (or buffer (current-buffer)))
292 erc-enable-logging)))
293
294 (defun erc-log-standardize-name (filename)
295 "Make FILENAME safe to use as the name of an ERC log.
296 This will not work with full paths, only names.
297
298 Any unsafe characters in the name are replaced with \"!\". The
299 filename is downcased."
300 (downcase (erc-replace-regexp-in-string
301 "[/\\]" "!" (convert-standard-filename filename))))
302
303 (defun erc-current-logfile (&optional buffer)
304 "Return the logfile to use for BUFFER.
305 If BUFFER is nil, the value of `current-buffer' is used.
306 This is determined by `erc-generate-log-file-name-function'.
307 The result is converted to lowercase, as IRC is case-insensitive"
308 (expand-file-name
309 (erc-log-standardize-name
310 (funcall erc-generate-log-file-name-function
311 (or buffer (current-buffer))
312 (or (buffer-name buffer) (erc-default-target))
313 (erc-current-nick)
314 erc-session-server erc-session-port))
315 erc-log-channels-directory))
316
317 (defun erc-generate-log-file-name-with-date (buffer &rest ignore)
318 "This function computes a short log file name.
319 The name of the log file is composed of BUFFER and the current date.
320 This function is a possible value for `erc-generate-log-file-name-function'."
321 (concat (buffer-name buffer) "-" (format-time-string "%Y-%m-%d") ".txt"))
322
323 (defun erc-generate-log-file-name-short (buffer &rest ignore)
324 "This function computes a short log file name.
325 In fact, it only uses the buffer name of the BUFFER argument, so
326 you can affect that using `rename-buffer' and the-like. This
327 function is a possible value for
328 `erc-generate-log-file-name-function'."
329 (concat (buffer-name buffer) ".txt"))
330
331 (defun erc-generate-log-file-name-long (buffer target nick server port)
332 "Generates a log-file name in the way ERC always did it.
333 This results in a file name of the form #channel!nick@server:port.txt.
334 This function is a possible value for `erc-generate-log-file-name-function'."
335 (let ((file (concat
336 (if target (concat target "!"))
337 nick "@" server ":" (cond ((stringp port) port)
338 ((numberp port)
339 (number-to-string port))) ".txt")))
340 ;; we need a make-safe-file-name function.
341 (convert-standard-filename file)))
342
343 ;;;###autoload
344 (defun erc-save-buffer-in-logs (&optional buffer)
345 "Append BUFFER contents to the log file, if logging is enabled.
346 If BUFFER is not provided, current buffer is used.
347 Logging is enabled if `erc-logging-enabled' returns non-nil.
348
349 This is normally done on exit, to save the unsaved portion of the
350 buffer, since only the text that runs off the buffer limit is logged
351 automatically.
352
353 You can save every individual message by putting this function on
354 `erc-insert-post-hook'."
355 (interactive)
356 (or buffer (setq buffer (current-buffer)))
357 (when (erc-logging-enabled buffer)
358 (let ((file (erc-current-logfile buffer))
359 (coding-system-for-write erc-log-file-coding-system))
360 (save-excursion
361 (with-current-buffer buffer
362 (save-restriction
363 (widen)
364 ;; early on in the initalisation, don't try and write the log out
365 (when (and (markerp erc-last-saved-position)
366 (> erc-insert-marker (1+ erc-last-saved-position)))
367 (write-region (1+ (marker-position erc-last-saved-position))
368 (marker-position erc-insert-marker)
369 file t 'nomessage)
370 (if (and erc-truncate-buffer-on-save (interactive-p))
371 (progn
372 (let ((inhibit-read-only t)) (erase-buffer))
373 (move-marker erc-last-saved-position (point-max))
374 (erc-display-prompt))
375 (move-marker erc-last-saved-position
376 ;; If we place erc-last-saved-position at
377 ;; erc-insert-marker, because text gets
378 ;; inserted /before/ erc-insert-marker,
379 ;; the log file will not be saved
380 ;; (erc-last-saved-position will always
381 ;; be equal to erc-insert-marker).
382 (1- (marker-position erc-insert-marker)))))
383 (set-buffer-modified-p nil))))))
384 t)
385
386 (provide 'erc-log)
387
388 ;;; erc-log.el ends here
389 ;;
390 ;; Local Variables:
391 ;; indent-tabs-mode: t
392 ;; tab-width: 8
393 ;; End:
394
395 ;; arch-tag: 54072f99-9f0a-4846-8908-2ccde92221de