Commit | Line | Data |
---|---|---|
597993cf MB |
1 | ;;; erc-log.el --- Logging facilities for ERC. |
2 | ||
ba318903 | 3 | ;; Copyright (C) 2003-2014 Free Software Foundation, Inc. |
597993cf MB |
4 | |
5 | ;; Author: Lawrence Mitchell <wence@gmx.li> | |
34dc21db | 6 | ;; Maintainer: emacs-devel@gnu.org |
597993cf MB |
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 | ||
4ee57b2a | 14 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
597993cf | 15 | ;; it under the terms of the GNU General Public License as published by |
4ee57b2a GM |
16 | ;; the Free Software Foundation, either version 3 of the License, or |
17 | ;; (at your option) any later version. | |
597993cf MB |
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 | |
4ee57b2a | 25 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
597993cf MB |
26 | |
27 | ;;; Commentary: | |
28 | ||
29 | ;; This file implements log file writing support for ERC. | |
30 | ||
31 | ;; Quick start: | |
32 | ;; | |
526dc846 | 33 | ;; (require 'erc-log) |
597993cf | 34 | ;; (setq erc-log-channels-directory "/path/to/logfiles") ; must be writable |
526dc846 | 35 | ;; (erc-log-enable) |
597993cf | 36 | ;; |
526dc846 | 37 | ;; Or: |
597993cf | 38 | ;; |
526dc846 | 39 | ;; M-x customize-variable erc-modules, and add "log". |
597993cf | 40 | ;; |
526dc846 MO |
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) | |
597993cf | 53 | ;; |
e1dbe924 | 54 | ;; If you only want to save logs for some buffers, customize the |
597993cf MB |
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: | |
597993cf MB |
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) | |
19dc7206 | 96 | (eval-when-compile (require 'erc-networks)) |
597993cf MB |
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 | |
fb7ada5f | 103 | "A function to generate a log filename. |
597993cf MB |
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, | |
526dc846 MO |
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." | |
597993cf | 114 | :group 'erc-log |
31bcb420 | 115 | :type '(choice (const :tag "#channel!nick@server:port.txt" |
b68f89c4 | 116 | erc-generate-log-file-name-long) |
31bcb420 | 117 | (const :tag "#channel!nick@network.txt" |
ff59d266 | 118 | erc-generate-log-file-name-network) |
b68f89c4 | 119 | (const :tag "#channel.txt" erc-generate-log-file-name-short) |
31bcb420 | 120 | (const :tag "#channel@date.txt" |
b68f89c4 | 121 | erc-generate-log-file-name-with-date) |
526dc846 | 122 | (function :tag "Other function"))) |
597993cf | 123 | |
597993cf MB |
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 | |
526dc846 MO |
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." | |
597993cf MB |
155 | :group 'erc-log |
156 | :type '(choice directory | |
526dc846 MO |
157 | (function "Function") |
158 | (const :tag "Disable logging" nil))) | |
597993cf | 159 | |
0b6bb130 | 160 | (defcustom erc-log-insert-log-on-open nil |
fb7ada5f | 161 | "Insert log file contents into the buffer if a log file exists." |
597993cf MB |
162 | :group 'erc-log |
163 | :type 'boolean) | |
164 | ||
0b6bb130 | 165 | (defcustom erc-save-buffer-on-part t |
fb7ada5f | 166 | "Save the channel buffer content using `erc-save-buffer-in-logs' on PART. |
0b6bb130 MB |
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 | |
fb7ada5f | 174 | "Save all query (also channel) buffers of the server on QUIT. |
0b6bb130 MB |
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 | |
fb7ada5f | 182 | "If non-nil, write to log file after every message you send. |
0b6bb130 MB |
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 | |
fb7ada5f | 190 | "If non-nil, write to log file when new text is added to a |
0b6bb130 MB |
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'." | |
597993cf MB |
195 | :group 'erc-log |
196 | :type 'boolean) | |
197 | ||
198 | (defcustom erc-log-file-coding-system (if (featurep 'xemacs) | |
199 | 'binary | |
200 | 'emacs-mule) | |
fb7ada5f | 201 | "The coding system ERC should use for writing log files. |
597993cf MB |
202 | |
203 | This should ideally, be a \"catch-all\" coding system, like | |
204 | `emacs-mule', or `iso-2022-7bit'." | |
9c5a5c77 | 205 | :type 'coding-system |
597993cf MB |
206 | :group 'erc-log) |
207 | ||
d20cf916 | 208 | (defcustom erc-log-filter-function nil |
fb7ada5f | 209 | "If non-nil, pass text through the given function before writing it to |
d20cf916 MO |
210 | a log file. |
211 | ||
212 | The function should take one argument, which is the text to filter." | |
213 | :group 'erc-log | |
214 | :type '(choice (function "Function") | |
215 | (const :tag "No filtering" nil))) | |
216 | ||
217 | ||
597993cf MB |
218 | ;;;###autoload (autoload 'erc-log-mode "erc-log" nil t) |
219 | (define-erc-module log nil | |
220 | "Automatically logs things you receive on IRC into files. | |
221 | Files are stored in `erc-log-channels-directory'; file name | |
222 | format is defined through a formatting function on | |
223 | `erc-generate-log-file-name-function'. | |
224 | ||
225 | Since automatic logging is not always a Good Thing (especially if | |
226 | people say things in different coding systems), you can turn logging | |
38b94e65 JB |
227 | behavior on and off with the variable `erc-enable-logging', which can |
228 | also be a predicate function. To only log when you are not set away, use: | |
597993cf MB |
229 | |
230 | \(setq erc-enable-logging | |
231 | (lambda (buffer) | |
232 | (with-current-buffer buffer | |
ff59d266 | 233 | (null (erc-away-time)))))" |
597993cf | 234 | ;; enable |
0b6bb130 MB |
235 | ((when erc-log-write-after-insert |
236 | (add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)) | |
237 | (when erc-log-write-after-send | |
238 | (add-hook 'erc-send-post-hook 'erc-save-buffer-in-logs)) | |
239 | (add-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs) | |
240 | (add-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs) | |
83dc6995 | 241 | (add-hook 'kill-emacs-hook 'erc-log-save-all-buffers) |
0b6bb130 MB |
242 | (add-hook 'erc-quit-hook 'erc-conditional-save-queries) |
243 | (add-hook 'erc-part-hook 'erc-conditional-save-buffer) | |
244 | ;; append, so that 'erc-initialize-log-marker runs first | |
2e3ef421 MB |
245 | (add-hook 'erc-connect-pre-hook 'erc-log-setup-logging 'append) |
246 | (dolist (buffer (erc-buffer-list)) | |
ff59d266 | 247 | (erc-log-setup-logging buffer))) |
597993cf | 248 | ;; disable |
0b6bb130 MB |
249 | ((remove-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs) |
250 | (remove-hook 'erc-send-post-hook 'erc-save-buffer-in-logs) | |
251 | (remove-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs) | |
252 | (remove-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs) | |
83dc6995 | 253 | (remove-hook 'kill-emacs-hook 'erc-log-save-all-buffers) |
0b6bb130 MB |
254 | (remove-hook 'erc-quit-hook 'erc-conditional-save-queries) |
255 | (remove-hook 'erc-part-hook 'erc-conditional-save-buffer) | |
2e3ef421 MB |
256 | (remove-hook 'erc-connect-pre-hook 'erc-log-setup-logging) |
257 | (dolist (buffer (erc-buffer-list)) | |
ff59d266 | 258 | (erc-log-disable-logging buffer)))) |
597993cf MB |
259 | |
260 | (define-key erc-mode-map "\C-c\C-l" 'erc-save-buffer-in-logs) | |
261 | ||
0b6bb130 | 262 | ;;; functionality referenced from erc.el |
ff59d266 | 263 | (defun erc-log-setup-logging (buffer) |
597993cf | 264 | "Setup the buffer-local logging variables in the current buffer. |
ff59d266 MB |
265 | This function is destined to be run from `erc-connect-pre-hook'. |
266 | The current buffer is given by BUFFER." | |
267 | (when (erc-logging-enabled buffer) | |
268 | (with-current-buffer buffer | |
269 | (auto-save-mode -1) | |
270 | (setq buffer-file-name nil) | |
88406d6e | 271 | (erc-set-write-file-functions '(erc-save-buffer-in-logs)) |
ff59d266 MB |
272 | (when erc-log-insert-log-on-open |
273 | (ignore-errors (insert-file-contents (erc-current-logfile)) | |
274 | (move-marker erc-last-saved-position | |
275 | (1- (point-max)))))))) | |
276 | ||
277 | (defun erc-log-disable-logging (buffer) | |
278 | "Disable logging in BUFFER." | |
279 | (when (erc-logging-enabled buffer) | |
280 | (with-current-buffer buffer | |
281 | (setq buffer-offer-save nil | |
282 | erc-enable-logging nil)))) | |
2e3ef421 | 283 | |
597993cf MB |
284 | (defun erc-log-all-but-server-buffers (buffer) |
285 | "Returns t if logging should be enabled in BUFFER. | |
81bb49ce | 286 | Returns nil if `erc-server-buffer-p' returns t." |
597993cf MB |
287 | (save-excursion |
288 | (save-window-excursion | |
289 | (set-buffer buffer) | |
290 | (not (erc-server-buffer-p))))) | |
291 | ||
292 | (defun erc-save-query-buffers (process) | |
83dc6995 | 293 | "Save all buffers of the given PROCESS." |
597993cf MB |
294 | (erc-with-all-buffers-of-server process |
295 | nil | |
296 | (erc-save-buffer-in-logs))) | |
297 | ||
298 | (defun erc-conditional-save-buffer (buffer) | |
299 | "Save Query BUFFER if `erc-save-queries-on-quit' is t." | |
300 | (when erc-save-buffer-on-part | |
301 | (erc-save-buffer-in-logs buffer))) | |
302 | ||
303 | (defun erc-conditional-save-queries (process) | |
304 | "Save Query buffers of PROCESS if `erc-save-queries-on-quit' is t." | |
305 | (when erc-save-queries-on-quit | |
306 | (erc-save-query-buffers process))) | |
307 | ||
83dc6995 MB |
308 | ;; Make sure that logs get saved, even if someone overrides the active |
309 | ;; process prompt for a quick exit from Emacs | |
310 | (defun erc-log-save-all-buffers () | |
311 | (dolist (buffer (erc-buffer-list)) | |
312 | (erc-save-buffer-in-logs buffer))) | |
313 | ||
597993cf MB |
314 | ;;;###autoload |
315 | (defun erc-logging-enabled (&optional buffer) | |
316 | "Return non-nil if logging is enabled for BUFFER. | |
317 | If BUFFER is nil, the value of `current-buffer' is used. | |
318 | Logging is enabled if `erc-log-channels-directory' is non-nil, the directory | |
cd1181db | 319 | is writable (it will be created as necessary) and |
597993cf MB |
320 | `erc-enable-logging' returns a non-nil value." |
321 | (and erc-log-channels-directory | |
526dc846 MO |
322 | (or (functionp erc-log-channels-directory) |
323 | (erc-directory-writable-p erc-log-channels-directory)) | |
597993cf MB |
324 | (if (functionp erc-enable-logging) |
325 | (funcall erc-enable-logging (or buffer (current-buffer))) | |
326 | erc-enable-logging))) | |
327 | ||
b5bc193f MB |
328 | (defun erc-log-standardize-name (filename) |
329 | "Make FILENAME safe to use as the name of an ERC log. | |
330 | This will not work with full paths, only names. | |
331 | ||
332 | Any unsafe characters in the name are replaced with \"!\". The | |
333 | filename is downcased." | |
334 | (downcase (erc-replace-regexp-in-string | |
335 | "[/\\]" "!" (convert-standard-filename filename)))) | |
336 | ||
597993cf MB |
337 | (defun erc-current-logfile (&optional buffer) |
338 | "Return the logfile to use for BUFFER. | |
339 | If BUFFER is nil, the value of `current-buffer' is used. | |
340 | This is determined by `erc-generate-log-file-name-function'. | |
341 | The result is converted to lowercase, as IRC is case-insensitive" | |
526dc846 MO |
342 | (unless buffer (setq buffer (current-buffer))) |
343 | (let ((target (or (buffer-name buffer) (erc-default-target))) | |
344 | (nick (erc-current-nick)) | |
345 | (server erc-session-server) | |
346 | (port erc-session-port)) | |
347 | (expand-file-name | |
348 | (erc-log-standardize-name | |
349 | (funcall erc-generate-log-file-name-function | |
350 | buffer target nick server port)) | |
351 | (if (functionp erc-log-channels-directory) | |
352 | (funcall erc-log-channels-directory | |
353 | buffer target nick server port) | |
354 | erc-log-channels-directory)))) | |
597993cf MB |
355 | |
356 | (defun erc-generate-log-file-name-with-date (buffer &rest ignore) | |
357 | "This function computes a short log file name. | |
358 | The name of the log file is composed of BUFFER and the current date. | |
359 | This function is a possible value for `erc-generate-log-file-name-function'." | |
360 | (concat (buffer-name buffer) "-" (format-time-string "%Y-%m-%d") ".txt")) | |
361 | ||
362 | (defun erc-generate-log-file-name-short (buffer &rest ignore) | |
363 | "This function computes a short log file name. | |
364 | In fact, it only uses the buffer name of the BUFFER argument, so | |
365 | you can affect that using `rename-buffer' and the-like. This | |
366 | function is a possible value for | |
367 | `erc-generate-log-file-name-function'." | |
368 | (concat (buffer-name buffer) ".txt")) | |
369 | ||
370 | (defun erc-generate-log-file-name-long (buffer target nick server port) | |
371 | "Generates a log-file name in the way ERC always did it. | |
372 | This results in a file name of the form #channel!nick@server:port.txt. | |
373 | This function is a possible value for `erc-generate-log-file-name-function'." | |
374 | (let ((file (concat | |
375 | (if target (concat target "!")) | |
376 | nick "@" server ":" (cond ((stringp port) port) | |
377 | ((numberp port) | |
378 | (number-to-string port))) ".txt"))) | |
379 | ;; we need a make-safe-file-name function. | |
380 | (convert-standard-filename file))) | |
381 | ||
f440830d GM |
382 | (declare-function erc-network-name "erc-networks" ()) |
383 | ||
ff59d266 MB |
384 | (defun erc-generate-log-file-name-network (buffer target nick server port) |
385 | "Generates a log-file name using the network name rather than server name. | |
386 | This results in a file name of the form #channel!nick@network.txt. | |
387 | This function is a possible value for `erc-generate-log-file-name-function'." | |
388 | (require 'erc-networks) | |
389 | (let ((file (concat | |
390 | (if target (concat target "!")) | |
391 | nick "@" | |
392 | (or (with-current-buffer buffer (erc-network-name)) server) | |
393 | ".txt"))) | |
394 | ;; we need a make-safe-file-name function. | |
395 | (convert-standard-filename file))) | |
396 | ||
597993cf MB |
397 | ;;;###autoload |
398 | (defun erc-save-buffer-in-logs (&optional buffer) | |
399 | "Append BUFFER contents to the log file, if logging is enabled. | |
400 | If BUFFER is not provided, current buffer is used. | |
401 | Logging is enabled if `erc-logging-enabled' returns non-nil. | |
402 | ||
403 | This is normally done on exit, to save the unsaved portion of the | |
404 | buffer, since only the text that runs off the buffer limit is logged | |
405 | automatically. | |
406 | ||
407 | You can save every individual message by putting this function on | |
408 | `erc-insert-post-hook'." | |
409 | (interactive) | |
410 | (or buffer (setq buffer (current-buffer))) | |
411 | (when (erc-logging-enabled buffer) | |
412 | (let ((file (erc-current-logfile buffer)) | |
b6675b2d MO |
413 | (coding-system erc-log-file-coding-system) |
414 | (inhibit-clash-detection t)) ; needed for XEmacs | |
597993cf MB |
415 | (save-excursion |
416 | (with-current-buffer buffer | |
417 | (save-restriction | |
418 | (widen) | |
d20cf916 | 419 | ;; early on in the initialization, don't try and write the log out |
597993cf MB |
420 | (when (and (markerp erc-last-saved-position) |
421 | (> erc-insert-marker (1+ erc-last-saved-position))) | |
d20cf916 MO |
422 | (let ((start (1+ (marker-position erc-last-saved-position))) |
423 | (end (marker-position erc-insert-marker))) | |
424 | (if (functionp erc-log-filter-function) | |
425 | (let ((text (buffer-substring start end))) | |
426 | (with-temp-buffer | |
427 | (insert (funcall erc-log-filter-function text)) | |
428 | (let ((coding-system-for-write coding-system)) | |
429 | (write-region (point-min) (point-max) | |
430 | file t 'nomessage)))) | |
431 | (let ((coding-system-for-write coding-system)) | |
432 | (write-region start end file t 'nomessage)))) | |
19dc7206 SM |
433 | (if (and erc-truncate-buffer-on-save |
434 | (called-interactively-p 'interactive)) | |
597993cf MB |
435 | (progn |
436 | (let ((inhibit-read-only t)) (erase-buffer)) | |
437 | (move-marker erc-last-saved-position (point-max)) | |
438 | (erc-display-prompt)) | |
439 | (move-marker erc-last-saved-position | |
440 | ;; If we place erc-last-saved-position at | |
441 | ;; erc-insert-marker, because text gets | |
442 | ;; inserted /before/ erc-insert-marker, | |
443 | ;; the log file will not be saved | |
444 | ;; (erc-last-saved-position will always | |
445 | ;; be equal to erc-insert-marker). | |
446 | (1- (marker-position erc-insert-marker))))) | |
447 | (set-buffer-modified-p nil)))))) | |
448 | t) | |
449 | ||
450 | (provide 'erc-log) | |
451 | ||
452 | ;;; erc-log.el ends here | |
453 | ;; | |
454 | ;; Local Variables: | |
455 | ;; indent-tabs-mode: t | |
456 | ;; tab-width: 8 | |
457 | ;; End: |