ERC: Sync from upstream.
[bpt/emacs.git] / lisp / erc / erc-dcc.el
CommitLineData
597993cf
MB
1;;; erc-dcc.el --- CTCP DCC module for ERC
2
8b72699e 3;; Copyright (C) 1993, 1994, 1995, 1998, 2002, 2003, 2004, 2006, 2007, 2008
597993cf
MB
4;; Free Software Foundation, Inc.
5
6;; Author: Ben A. Mesander <ben@gnu.ai.mit.edu>
7;; Noah Friedman <friedman@prep.ai.mit.edu>
8;; Per Persson <pp@sno.pp.se>
9;; Maintainer: mlang@delysid.org
10;; Keywords: comm, processes
11;; Created: 1994-01-23
12
13;; This file is part of GNU Emacs.
14
15;; GNU Emacs is free software; you can redistribute it and/or modify
16;; it under the terms of the GNU General Public License as published by
e0085d62 17;; the Free Software Foundation; either version 3, or (at your option)
597993cf
MB
18;; any later version.
19
20;; GNU Emacs is distributed in the hope that it will be useful,
21;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23;; GNU General Public License for more details.
24
25;; You should have received a copy of the GNU General Public License
26;; along with GNU Emacs; see the file COPYING. If not, write to the
27;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28;; Boston, MA 02110-1301, USA.
29
30;;; Commentary:
31
9cc8d0b6 32;; This file provides Direct Client-to-Client support for ERC.
597993cf
MB
33;;
34;; The original code was taken from zenirc-dcc.el, heavily mangled and
35;; rewritten to support the way how ERC operates. Server socket support
36;; was added for DCC CHAT and SEND afterwards. Thanks
37;; to the original authors for their work.
83dc6995
MB
38
39;;; Usage:
40
597993cf
MB
41;; To use this file, put
42;; (require 'erc-dcc)
43;; in your .emacs.
44;;
45;; Provided commands
46;; /dcc chat nick - Either accept pending chat offer from nick, or offer
47;; DCC chat to nick
48;; /dcc close type [nick] - Close DCC connection (SEND/GET/CHAT) with nick
49;; /dcc get nick [file] - Accept DCC offer from nick
50;; /dcc list - List all DCC offers/connections
51;; /dcc send nick file - Offer DCC SEND to nick
52;;
53;; Please note that offering DCC connections (offering chats and sending
83dc6995 54;; files) is only supported with Emacs 22.
597993cf
MB
55
56;;; Code:
57
58(require 'erc)
59(eval-when-compile
240029d9
MB
60 (require 'cl)
61 (require 'pcomplete))
597993cf 62
5e56b3fb
MO
63;;;###autoload (autoload 'erc-dcc-mode "erc-dcc")
64(define-erc-module dcc nil
65 "Provide Direct Client-to-Client support for ERC."
66 ((add-hook 'erc-server-401-functions 'erc-dcc-no-such-nick))
67 ((remove-hook 'erc-server-401-functions 'erc-dcc-no-such-nick)))
68
597993cf
MB
69(defgroup erc-dcc nil
70 "DCC stands for Direct Client Communication, where you and your
71friend's client programs connect directly to each other,
72bypassing IRC servers and their occasional \"lag\" or \"split\"
73problems. Like /MSG, the DCC chat is completely private.
74
75Using DCC get and send, you can transfer files directly from and to other
76IRC users."
77 :group 'erc)
78
5e56b3fb 79(defcustom erc-dcc-verbose nil
597993cf
MB
80 "*If non-nil, be verbose about DCC activity reporting."
81 :group 'erc-dcc
82 :type 'boolean)
83
84(defvar erc-dcc-list nil
85 "List of DCC connections. Looks like:
86 ((:nick \"nick!user@host\" :type GET :peer proc :parent proc :size size :file file)
87 (:nick \"nick!user@host\" :type CHAT :peer proc :parent proc)
88 (:nick \"nick\" :type SEND :peer server-proc :parent parent-proc :file
89 file :sent <marker> :confirmed <marker>))
90
91 :nick - a user or userhost for the peer. combine with :parent to reach them
92
93 :type - the type of DCC connection - SEND for outgoing files, GET for
94 incoming, and CHAT for both directions. To tell which end started
95 the DCC chat, look at :peer
96
97 :peer - the other end of the DCC connection. In the case of outgoing DCCs,
98 this represents a server process until a connection is established
99
100 :parent - the server process where the dcc connection was established.
101 Note that this can be nil or an invalid process since a DCC
102 connection is in general independent from a particular server
103 connection after it was established.
104
105 :file - for outgoing sends, the full path to the file. for incoming sends,
106 the suggested filename or vetted filename
107
108 :size - size of the file, may be nil on incoming DCCs")
109
110(defun erc-dcc-list-add (type nick peer parent &rest args)
111 "Add a new entry of type TYPE to `erc-dcc-list' and return it."
112 (car
113 (setq erc-dcc-list
114 (cons
115 (append (list :nick nick :type type :peer peer :parent parent) args)
116 erc-dcc-list))))
117
118;; This function takes all the usual args as open-network-stream, plus one
119;; more: the entry data from erc-dcc-list for this particular process.
120(defvar erc-dcc-connect-function 'erc-dcc-open-network-stream)
121
122(defun erc-dcc-open-network-stream (procname buffer addr port entry)
123 (if nil; (fboundp 'open-network-stream-nowait) ;; this currently crashes
124 ;; cvs emacs
125 (open-network-stream-nowait procname buffer addr port)
126 (open-network-stream procname buffer addr port)))
127
128(erc-define-catalog
129 'english
130 '((dcc-chat-discarded
131 . "DCC: previous chat request from %n (%u@%h) discarded")
132 (dcc-chat-ended . "DCC: chat with %n ended %t: %e")
133 (dcc-chat-no-request . "DCC: chat request from %n not found")
134 (dcc-chat-offered . "DCC: chat offered by %n (%u@%h:%p)")
135 (dcc-chat-offer . "DCC: offering chat to %n")
136 (dcc-chat-accept . "DCC: accepting chat from %n")
137 (dcc-chat-privmsg . "=%n= %m")
138 (dcc-closed . "DCC: Closed %T from %n")
139 (dcc-command-undefined
140 . "DCC: %c undefined subcommand. GET, CHAT and LIST are defined.")
141 (dcc-ctcp-errmsg . "DCC: `%s' is not a DCC subcommand known to this client")
142 (dcc-ctcp-unknown . "DCC: unknown dcc command `%q' from %n (%u@%h)")
143 (dcc-get-bytes-received . "DCC: %f: %b bytes received")
144 (dcc-get-complete
145 . "DCC: file %f transfer complete (%s bytes in %t seconds)")
146 (dcc-get-cmd-aborted . "DCC: Aborted getting %f from %n")
147 (dcc-get-file-too-long
148 . "DCC: %f: File longer than sender claimed; aborting transfer")
149 (dcc-get-notfound . "DCC: %n hasn't offered %f for DCC transfer")
150 (dcc-list-head . "DCC: From Type Active Size Filename")
151 (dcc-list-line . "DCC: -------- ---- ------ ------------ --------")
152 (dcc-list-item . "DCC: %-8n %-4t %-6a %-12s %f")
153 (dcc-list-end . "DCC: End of list.")
154 (dcc-malformed . "DCC: error: %n (%u@%h) sent malformed request: %q")
155 (dcc-privileged-port
156 . "DCC: possibly bogus request: %p is a privileged port.")
157 (dcc-request-bogus . "DCC: bogus dcc `%r' from %n (%u@%h)")
158 (dcc-send-finished . "DCC: SEND of %f to %n finished (size %s)")
159 (dcc-send-offered . "DCC: file %f offered by %n (%u@%h) (size %s)")
160 (dcc-send-offer . "DCC: offering %f to %n")))
161
162;;; Misc macros and utility functions
163
164(defun erc-dcc-member (&rest args)
165 "Return the first matching entry in `erc-dcc-list' which satisfies the
166constraints given as a plist in ARGS. Returns nil on no match.
167
168The property :nick is treated specially, if it contains a '!' character,
169it is treated as a nick!user@host string, and compared with the :nick property
170value of the individual elements using string-equal. Otherwise it is
171compared with `erc-nick-equal-p' which is IRC case-insensitive."
172 (let ((list erc-dcc-list)
173 result test)
174 ;; for each element in erc-dcc-list
175 (while (and list (not result))
176 (let ((elt (car list))
177 (prem args)
178 (cont t))
179 ;; loop through the constraints
180 (while (and prem cont)
181 (let ((prop (car prem))
182 (val (cadr prem)))
183 (setq prem (cddr prem)
184 ;; plist-member is a predicate in xemacs
185 test (and (plist-member elt prop)
186 (plist-get elt prop)))
187 ;; if the property exists and is equal, we continue, else, try the
188 ;; next element of the list
189 (or (and (eq prop :nick) (string-match "!" val)
190 test (string-equal test val))
191 (and (eq prop :nick)
192 test val
193 (erc-nick-equal-p
194 (erc-extract-nick test)
195 (erc-extract-nick val)))
196 ;; not a nick
197 (eq test val)
198 (setq cont nil))))
199 (if cont
200 (setq result elt)
201 (setq list (cdr list)))))
202 result))
203
5e56b3fb
MO
204(defun erc-pack-int (value)
205 "Convert an integer into a packed string."
206 (let* ((len (ceiling (/ value 256.0)))
207 (str (make-string len ?a))
208 (i (1- len)))
209 (while (>= i 0)
210 (aset str i (% value 256))
211 (setq value (/ value 256))
212 (setq i (1- i)))
213 str))
597993cf
MB
214
215(defun erc-unpack-int (str)
5e56b3fb 216 "Unpack a packed string into an integer."
597993cf
MB
217 (let ((len (length str))
218 (num 0)
219 (count 0))
597993cf
MB
220 (while (< count len)
221 (setq num (+ num (lsh (aref str (- len count 1)) (* 8 count))))
222 (setq count (1+ count)))
223 num))
224
225(defconst erc-dcc-ipv4-regexp
226 (concat "^"
227 (mapconcat #'identity (make-list 4 "\\([0-9]\\{1,3\\}\\)") "\\.")
228 "$"))
229
230(defun erc-ip-to-decimal (ip)
231 "Convert IP address to its decimal representation.
232Argument IP is the address as a string. The result is also a string."
233 (interactive "sIP Address: ")
234 (if (not (string-match erc-dcc-ipv4-regexp ip))
235 (error "Not an IP address")
236 (let* ((ips (mapcar
237 (lambda (str)
238 (let ((n (string-to-number str)))
239 (if (and (>= n 0) (< n 256))
240 n
241 (error "%d out of range" n))))
242 (split-string ip "\\.")))
243 (res (+ (* (car ips) 16777216.0)
244 (* (nth 1 ips) 65536.0)
245 (* (nth 2 ips) 256.0)
246 (nth 3 ips))))
247 (if (interactive-p)
248 (message "%s is %.0f" ip res)
249 (format "%.0f" res)))))
250
251(defun erc-decimal-to-ip (dec)
252 "Convert a decimal representation DEC to an IP address.
253The result is also a string."
254 (when (stringp dec)
255 (setq dec (string-to-number (concat dec ".0"))))
256 (let* ((first (floor (/ dec 16777216.0)))
257 (first-rest (- dec (* first 16777216.0)))
258 (second (floor (/ first-rest 65536.0)))
259 (second-rest (- first-rest (* second 65536.0)))
260 (third (floor (/ second-rest 256.0)))
261 (third-rest (- second-rest (* third 256.0)))
262 (fourth (floor third-rest)))
263 (format "%s.%s.%s.%s" first second third fourth)))
264
265;;; Server code
266
5e56b3fb
MO
267(defcustom erc-dcc-listen-host nil
268 "IP address to listen on when offering files.
269Should be set to a string or nil. If nil, automatic detection of
270the host interface to use will be attempted."
597993cf
MB
271 :group 'erc-dcc
272 :type (list 'choice (list 'const :tag "Auto-detect" nil)
273 (list 'string :tag "IP-address"
274 :valid-regexp erc-dcc-ipv4-regexp)))
275
5e56b3fb
MO
276(defcustom erc-dcc-public-host nil
277 "IP address to use for outgoing DCC offers.
278Should be set to a string or nil. If nil, use the value of
279`erc-dcc-listen-host'."
280 :group 'erc-dcc
281 :type (list 'choice (list 'const :tag "Same as erc-dcc-listen-host" nil)
282 (list 'string :tag "IP-address"
283 :valid-regexp erc-dcc-ipv4-regexp)))
284
597993cf
MB
285(defcustom erc-dcc-send-request 'ask
286 "*How to treat incoming DCC Send requests.
287'ask - Report the Send request, and wait for the user to manually accept it
288 You might want to set `erc-dcc-auto-masks' for this.
289'auto - Automatically accept the request and begin downloading the file
290'ignore - Ignore incoming DCC Send requests completely."
291 :group 'erc-dcc
292 :type '(choice (const ask) (const auto) (const ignore)))
293
294(defun erc-dcc-get-host (proc)
295 "Returns the local IP address used for an open PROCess."
296 (format-network-address (process-contact proc :local) t))
297
298(defun erc-dcc-host ()
299 "Determine the IP address we are using.
300If variable `erc-dcc-host' is non-nil, use it. Otherwise call
301`erc-dcc-get-host' on the erc-server-process."
5e56b3fb 302 (or erc-dcc-listen-host (erc-dcc-get-host erc-server-process)
597993cf
MB
303 (error "Unable to determine local address")))
304
305(defcustom erc-dcc-port-range nil
306 "If nil, any available user port is used for outgoing DCC connections.
307If set to a cons, it specifies a range of ports to use in the form (min . max)"
308 :group 'erc-dcc
309 :type '(choice
310 (const :tag "Any port" nil)
311 (cons :tag "Port range"
312 (integer :tag "Lower port")
313 (integer :tag "Upper port"))))
314
315(defcustom erc-dcc-auto-masks nil
316 "List of regexps matching user identifiers whose DCC send offers should be
317accepted automatically. A user identifier has the form \"nick!login@host\".
318For instance, to accept all incoming DCC send offers automatically, add the
319string \".*!.*@.*\" to this list."
320 :group 'erc-dcc
321 :type '(repeat regexp))
322
323(defun erc-dcc-server (name filter sentinel)
324 "Start listening on a port for an incoming DCC connection. Returns the newly
325created subprocess, or nil."
326 (let ((port (or (and erc-dcc-port-range (car erc-dcc-port-range)) t))
327 (upper (and erc-dcc-port-range (cdr erc-dcc-port-range)))
328 process)
329 (while (not process)
330 (condition-case err
5e56b3fb 331 (progn
597993cf
MB
332 (setq process
333 (make-network-process :name name
334 :buffer nil
335 :host (erc-dcc-host)
336 :service port
337 :nowait t
338 :noquery nil
339 :filter filter
340 :sentinel sentinel
341 :log #'erc-dcc-server-accept
342 :server t))
5e56b3fb
MO
343 (when (processp process)
344 (when (fboundp 'set-process-coding-system)
345 (set-process-coding-system process 'binary 'binary))
346 (when (fboundp 'set-process-filter-multibyte)
347 (set-process-filter-multibyte process nil))))
597993cf
MB
348 (file-error
349 (unless (and (string= "Cannot bind server socket" (cadr err))
350 (string= "address already in use" (caddr err)))
351 (signal (car err) (cdr err)))
352 (setq port (1+ port))
353 (unless (< port upper)
354 (error "No available ports in erc-dcc-port-range")))))
355 process))
356
357(defun erc-dcc-server-accept (server client message)
358 "Log an accepted DCC offer, then terminate the listening process and set up
359the accepted connection."
360 (erc-log (format "(erc-dcc-server-accept): server %s client %s message %s"
361 server client message))
362 (when (and (string-match "^accept from " message)
363 (processp server) (processp client))
364 (let ((elt (erc-dcc-member :peer server)))
365 ;; change the entry in erc-dcc-list from the listening process to the
366 ;; accepted process
367 (setq elt (plist-put elt :peer client))
368 ;; delete the listening process, as we've accepted the connection
369 (delete-process server))))
370
371;;; Interactive command handling
372
373(defcustom erc-dcc-get-default-directory nil
374 "*Default directory for incoming DCC file transfers.
375If this is nil, then the current value of `default-directory' is used."
376 :group 'erc-dcc
377 :type '(choice (const nil :tag "Default directory") directory))
378
379;;;###autoload
380(defun erc-cmd-DCC (cmd &rest args)
381 "Parser for /dcc command.
382This figures out the dcc subcommand and calls the appropriate routine to
383handle it. The function dispatched should be named \"erc-dcc-do-FOO-command\",
384where FOO is one of CLOSE, GET, SEND, LIST, CHAT, etc."
385 (when cmd
386 (let ((fn (intern-soft (concat "erc-dcc-do-" (upcase cmd) "-command"))))
387 (if fn
388 (apply fn erc-server-process args)
389 (erc-display-message
390 nil 'notice 'active
391 'dcc-command-undefined ?c cmd)
392 (apropos "erc-dcc-do-.*-command")
393 t))))
394
395;;;###autoload
396(defun pcomplete/erc-mode/DCC ()
397 "Provides completion for the /DCC command."
398 (pcomplete-here (append '("chat" "close" "get" "list")
399 (when (fboundp 'make-network-process) '("send"))))
400 (pcomplete-here
401 (case (intern (downcase (pcomplete-arg 1)))
402 (chat (mapcar (lambda (elt) (plist-get elt :nick))
403 (erc-remove-if-not
404 #'(lambda (elt)
405 (eq (plist-get elt :type) 'CHAT))
406 erc-dcc-list)))
407 (close (remove-duplicates
408 (mapcar (lambda (elt) (symbol-name (plist-get elt :type)))
409 erc-dcc-list) :test 'string=))
410 (get (mapcar #'erc-dcc-nick
411 (erc-remove-if-not
412 #'(lambda (elt)
413 (eq (plist-get elt :type) 'GET))
414 erc-dcc-list)))
415 (send (pcomplete-erc-all-nicks))))
416 (pcomplete-here
417 (case (intern (downcase (pcomplete-arg 2)))
418 (get (mapcar (lambda (elt) (plist-get elt :file))
419 (erc-remove-if-not
420 #'(lambda (elt)
421 (and (eq (plist-get elt :type) 'GET)
422 (erc-nick-equal-p (erc-extract-nick
423 (plist-get elt :nick))
424 (pcomplete-arg 1))))
425 erc-dcc-list)))
426 (close (mapcar #'erc-dcc-nick
427 (erc-remove-if-not
428 #'(lambda (elt)
429 (eq (plist-get elt :type)
430 (intern (upcase (pcomplete-arg 1)))))
431 erc-dcc-list)))
432 (send (pcomplete-entries)))))
433
434(defun erc-dcc-do-CHAT-command (proc &optional nick)
435 (when nick
436 (let ((elt (erc-dcc-member :nick nick :type 'CHAT :parent proc)))
437 (if (and elt (not (processp (plist-get elt :peer))))
438 ;; accept an existing chat offer
439 ;; FIXME: perhaps /dcc accept like other clients?
440 (progn (erc-dcc-chat-accept elt erc-server-process)
441 (erc-display-message
442 nil 'notice 'active
443 'dcc-chat-accept ?n nick)
444 t)
445 (erc-dcc-chat nick erc-server-process)
446 (erc-display-message
447 nil 'notice 'active
448 'dcc-chat-offer ?n nick)
449 t))))
450
451(defun erc-dcc-do-CLOSE-command (proc &optional type nick)
452 "/dcc close type nick
453type and nick are optional."
454 ;; FIXME, should also work if only nick is specified
455 (when (string-match (concat "^\\s-*\\(\\S-+\\)? *\\("
456 erc-valid-nick-regexp "\\)?\\s-*$") line)
457 (let ((type (when (match-string 1 line)
458 (intern (upcase (match-string 1 line)))))
459 (nick (match-string 2 line))
460 (ret t))
461 (while ret
462 (if nick
463 (setq ret (erc-dcc-member :type type :nick nick))
464 (setq ret (erc-dcc-member :type type)))
465 (when ret
466 ;; found a match - delete process if it exists.
467 (and (processp (plist-get ret :peer))
468 (delete-process (plist-get ret :peer)))
469 (setq erc-dcc-list (delq ret erc-dcc-list))
470 (erc-display-message
471 nil 'notice 'active
472 'dcc-closed
473 ?T (plist-get ret :type)
474 ?n (erc-extract-nick (plist-get ret :nick))))))
475 t))
476
2a927019
MO
477(defun erc-dcc-do-GET-command (proc nick &rest file)
478 "Do a DCC GET command. NICK is the person who is sending the file.
479FILE is the filename. If FILE is split into multiple arguments,
480re-join the arguments, separated by a space.
481PROC is the server process."
482 (setq file (and file (mapconcat #'identity file " ")))
597993cf
MB
483 (let* ((elt (erc-dcc-member :nick nick :type 'GET))
484 (filename (or file (plist-get elt :file) "unknown")))
485 (if elt
486 (let* ((file (read-file-name
487 (format "Local filename (default %s): "
488 (file-name-nondirectory filename))
489 (or erc-dcc-get-default-directory
490 default-directory)
491 (expand-file-name (file-name-nondirectory filename)
492 (or erc-dcc-get-default-directory
493 default-directory)))))
494 (cond ((file-exists-p file)
495 (if (yes-or-no-p (format "File %s exists. Overwrite? "
496 file))
497 (erc-dcc-get-file elt file proc)
498 (erc-display-message
499 nil '(notice error) proc
500 'dcc-get-cmd-aborted
501 ?n nick ?f filename)))
502 (t
503 (erc-dcc-get-file elt file proc))))
504 (erc-display-message
505 nil '(notice error) 'active
506 'dcc-get-notfound ?n nick ?f filename))))
507
508(defun erc-dcc-do-LIST-command (proc)
509 "This is the handler for the /dcc list command.
510It lists the current state of `erc-dcc-list' in an easy to read manner."
511 (let ((alist erc-dcc-list)
512 size elt)
513 (erc-display-message
514 nil 'notice 'active
515 'dcc-list-head)
516 (erc-display-message
517 nil 'notice 'active
518 'dcc-list-line)
519 (while alist
520 (setq elt (car alist)
521 alist (cdr alist))
522
523 (setq size (or (and (plist-member elt :size)
524 (plist-get elt :size))
525 ""))
526 (setq size
527 (cond ((null size) "")
528 ((numberp size) (number-to-string size))
529 ((string= size "") "unknown")))
530 (erc-display-message
531 nil 'notice 'active
532 'dcc-list-item
533 ?n (erc-dcc-nick elt)
534 ?t (plist-get elt :type)
535 ?a (if (processp (plist-get elt :peer))
536 (process-status (plist-get elt :peer))
537 "no")
538 ?s (concat size
539 (if (and (eq 'GET (plist-get elt :type))
540 (plist-member elt :file)
541 (buffer-live-p (get-buffer (plist-get elt :file)))
542 (plist-member elt :size))
543 (concat " (" (number-to-string
544 (* 100
545 (/ (buffer-size
546 (get-buffer (plist-get elt :file)))
547 (plist-get elt :size))))
548 "%)")))
549 ?f (or (and (plist-member elt :file) (plist-get elt :file)) "")))
550 (erc-display-message
551 nil 'notice 'active
552 'dcc-list-end)
553 t))
554
2a927019
MO
555(defun erc-dcc-do-SEND-command (proc nick &rest file)
556 "Offer FILE to NICK by sending a ctcp dcc send message.
557If FILE is split into multiple arguments, re-join the arguments,
558separated by a space."
559 (setq file (and file (mapconcat #'identity file " ")))
597993cf
MB
560 (if (file-exists-p file)
561 (progn
562 (erc-display-message
563 nil 'notice 'active
564 'dcc-send-offer ?n nick ?f file)
565 (erc-dcc-send-file nick file) t)
566 (erc-display-message nil '(notice error) proc "File not found") t))
567
568;;; Server message handling (i.e. messages from remote users)
569
570;;;###autoload
571(defvar erc-ctcp-query-DCC-hook '(erc-ctcp-query-DCC)
572 "Hook variable for CTCP DCC queries")
573
574(defvar erc-dcc-query-handler-alist
575 '(("SEND" . erc-dcc-handle-ctcp-send)
576 ("CHAT" . erc-dcc-handle-ctcp-chat)))
577
578;;;###autoload
579(defun erc-ctcp-query-DCC (proc nick login host to query)
580 "The function called when a CTCP DCC request is detected by the client.
581It examines the DCC subcommand, and calls the appropriate routine for
582that subcommand."
583 (let* ((cmd (cadr (split-string query " ")))
584 (handler (cdr (assoc cmd erc-dcc-query-handler-alist))))
585 (if handler
586 (funcall handler proc query nick login host to)
587 ;; FIXME: Send a ctcp error notice to the remote end?
588 (erc-display-message
589 nil '(notice error) proc
590 'dcc-ctcp-unknown
591 ?q query ?n nick ?u login ?h host))))
592
593(defconst erc-dcc-ctcp-query-send-regexp
594 "^DCC SEND \\([^ ]+\\) \\([0-9]+\\) \\([0-9]+\\) *\\([0-9]*\\)")
595
596(defun erc-dcc-handle-ctcp-send (proc query nick login host to)
597 "This is called if a CTCP DCC SEND subcommand is sent to the client.
598It extracts the information about the dcc request and adds it to
599`erc-dcc-list'."
600 (unless (eq erc-dcc-send-request 'ignore)
601 (cond
602 ((not (erc-current-nick-p to))
603 ;; DCC SEND requests must be sent to you, and you alone.
604 (erc-display-message
605 nil 'notice proc
606 'dcc-request-bogus
607 ?r "SEND" ?n nick ?u login ?h host))
608 ((string-match erc-dcc-ctcp-query-send-regexp query)
609 (let ((filename (match-string 1 query))
610 (ip (erc-decimal-to-ip (match-string 2 query)))
611 (port (match-string 3 query))
612 (size (match-string 4 query)))
613 ;; FIXME: a warning really should also be sent
614 ;; if the ip address != the host the dcc sender is on.
615 (erc-display-message
616 nil 'notice proc
617 'dcc-send-offered
618 ?f filename ?n nick ?u login ?h host
619 ?s (if (string= size "") "unknown" size))
620 (and (< (string-to-number port) 1025)
621 (erc-display-message
622 nil 'notice proc
623 'dcc-privileged-port
624 ?p port))
625 (erc-dcc-list-add
626 'GET (format "%s!%s@%s" nick login host)
627 nil proc
628 :ip ip :port port :file filename
629 :size (string-to-number size))
630 (if (and (eq erc-dcc-send-request 'auto)
631 (erc-dcc-auto-mask-p (format "\"%s!%s@%s\"" nick login host)))
632 (erc-dcc-get-file (car erc-dcc-list) filename proc))))
633 (t
634 (erc-display-message
635 nil 'notice proc
636 'dcc-malformed
637 ?n nick ?u login ?h host ?q query)))))
638
639(defun erc-dcc-auto-mask-p (spec)
640 "Takes a full SPEC of a user in the form \"nick!login@host\" and
641matches against all the regexp's in `erc-dcc-auto-masks'. If any
642match, returns that regexp and nil otherwise."
643 (let ((lst erc-dcc-auto-masks))
644 (while (and lst
645 (not (string-match (car lst) spec)))
646 (setq lst (cdr lst)))
647 (and lst (car lst))))
648
649(defconst erc-dcc-ctcp-query-chat-regexp
650 "^DCC CHAT +chat +\\([0-9]+\\) +\\([0-9]+\\)")
651
652(defcustom erc-dcc-chat-request 'ask
653 "*How to treat incoming DCC Chat requests.
654'ask - Report the Chat request, and wait for the user to manually accept it
655'auto - Automatically accept the request and open a new chat window
656'ignore - Ignore incoming DCC chat requests completely."
657 :group 'erc-dcc
658 :type '(choice (const ask) (const auto) (const ignore)))
659
660(defun erc-dcc-handle-ctcp-chat (proc query nick login host to)
661 (unless (eq erc-dcc-chat-request 'ignore)
662 (cond
663 (;; DCC CHAT requests must be sent to you, and you alone.
664 (not (erc-current-nick-p to))
665 (erc-display-message
666 nil '(notice error) proc
667 'dcc-request-bogus ?r "CHAT" ?n nick ?u login ?h host))
668 ((string-match erc-dcc-ctcp-query-chat-regexp query)
669 ;; We need to use let* here, since erc-dcc-member might clutter
670 ;; the match value.
671 (let* ((ip (erc-decimal-to-ip (match-string 1 query)))
672 (port (match-string 2 query))
673 (elt (erc-dcc-member :nick nick :type 'CHAT)))
674 ;; FIXME: A warning really should also be sent if the ip
675 ;; address != the host the dcc sender is on.
676 (erc-display-message
677 nil 'notice proc
678 'dcc-chat-offered
679 ?n nick ?u login ?h host ?p port)
680 (and (< (string-to-number port) 1025)
681 (erc-display-message
682 nil 'notice proc
683 'dcc-privileged-port ?p port))
684 (cond (elt
685 ;; XXX: why are we updating ip/port on the existing connection?
686 (setq elt (plist-put (plist-put elt :port port) :ip ip))
687 (erc-display-message
688 nil 'notice proc
689 'dcc-chat-discarded ?n nick ?u login ?h host))
690 (t
691 (erc-dcc-list-add
692 'CHAT (format "%s!%s@%s" nick login host)
693 nil proc
694 :ip ip :port port)))
695 (if (eq erc-dcc-chat-request 'auto)
696 (erc-dcc-chat-accept (erc-dcc-member :nick nick :type 'CHAT)
697 proc))))
698 (t
699 (erc-display-message
700 nil '(notice error) proc
701 'dcc-malformed ?n nick ?u login ?h host ?q query)))))
702
703
704(defvar erc-dcc-entry-data nil
705 "Holds the `erc-dcc-list' entry for this DCC connection.")
706(make-variable-buffer-local 'erc-dcc-entry-data)
707
708;;; SEND handling
709
710(defcustom erc-dcc-block-size 1024
711 "*Block size to use for DCC SEND sessions."
712 :group 'erc-dcc
713 :type 'integer)
714
715(defcustom erc-dcc-pump-bytes nil
716 "*If set to an integer, keep sending until that number of bytes are
717unconfirmed."
718 :group 'erc-dcc
719 :type '(choice (const nil) integer))
720
721(defsubst erc-dcc-get-parent (proc)
722 (plist-get (erc-dcc-member :peer proc) :parent))
723
724(defun erc-dcc-send-block (proc)
725 "Send one block of data.
726PROC is the process-object of the DCC connection. Returns the number of
727bytes sent."
728 (let* ((elt (erc-dcc-member :peer proc))
729 (confirmed-marker (plist-get elt :sent))
730 (sent-marker (plist-get elt :sent)))
731 (with-current-buffer (process-buffer proc)
5e56b3fb 732 (when erc-dcc-verbose
597993cf
MB
733 (erc-display-message
734 nil 'notice (erc-dcc-get-parent proc)
735 (format "DCC: Confirmed %d, sent %d, sending block now"
736 (- confirmed-marker (point-min))
737 (- sent-marker (point-min)))))
738 (let* ((end (min (+ sent-marker erc-dcc-block-size)
739 (point-max)))
740 (string (buffer-substring-no-properties sent-marker end)))
741 (when (< sent-marker end)
742 (set-marker sent-marker end)
743 (process-send-string proc string))
744 (length string)))))
745
746(defun erc-dcc-send-filter (proc string)
5e56b3fb 747 (let* ((size (erc-unpack-int string))
597993cf
MB
748 (elt (erc-dcc-member :peer proc))
749 (parent (plist-get elt :parent))
750 (sent-marker (plist-get elt :sent))
751 (confirmed-marker (plist-get elt :confirmed)))
752 (with-current-buffer (process-buffer proc)
753 (set-marker confirmed-marker (+ (point-min) size))
754 (cond
755 ((and (= confirmed-marker sent-marker)
756 (= confirmed-marker (point-max)))
757 (erc-display-message
758 nil 'notice parent
759 'dcc-send-finished
760 ?n (plist-get elt :nick)
761 ?f buffer-file-name
762 ?s (number-to-string (- sent-marker (point-min))))
763 (setq erc-dcc-list (delete elt erc-dcc-list))
764 (set-buffer-modified-p nil)
765 (kill-buffer (current-buffer))
766 (delete-process proc))
767 ((<= confirmed-marker sent-marker)
768 (while (and (< (- sent-marker confirmed-marker)
769 (or erc-dcc-pump-bytes
770 erc-dcc-block-size))
771 (> (erc-dcc-send-block proc) 0))))
772 ((> confirmed-marker sent-marker)
773 (erc-display-message
774 nil 'notice parent
5e56b3fb
MO
775 (format "DCC: Client confirmed too much (%s vs %s)!"
776 (marker-position confirmed-marker)
777 (marker-position sent-marker)))
778 (set-buffer-modified-p nil)
779 (kill-buffer (current-buffer))
597993cf
MB
780 (delete-process proc))))))
781
5e56b3fb
MO
782(defun erc-dcc-display-send (proc)
783 (erc-display-message
784 nil 'notice (erc-dcc-get-parent proc)
785 (format "DCC: SEND connect from %s"
786 (format-network-address (process-contact proc :remote)))))
787
597993cf 788(defcustom erc-dcc-send-connect-hook
5e56b3fb 789 '(erc-dcc-display-send erc-dcc-send-block)
597993cf
MB
790 "*Hook run whenever the remote end of a DCC SEND offer connected to your
791listening port."
792 :group 'erc-dcc
793 :type 'hook)
794
795(defun erc-dcc-nick (plist)
796 "Extract the nickname portion of the :nick property value in PLIST."
797 (erc-extract-nick (plist-get plist :nick)))
798
799(defun erc-dcc-send-sentinel (proc event)
5e56b3fb 800 (let* ((elt (erc-dcc-member :peer proc)))
597993cf
MB
801 (cond
802 ((string-match "^open from " event)
803 (when elt
5e56b3fb
MO
804 (let ((buf (marker-buffer (plist-get elt :sent))))
805 (with-current-buffer buf
806 (set-process-buffer proc buf)
807 (setq erc-dcc-entry-data elt)))
597993cf
MB
808 (run-hook-with-args 'erc-dcc-send-connect-hook proc))))))
809
810(defun erc-dcc-find-file (file)
811 (with-current-buffer (generate-new-buffer (file-name-nondirectory file))
812 (insert-file-contents-literally file)
813 (setq buffer-file-name file)
814 (current-buffer)))
815
816(defun erc-dcc-file-to-name (file)
817 (with-temp-buffer
818 (insert (file-name-nondirectory file))
819 (subst-char-in-region (point-min) (point-max) ? ?_ t)
820 (buffer-string)))
821
822(defun erc-dcc-send-file (nick file &optional pproc)
823 "Open a socket for incoming connections, and send a CTCP send request to the
824other client."
825 (interactive "sNick: \nfFile: ")
826 (when (null pproc) (if (processp erc-server-process)
827 (setq pproc erc-server-process)
828 (error "Can not find parent process")))
829 (if (featurep 'make-network-process)
830 (let* ((buffer (erc-dcc-find-file file))
831 (size (buffer-size buffer))
832 (start (with-current-buffer buffer
833 (set-marker (make-marker) (point-min))))
834 (sproc (erc-dcc-server "dcc-send"
835 'erc-dcc-send-filter
836 'erc-dcc-send-sentinel))
837 (contact (process-contact sproc)))
838 (erc-dcc-list-add
839 'SEND nick sproc pproc
840 :file file :size size
841 :sent start :confirmed (copy-marker start))
842 (process-send-string
843 pproc (format "PRIVMSG %s :\C-aDCC SEND %s %s %d %d\C-a\n"
844 nick (erc-dcc-file-to-name file)
5e56b3fb
MO
845 (erc-ip-to-decimal (or erc-dcc-public-host
846 (nth 0 contact)))
597993cf
MB
847 (nth 1 contact)
848 size)))
c6e43135 849 (error "`make-network-process' not supported by your Emacs")))
597993cf
MB
850
851;;; GET handling
852
5e56b3fb
MO
853(defcustom erc-dcc-receive-cache (* 1024 512)
854 "Number of bytes to let the receive buffer grow before flushing it."
855 :group 'erc-dcc
856 :type 'integer)
857
597993cf
MB
858(defvar erc-dcc-byte-count nil)
859(make-variable-buffer-local 'erc-dcc-byte-count)
5e56b3fb
MO
860(defvar erc-dcc-file-name nil)
861(make-variable-buffer-local 'erc-dcc-file-name)
597993cf
MB
862
863(defun erc-dcc-get-file (entry file parent-proc)
864 "This function does the work of setting up a transfer from the remote client
865to the local one over a tcp connection. This involves setting up a process
866filter and a process sentinel, and making the connection."
867 (let* ((buffer (generate-new-buffer (file-name-nondirectory file)))
868 proc)
869 (with-current-buffer buffer
870 (fundamental-mode)
5e56b3fb 871 (buffer-disable-undo (current-buffer))
597993cf
MB
872 ;; This is necessary to have the buffer saved as-is in GNU
873 ;; Emacs.
874 ;; XEmacs change: We don't have `set-buffer-multibyte', setting
875 ;; coding system to 'binary below takes care of us.
876 (when (fboundp 'set-buffer-multibyte)
877 (set-buffer-multibyte nil))
878
879 (setq mode-line-process '(":%s")
880 buffer-file-type t
881 buffer-read-only t)
5e56b3fb
MO
882 (setq erc-dcc-file-name file)
883
884 ;; Truncate the given file to size 0 before appending to it.
885 (write-region (point) (point) erc-dcc-file-name nil 'nomessage)
597993cf
MB
886
887 (setq erc-server-process parent-proc
888 erc-dcc-entry-data entry)
889 (setq erc-dcc-byte-count 0)
890 (setq proc
891 (funcall erc-dcc-connect-function
892 "dcc-get" buffer
893 (plist-get entry :ip)
894 (string-to-number (plist-get entry :port))
895 entry))
896 (set-process-buffer proc buffer)
597993cf
MB
897 (set-process-coding-system proc 'binary 'binary)
898 (set-buffer-file-coding-system 'binary t)
899
900 (set-process-filter proc 'erc-dcc-get-filter)
901 (set-process-sentinel proc 'erc-dcc-get-sentinel)
902 (setq entry (plist-put entry :start-time (erc-current-time)))
903 (setq entry (plist-put entry :peer proc)))))
904
5e56b3fb
MO
905(defun erc-dcc-append-contents (buffer file)
906 "Append the contents of BUFFER to FILE.
907The contents of the BUFFER will then be erased."
908 (with-current-buffer buffer
909 (let ((coding-system-for-write 'binary))
910 (write-region (point-min) (point-max) erc-dcc-file-name t 'nomessage)
911 (erase-buffer))))
912
597993cf
MB
913(defun erc-dcc-get-filter (proc str)
914 "This is the process filter for transfers from other clients to this one.
915It reads incoming bytes from the network and stores them in the DCC
916buffer, and sends back the replies after each block of data per the DCC
917protocol spec. Well not really. We write back a reply after each read,
918rather than every 1024 byte block, but nobody seems to care."
919 (with-current-buffer (process-buffer proc)
6904f7fe
MB
920 (let ((inhibit-read-only t))
921 (goto-char (point-max))
922 (insert (string-make-unibyte str))
923
924 (setq erc-dcc-byte-count (+ (length str) erc-dcc-byte-count))
5e56b3fb
MO
925 (when (> (point-max) erc-dcc-receive-cache)
926 (erc-dcc-append-contents (current-buffer) erc-dcc-file-name))
927
928 (and erc-dcc-verbose
6904f7fe
MB
929 (erc-display-message
930 nil 'notice erc-server-process
931 'dcc-get-bytes-received
932 ?f (file-name-nondirectory buffer-file-name)
933 ?b (number-to-string erc-dcc-byte-count)))
934 (cond
935 ((and (> (plist-get erc-dcc-entry-data :size) 0)
936 (> erc-dcc-byte-count (plist-get erc-dcc-entry-data :size)))
937 (erc-display-message
938 nil '(error notice) 'active
939 'dcc-get-file-too-long
940 ?f (file-name-nondirectory buffer-file-name))
941 (delete-process proc))
942 (t
943 (process-send-string
5e56b3fb 944 proc (erc-pack-int erc-dcc-byte-count)))))))
597993cf
MB
945
946
947(defun erc-dcc-get-sentinel (proc event)
948 "This is the process sentinel for CTCP DCC SEND connections.
949It shuts down the connection and notifies the user that the
950transfer is complete."
951 ;; FIXME, we should look at EVENT, and also check size.
952 (with-current-buffer (process-buffer proc)
953 (delete-process proc)
597993cf 954 (setq erc-dcc-list (delete erc-dcc-entry-data erc-dcc-list))
5e56b3fb
MO
955 (unless (= (point-min) (point-max))
956 (setq erc-dcc-byte-count (+ (buffer-size) erc-dcc-byte-count))
957 (erc-dcc-append-contents (current-buffer) erc-dcc-file-name))
597993cf
MB
958 (erc-display-message
959 nil 'notice erc-server-process
960 'dcc-get-complete
5e56b3fb
MO
961 ?f erc-dcc-file-name
962 ?s (number-to-string erc-dcc-byte-count)
597993cf
MB
963 ?t (format "%.0f"
964 (erc-time-diff (plist-get erc-dcc-entry-data :start-time)
5e56b3fb 965 (erc-current-time)))))
597993cf
MB
966 (kill-buffer (process-buffer proc))
967 (delete-process proc))
968
969;;; CHAT handling
970
971(defcustom erc-dcc-chat-buffer-name-format "DCC-CHAT-%s"
972 "*Format to use for DCC Chat buffer names."
973 :group 'erc-dcc
974 :type 'string)
975
976(defcustom erc-dcc-chat-mode-hook nil
977 "*Hook calls when `erc-dcc-chat-mode' finished setting up the buffer."
978 :group 'erc-dcc
979 :type 'hook)
980
981(defcustom erc-dcc-chat-connect-hook nil
982 ""
983 :group 'erc-dcc
984 :type 'hook)
985
986(defcustom erc-dcc-chat-exit-hook nil
987 ""
988 :group 'erc-dcc
989 :type 'hook)
990
991(defun erc-cmd-CREQ (line &optional force)
992 "Set or get the DCC chat request flag.
993Possible values are: ask, auto, ignore."
994 (when (string-match "^\\s-*\\(auto\\|ask\\|ignore\\)?$" line)
995 (let ((cmd (match-string 1 line)))
996 (if (stringp cmd)
997 (erc-display-message
998 nil 'notice 'active
999 (format "Set DCC Chat requests to %S"
1000 (setq erc-dcc-chat-request (intern cmd))))
1001 (erc-display-message nil 'notice 'active
1002 (format "DCC Chat requests are set to %S"
1003 erc-dcc-chat-request)))
1004 t)))
1005
1006(defun erc-cmd-SREQ (line &optional force)
1007 "Set or get the DCC send request flag.
1008Possible values are: ask, auto, ignore."
1009 (when (string-match "^\\s-*\\(auto\\|ask\\|ignore\\)?$" line)
1010 (let ((cmd (match-string 1 line)))
1011 (if (stringp cmd)
1012 (erc-display-message
1013 nil 'notice 'active
1014 (format "Set DCC Send requests to %S"
1015 (setq erc-dcc-send-request (intern cmd))))
1016 (erc-display-message nil 'notice 'active
1017 (format "DCC Send requests are set to %S"
1018 erc-dcc-send-request)))
1019 t)))
1020
1021(defun pcomplete/erc-mode/CREQ ()
1022 (pcomplete-here '("auto" "ask" "ignore")))
1023(defalias 'pcomplete/erc-mode/SREQ 'pcomplete/erc-mode/CREQ)
1024
1025(defvar erc-dcc-chat-filter-hook '(erc-dcc-chat-parse-output)
1026 "*Hook to run after doing parsing (and possible insertion) of DCC messages.")
1027
1028(defvar erc-dcc-chat-mode-map
1029 (let ((map (make-sparse-keymap)))
1030 (define-key map (kbd "RET") 'erc-send-current-line)
1031 (define-key map "\t" 'erc-complete-word)
1032 map)
1033 "Keymap for `erc-dcc-mode'.")
1034
1035(defun erc-dcc-chat-mode ()
1036 "Major mode for wasting time via DCC chat."
1037 (interactive)
1038 (kill-all-local-variables)
1039 (setq mode-line-process '(":%s")
1040 mode-name "DCC-Chat"
1041 major-mode 'erc-dcc-chat-mode
1042 erc-send-input-line-function 'erc-dcc-chat-send-input-line
1043 erc-default-recipients '(dcc))
1044 (use-local-map erc-dcc-chat-mode-map)
1045 (run-hooks 'erc-dcc-chat-mode-hook))
1046
1047(defun erc-dcc-chat-send-input-line (recipient line &optional force)
1048 "Send LINE to the remote end.
1049Argument RECIPIENT should always be the symbol dcc, and force
1050is ignored."
1051 ;; FIXME: We need to get rid of all force arguments one day!
1052 (if (eq recipient 'dcc)
1053 (process-send-string
1054 (get-buffer-process (current-buffer)) line)
1055 (error "erc-dcc-chat-send-input-line in %s" (current-buffer))))
1056
1057(defun erc-dcc-chat (nick &optional pproc)
1058 "Open a socket for incoming connections, and send a chat request to the
1059other client."
1060 (interactive "sNick: ")
1061 (when (null pproc) (if (processp erc-server-process)
1062 (setq pproc erc-server-process)
1063 (error "Can not find parent process")))
1064 (let* ((sproc (erc-dcc-server "dcc-chat-out"
1065 'erc-dcc-chat-filter
1066 'erc-dcc-chat-sentinel))
1067 (contact (process-contact sproc)))
1068 (erc-dcc-list-add 'OCHAT nick sproc pproc)
1069 (process-send-string pproc
1070 (format "PRIVMSG %s :\C-aDCC CHAT chat %s %d\C-a\n"
1071 nick
1072 (erc-ip-to-decimal (nth 0 contact)) (nth 1 contact)))))
1073
1074(defvar erc-dcc-from)
1075(make-variable-buffer-local 'erc-dcc-from)
1076
1077(defvar erc-dcc-unprocessed-output)
1078(make-variable-buffer-local 'erc-dcc-unprocessed-output)
1079
1080(defun erc-dcc-chat-setup (entry)
1081 "Setup a DCC chat buffer, returning the buffer."
1082 (let* ((nick (erc-extract-nick (plist-get entry :nick)))
1083 (buffer (generate-new-buffer
1084 (format erc-dcc-chat-buffer-name-format nick)))
1085 (proc (plist-get entry :peer))
1086 (parent-proc (plist-get entry :parent)))
1087 (erc-setup-buffer buffer)
1088 ;; buffer is now the current buffer.
1089 (erc-dcc-chat-mode)
1090 (setq erc-server-process parent-proc)
1091 (setq erc-dcc-from nick)
1092 (setq erc-dcc-entry-data entry)
1093 (setq erc-dcc-unprocessed-output "")
1094 (setq erc-insert-marker (set-marker (make-marker) (point-max)))
cb0a26d3 1095 (setq erc-input-marker (make-marker))
597993cf
MB
1096 (erc-display-prompt buffer (point-max))
1097 (set-process-buffer proc buffer)
1098 (add-hook 'kill-buffer-hook 'erc-dcc-chat-buffer-killed nil t)
1099 (run-hook-with-args 'erc-dcc-chat-connect-hook proc)
1100 buffer))
1101
1102(defun erc-dcc-chat-accept (entry parent-proc)
1103 "Accept an incoming DCC connection and open a DCC window"
1104 (let* ((nick (erc-extract-nick (plist-get entry :nick)))
1105 buffer proc)
1106 (setq proc
1107 (funcall erc-dcc-connect-function
1108 "dcc-chat" nil
1109 (plist-get entry :ip)
1110 (string-to-number (plist-get entry :port))
1111 entry))
1112 ;; XXX: connected, should we kill the ip/port properties?
1113 (setq entry (plist-put entry :peer proc))
1114 (setq entry (plist-put entry :parent parent-proc))
1115 (set-process-filter proc 'erc-dcc-chat-filter)
1116 (set-process-sentinel proc 'erc-dcc-chat-sentinel)
1117 (setq buffer (erc-dcc-chat-setup entry))))
1118
1119(defun erc-dcc-chat-filter (proc str)
1120 (let ((orig-buffer (current-buffer)))
1121 (unwind-protect
1122 (progn
1123 (set-buffer (process-buffer proc))
1124 (setq erc-dcc-unprocessed-output
1125 (concat erc-dcc-unprocessed-output str))
1126 (run-hook-with-args 'erc-dcc-chat-filter-hook proc
1127 erc-dcc-unprocessed-output))
1128 (set-buffer orig-buffer))))
1129
1130(defun erc-dcc-chat-parse-output (proc str)
1131 (save-match-data
1132 (let ((posn 0)
1133 line)
1134 (while (string-match "\n" str posn)
1135 (setq line (substring str posn (match-beginning 0)))
1136 (setq posn (match-end 0))
1137 (erc-display-message
1138 nil nil proc
1139 'dcc-chat-privmsg ?n (erc-propertize erc-dcc-from 'face
1140 'erc-nick-default-face) ?m line))
1141 (setq erc-dcc-unprocessed-output (substring str posn)))))
1142
1143(defun erc-dcc-chat-buffer-killed ()
1144 (erc-dcc-chat-close "killed buffer"))
1145
1146(defun erc-dcc-chat-close (&optional event)
1147 "Close a DCC chat, removing any associated processes and tidying up
1148`erc-dcc-list'"
1149 (let ((proc (plist-get erc-dcc-entry-data :peer))
1150 (evt (or event "")))
1151 (when proc
1152 (setq erc-dcc-list (delq erc-dcc-entry-data erc-dcc-list))
1153 (run-hook-with-args 'erc-dcc-chat-exit-hook proc)
1154 (delete-process proc)
1155 (erc-display-message
1156 nil 'notice erc-server-process
1157 'dcc-chat-ended ?n erc-dcc-from ?t (current-time-string) ?e evt)
1158 (setq erc-dcc-entry-data (plist-put erc-dcc-entry-data :peer nil)))))
1159
1160(defun erc-dcc-chat-sentinel (proc event)
1161 (let ((buf (current-buffer))
1162 (elt (erc-dcc-member :peer proc)))
1163 ;; the sentinel is also notified when the connection is opened, so don't
1164 ;; immediately kill it again
1165 ;(message "buf %s elt %S evt %S" buf elt event)
1166 (unwind-protect
1167 (if (string-match "^open from" event)
1168 (erc-dcc-chat-setup elt)
1169 (erc-dcc-chat-close event))
1170 (set-buffer buf))))
1171
1172(defun erc-dcc-no-such-nick (proc parsed)
1173 "Detect and handle no-such-nick replies from the IRC server."
1174 (let* ((elt (erc-dcc-member :nick (second (erc-response.command-args parsed))
1175 :parent proc))
1176 (peer (plist-get elt :peer)))
1177 (when (or (and (processp peer) (not (eq (process-status peer) 'open)))
1178 elt)
1179 ;; Since we already created an entry before sending the CTCP
1180 ;; message, we now remove it, if it doesn't point to a process
1181 ;; which is already open.
1182 (setq erc-dcc-list (delq elt erc-dcc-list))
1183 (if (processp peer) (delete-process peer)))
1184 nil))
1185
597993cf
MB
1186(provide 'erc-dcc)
1187
1188;;; erc-dcc.el ends here
1189;;
1190;; Local Variables:
1191;; indent-tabs-mode: nil
1192;; End:
1193
1194;; arch-tag: cda5a6b3-c510-4dbe-b699-84cccfa04edb