Transfer from CVS at SourceForge
[doldaconnect.git] / lib / guile / autodl
1 #!/usr/bin/guile -s
2 !#
3
4 (use-modules (dolcon ui))
5 (use-modules (ice-9 pretty-print))
6
7 (define sr '())
8 (define lastsearch 0)
9 (define info-searcheta 0)
10 (define info-numavail 0)
11 (define info-numreal 0)
12 (define info-numtotal 0)
13 (define lastparse 0)
14 (define srchid -1)
15 (define session '())
16 (define trans '())
17 (define dpeers '())
18 (define lastdl 0)
19
20 (define (logf msg)
21   (write-line msg (current-output-port))
22   (catch 'system-error (lambda ()
23                          (fsync (current-output-port)))
24          (lambda (key . err) #f))
25   )
26
27 (define (make-getopt opts optdesc)
28   (let ((arg opts) (curpos 0) (rest '()))
29     (lambda ()
30       (if (eq? arg '()) rest
31           (let ((ret #f))
32             (while (not ret)
33                    (if (= curpos 0)
34                        (if (eq? (string-ref (car arg) 0) #\-)
35                            (set! curpos 1)
36                            (begin
37                              (set! rest (append rest (list (car arg))))
38                              (set! arg (cdr arg))
39                              (if (eq? arg '())
40                                  (set! ret #t)))))
41                    (if (> curpos 0)
42                        (if (< curpos (string-length (car arg)))
43                            (begin (set! ret (string-ref (car arg) curpos)) (set! curpos (+ curpos 1)))
44                            (begin (set! curpos 0) (set! arg (cdr arg)) (if (eq? arg '()) (set! ret #t))))))
45             (if (eq? ret #t) rest
46                 (let ((opt (string-index optdesc ret)))
47                   (if (eq? opt #f) (throw 'illegal-option ret)
48                       (if (and (< opt (- (string-length optdesc) 1)) (eq? (string-ref optdesc (+ opt 1)) #\:))
49                           (let ((ret
50                                  (cons ret (let ((optarg
51                                                   (if (< curpos (string-length (car arg)))
52                                                       (substring (car arg) curpos)
53                                                       (begin (set! arg (cdr arg)) (if (eq? arg '()) (throw 'requires-argument ret)) (car arg)))))
54                                              (set! arg (cdr arg)) optarg))))
55                             (set! curpos 0)
56                             ret)
57                           (list ret))))))))))
58
59 (define (ftime)
60   (let ((ctime (gettimeofday)))
61     (+ (car ctime) (/ (cdr ctime) 1000000))))
62
63 (define (wanttosearch)
64   (> (- (current-time) lastsearch)
65      (if (> (length trans) 0) 300 60))
66   )
67
68 (define defspeed '())
69 (let ((matchlist (list
70                   (cons (make-regexp "^[][{}() ]*BBB" regexp/icase) 100000))))
71   (set! defspeed
72         (lambda (sr)
73           (catch 'ret
74                  (lambda ()
75                    (for-each (lambda (o)
76                                (if (regexp-exec (car o) (cadr (cdr (assoc 'peer sr))))
77                                    (throw 'ret (cdr o))))
78                              matchlist)
79                    15000)
80                  (lambda (sig ret)
81                    ret))
82           )))
83
84 (define (sr-less? sr1 sr2)
85   (let ((s1 (if (cdr (assoc 'speed sr1)) (cdr (assoc 'speed sr1)) (defspeed sr1)))
86         (s2 (if (cdr (assoc 'speed sr2)) (cdr (assoc 'speed sr2)) (defspeed sr2))))
87     (if (= s1 s2)
88         (< (cdr (assoc 'resptime sr1)) (cdr (assoc 'resptime sr2)))
89         (> s1 s2)))
90   )
91
92 (define (srg-less? g1 g2)
93   (or (> (length (cdr g1)) (length (cdr g2)))
94       (and (= (length (cdr g1)) (length (cdr g2)))
95            (> (car g1) (car g2))))
96   )
97
98 (define (gettrbysize size)
99   (catch 'ret
100          (lambda ()
101            (for-each (lambda (o)
102                        (if (= (cdr (assoc 'size (cdr o))) size)
103                            (throw 'ret (cdr o))))
104                      trans)
105            #f)
106          (lambda (sig ret)
107            ret))
108   )
109
110 (define (download sr)
111   (let ((resp #f))
112     (let ((args (list "download"
113                       (car (cdr (assoc 'peer sr)))
114                       (cadr (cdr (assoc 'peer sr)))
115                       (cdr (assoc 'filename sr))
116                       (cdr (assoc 'size sr)))))
117       (let ((tag (assoc 'tag session)))
118         (if tag (set! args (append args (list "tag" (cdr tag))))))
119       (let ((uarg (assoc 'uarg session)))
120         (if uarg (set! args (append args (list "user" (cdr uarg))))))
121       (set! resp (apply dc-ecmd-assert 200 args)))
122     (let ((id (car (dc-intresp resp))))
123       (set! trans
124             (cons (cons id (list (assoc 'size sr)
125                                  (assoc 'peer sr)
126                                  (assoc 'filename sr)
127                                  (assoc 'resptime sr)
128                                  '(curpos . 0)
129                                  '(state . wait)
130                                  '(curspeed . #f)
131                                  '(lastpos . 0)
132                                  (cons 'id id)
133                                  (cons 'lasttime (current-time))
134                                  (cons 'lastprog (current-time))))
135                   trans))
136       (logf (string-append "downloading "
137                            (cdr (assoc 'filename sr))
138                            " from "
139                            (cadr (cdr (assoc 'peer sr)))
140                            ", "
141                            (number->string (cdr (assoc 'size sr)))
142                            " bytes (id "
143                            (number->string id)
144                            ", "
145                            (number->string (cdr (assoc 'slots sr)))
146                            " slots), timing out in "
147                            (number->string (max 10 (* (cdr (assoc 'resptime sr)) 2)))
148                            " seconds"))))
149   (set! lastdl (current-time))
150   )
151
152 (define (disablepeer peer)
153   (let ((newglist '()) (numrem 0))
154     (for-each (lambda (g)
155                 (let ((newlist '()))
156                   (for-each (lambda (o)
157                               (if (not (equal? (cdr (assoc 'peer o)) peer))
158                                   (set! newlist (cons o newlist))
159                                   (set! numrem (+ numrem 1))))
160                             (cdr g))
161                   (if (not (eq? newlist '()))
162                       (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist)))))
163               sr)
164     (set! sr (sort newglist srg-less?))
165     (logf (string-append "disabled " (cadr peer) " and removed " (number->string numrem) " search results")))
166   (let* ((dpa (assoc peer dpeers)) (dp (and (pair? dpa) (cdr dpa))))
167     (if dp
168         (set-cdr! (assoc 'time dp) (current-time))
169         (set! dpeers (cons (cons peer (list (cons 'time (current-time))
170                                             (cons 'peer peer)))
171                            dpeers))))
172   )
173
174 (define (checktrans)
175   (let ((time (current-time)))
176     (for-each (lambda (o)
177                 (if (and (memq (cdr (assoc 'state (cdr o))) '(wait hs))
178                          (> (- time (cdr (assoc 'lastprog (cdr o)))) (max 10 (* (cdr (assoc 'resptime (cdr o))) 2))))
179                     (begin (logf (string-append "transfer " (number->string (car o)) " timing out"))
180                            (dc-ecmd-assert 200 "cancel" (car o))
181                            (disablepeer (cdr (assoc 'peer (cdr o))))
182                            (set! trans (assq-remove! trans (car o)))))
183                 (if (and (eq? (cdr (assoc 'state (cdr o))) 'main)
184                          (> (- time (cdr (assoc 'lastprog (cdr o)))) 60))
185                     (begin (logf (string-append "transfer " (number->string (car o)) " seems to have stalled"))
186                            (dc-ecmd-assert 200 "cancel" (car o))
187                            (set! trans (assq-remove! trans (car o)))))
188                 (if (and (eq? (cdr (assoc 'state (cdr o))) 'main)
189                          (> (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o)))) 20))
190                     (begin (set-cdr! (assoc 'curspeed (cdr o))
191                                      (/ (- (cdr (assoc 'curpos (cdr o))) (cdr (assoc 'lastpos (cdr o))))
192                                         (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o))))))
193                            (set-cdr! (assoc 'lastpos (cdr o)) (cdr (assoc 'curpos (cdr o))))
194                            (set-cdr! (assoc 'lasttime (cdr o)) (cdr (assoc 'lastprog (cdr o)))))))
195                 trans))
196   )
197
198 (define (write-info-file)
199   (if (assoc 'info-file session)
200       (let ((op (open-output-file (cdr (assoc 'info-file session)))))
201         (write (list (cons 'numdl (length trans))
202                      (cons 'lastdl lastdl)
203                      (cons 'availsr info-numavail)
204                      (cons 'realsr info-numreal)
205                      (cons 'totalsr info-numtotal)
206                      (cons 'lastsrch lastsearch)
207                      (cons 'srcheta info-searcheta))
208                op)
209         (newline op)
210         (close-port op))))
211
212 (define (parseresults)
213   (logf (string-append "entering parseresults with "
214                        (number->string
215                         (apply + (map (lambda (o) (length (cdr o))) sr)))
216                        " results in "
217                        (number->string (length sr))
218                        " sizes"))
219   (let ((retval #t) (numreal 0) (numtotal 0) (numavail 0))
220     (catch 'ret
221            (lambda ()
222              (and (eq? sr '()) (throw 'ret #f))
223              (let ((numrem 0) (countrem 0) (newglist '()))
224                (for-each (lambda (g)
225                            (let ((newlist '()))
226                              (for-each (lambda (o)
227                                          (if (< (- (current-time) (cdr (assoc 'recvtime o))) 300)
228                                              (set! newlist (cons o newlist))
229                                              (set! countrem (+ countrem 1))))
230                                        (cdr g))
231                              (if (> (length newlist) 0)
232                                  (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist))
233                                  (set! numrem (+ numrem 1)))))
234                          sr)
235                (set! sr (sort newglist srg-less?))
236                (if (> countrem 0)
237                    (logf (string-append "removed " (number->string countrem) " time-outed results and " (number->string numrem) " entire sizes"))))
238              (let ((numrem 0) (newlist '()))
239                (for-each (lambda (o)
240                            (if (> (- (current-time) (cdr (assoc 'time o))) 1800)
241                                (set! numrem (+ numrem 1))
242                                (set! newlist (cons o newlist))))
243                          dpeers)
244                (set! dpeers newlist)
245                (logf (string-append "re-enabled " (number->string numrem) " disabled users")))
246              (let ((numrem 0) (countrem 0) (newglist '()))
247                (for-each (lambda (g)
248                            (let ((newlist '()))
249                              (for-each (lambda (o)
250                                          (if (not (assoc (cdr (assoc 'peer o)) dpeers))
251                                              (set! newlist (cons o newlist))
252                                              (set! countrem (+ countrem 1))))
253                                        (cdr g))
254                              (if (> (length newlist) 0)
255                                  (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist))
256                                  (set! numrem (+ numrem 1)))))
257                          sr)
258                (set! sr (sort newglist srg-less?))
259                (if (> countrem 0)
260                    (logf (string-append "removed " (number->string countrem) " results with disabled peers and " (number->string numrem) " entire sizes"))))
261              (and (eq? sr '()) (throw 'ret #f))
262              (set! numtotal (apply + (map (lambda (o) (length (cdr o))) sr)))
263              (let* ((maxsize (apply max (map (lambda (o) (length (cdr o))) sr)))
264                     (minsize (/ maxsize 3)))
265                (let ((numrem 0) (countrem 0))
266                  (for-each (lambda (o) (if (< (length (cdr o)) minsize)
267                                            (begin (set! countrem (+ countrem (length (cdr o))))
268                                                   (set! numrem (+ numrem 1)))))
269                            sr)
270                  (if (> countrem 0)
271                      (logf (string-append "will disregard " (number->string countrem) " results from " (number->string numrem) " sizes due to popularity lack")))
272                  (set! numreal (- numtotal countrem)))
273                (let ((numrem 0) (numrrem 0))
274                  (for-each (lambda (g)
275                              (for-each (lambda (o)
276                                          (if (< (cdr (assoc 'slots o)) 1)
277                                              (begin (set! numrem (+ numrem 1))
278                                                     (if (>= (length (cdr g)) minsize)
279                                                         (set! numrrem (+ numrrem 1))))))
280                                        (cdr g)))
281                            sr)
282                  (if (> numrem 0)
283                      (logf (string-append (number->string numrem) " results had no slots")))
284                  (set! numavail (- numreal numrrem)))
285                (for-each (lambda (g)
286                            (if (>= (length (cdr g)) minsize)
287                                (catch 'found
288                                       (lambda ()
289                                         (for-each (lambda (o)
290                                                     (and (> (cdr (assoc 'slots o)) 0)
291                                                          (throw 'found o)))
292                                                   (cdr g)))
293                                       (lambda (sig sr)
294                                         (let ((tr (gettrbysize (cdr (assoc 'size sr)))))
295                                           (if (not tr)
296                                               (if (< (length trans) (cdr (assoc 'maxtrans session)))
297                                                   (download sr))
298                                               (if (and (cdr (assoc 'curspeed tr))
299                                                        (not (equal? (cdr (assoc 'peer sr)) (cdr (assoc 'peer tr))))
300                                                        (> (- (or (cdr (assoc 'speed sr)) (defspeed sr)) (cdr (assoc 'curspeed tr))) 10000))
301                                                   (begin (logf (string-append "abandoning transfer "
302                                                                               (number->string (cdr (assoc 'id tr)))
303                                                                               " for possible faster sender"))
304                                                          (dc-ecmd-assert 200 "cancel" (cdr (assoc 'id tr)))
305                                                          (set! trans (assq-remove! trans (cdr (assoc 'id tr))))
306                                                          (download sr)))))))))
307                          sr)
308                )
309              )
310            (lambda (sig ret)
311              (set! retval ret)
312              ))
313     (set! info-numavail numavail)
314     (set! info-numreal numreal)
315     (set! info-numtotal numtotal)
316     retval)
317   )
318
319 (define (handlesr filename fnet peer size slots resptime)
320   (let ((cl (or (assoc size sr)
321                 (let ((newp (cons size '()))) (set! sr (append sr (list newp))) newp)))
322         (newsr (list
323                 (cons 'filename filename)
324                 (cons 'peer (list fnet peer))
325                 (cons 'size size)
326                 (cons 'slots slots)
327                 (cons 'resptime resptime)
328                 (cons 'speed (getspeed peer))
329                 (cons 'recvtime (current-time))
330                 (cons 'dis #f)))
331         (newlist '()))
332     (for-each (lambda (o) (if (not (and (equal? (cdr (assoc 'filename o)) filename)
333                                         (equal? (cdr (assoc 'peer o)) (list fnet peer))))
334                               (set! newlist (cons o newlist))))
335               (cdr cl))
336     (set-cdr! cl (sort (cons newsr newlist) sr-less?))
337     )
338   )
339
340 ; XXX: Redefine to go through the server, once that is implemented
341 (define (getspeed username)
342   (catch 'system-error
343          (lambda ()
344            (let* ((port (open-input-file (string-append (getenv "HOME") "/dc/users/" username))) (avg 0) (numdls (string->number (read-line port))) (max (string->number (read-line port))) (numents (string->number (read-line port))))
345              (do ((i 0 (+ i 1))) ((= i numents) (close-port port) (/ avg numents)) (set! avg (+ avg (string->number (read-line port)))))
346            ))
347          (lambda args
348            #f
349            )
350          )
351   )
352
353 (define (validate-session session)
354   (catch 'wrong-type-arg
355          (lambda ()
356            (and
357             (assoc 'sexpr session)
358             (assoc 'prio session)
359             (assoc 'maxtrans session)
360             #t
361             )
362            )
363          (lambda (key . args)
364            (display "Session data is not an a-list\n" (current-error-port))
365            #f)
366          )
367   )
368
369 (define (autodl-main args)
370   (let ((dc-server #f) (done #f) (retval 0))
371     (let ((getopt (make-getopt (cdr args) "hs:S:e:p:t:a:I:")) (arg #f))
372       (do ((arg (getopt) (getopt))) ((not (and (pair? arg) (char? (car arg)))) (set! args arg))
373         (cond ((eq? (car arg) #\h)
374                (begin (display "usage: autodl [-s server] -S sessfile\n" (current-error-port))
375                       (display "       autodl [-s server] -e search-expression [-p prio] [-t tag] [-a userarg]\n" (current-error-port))
376                       (display "       autodl [-s server]\n" (current-error-port))
377                       (display "       autodl -h\n" (current-error-port))
378                       (exit 0)))
379               ((eq? (car arg) #\s)
380                (set! dc-server (cdr arg)))
381               ((eq? (car arg) #\S)
382                (let ((port (open-file (cdr arg)))) (set! session (read port)) (close-port port)))
383               ((eq? (car arg) #\p)
384                (let ((c (assoc 'prio session)))
385                  (if c (set-cdr! c (cdr arg))
386                      (set! session (cons (cons 'prio (cdr arg)) session)))))
387               ((eq? (car arg) #\t)
388                (let ((c (assoc 'tag session)))
389                  (if c (set-cdr! c (cdr arg))
390                      (set! session (cons (cons 'tag (cdr arg)) session)))))
391               ((eq? (car arg) #\a)
392                (let ((c (assoc 'uarg session)))
393                  (if c (set-cdr! c (cdr arg))
394                      (set! session (cons (cons 'uarg (cdr arg)) session)))))
395               ((eq? (car arg) #\I)
396                (let ((c (assoc 'info-file session)))
397                  (if c (set-cdr! c (cdr arg))
398                      (set! session (cons (cons 'info-file (cdr arg)) session)))))
399               ((eq? (car arg) #\e)
400                (set! session (cons (cons 'sexpr (dc-lexsexpr (cdr arg))) session)))
401               )
402         )
403       )
404     (if (eq? session '()) (begin (if (isatty? (current-input-port)) (display "Enter session data (s-expr):\n" (current-error-port))) (set! session (read))))
405     (if (not (assoc 'prio session))
406         (set! session (cons '(prio . 10) session)))
407     (if (not (assoc 'maxtrans session))
408         (set! session (cons '(maxtrans . 1) session)))
409     (if (not (validate-session session)) (begin (display "Invalid session!\n" (current-error-port)) (exit 1)))
410     (if (not dc-server) (set! dc-server (getenv "DCSERVER")))
411     (if (not dc-server) (set! dc-server "localhost"))
412     (catch 'system-error
413            (lambda ()
414              (dc-c&l #t dc-server #t))
415            (lambda (key . args)
416              (logf (string-append "could not connect to server: " (apply format #f (cadr args) (caddr args))))
417              (exit 2)))
418     (dc-ecmd-assert 200 "notify" "all" "on")
419     (for-each (lambda (sig) (sigaction sig (lambda (sig) (throw 'sig sig)))) (list SIGINT SIGTERM SIGHUP))
420     (catch 'sig
421            (lambda ()
422              (while #t
423                     (if (and (not (= lastsearch -1)) (wanttosearch))
424                         (begin
425                           (if (not (= srchid -1))
426                               (dc-ecmd "cansrch" srchid))
427                           (let* ((resp (apply dc-ecmd-assert (append (list '(200 501 509) "search" "prio" (number->string (cdr (assoc 'prio session))) "all") (cdr (assoc 'sexpr session)))))
428                                  (ires (dc-intresp resp))
429                                  (eres (dc-extract resp)))
430                             (case (cdr (assoc 'code eres))
431                               ((200)
432                                (begin (set! srchid (car ires))
433                                       (logf (string-append "search scheduled in " (number->string (cadr ires)) " seconds (id " (number->string srchid) ")"))
434                                       (set! info-searcheta (+ (current-time) (cadr ires)))
435                                       (set! lastsearch -1)))
436                               ((501)
437                                (begin (set! srchid -1)
438                                       (logf (string-append "no fnetnodes available to search on"))
439                                       (set! lastsearch (current-time))))
440                               ((509)
441                                (begin (logf "illegal search expression")
442                                       (set! done #t)
443                                       (set! retval 3)
444                                       (throw 'sig 0)))))))
445                     (checktrans)
446                     (if (> (- (current-time) lastparse) 20)
447                         (begin (parseresults)
448                                (set! lastparse (current-time))))
449                     (write-info-file)
450                     (dc-select 10000)
451                     (while (let ((resp (dc-getresp)))
452                              (if resp
453                                  (begin
454                                    (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er))))
455                                      (cond
456                                        ((equal? cmd ".notify")
457                                         (case code
458                                           ((611) ; Transfer state change
459                                            (let ((ires (dc-intresp resp)) (tr #f))
460                                              (if (and ires (assoc (car ires) trans))
461                                                  (begin (set! tr (cdr (assoc (car ires) trans)))
462                                                         (set-cdr! (assoc 'state tr)
463                                                                   (cdr (assoc (cadr ires) '((0 . wait) (1 . hs) (2 . main) (3 . done)))))
464                                                         (set-cdr! (assoc 'lastprog tr) (current-time))))))
465                                           ((614) ; Transfer error
466                                            (let ((ires (dc-intresp resp)))
467                                              (if (and ires (assoc (car ires) trans))
468                                                  (begin (logf (string-append "transfer " (number->string (car ires)) " encountered error " (number->string (cadr ires))))
469                                                         (dc-ecmd-assert 200 "cancel" (car ires))
470                                                         (let ((tr (cdr (assoc (car ires) trans))))
471                                                           (disablepeer (cdr (assoc 'peer tr))))
472                                                         (set! trans (assq-remove! trans (car ires)))))))
473                                           ((615) ; Transfer progress
474                                            (let ((ires (dc-intresp resp)) (tr #f))
475                                              (if (and ires (assoc (car ires) trans))
476                                                  (begin (set! tr (cdr (assoc (car ires) trans)))
477                                                         (set-cdr! (assoc 'curpos tr) (cadr ires))
478                                                         (set-cdr! (assoc 'lastprog tr) (current-time))))))
479                                           ((617) ; Transfer destroyed
480                                            (let* ((ires (dc-intresp resp)) (tr (and ires (assoc (car ires) trans))))
481                                              (if tr
482                                                  (begin (if (eq? (cdr (assoc 'state (cdr tr))) 'done)
483                                                             (begin (logf (string-append "transfer " (number->string (car ires)) " done"))
484                                                                    (set! trans (assq-remove! trans (car ires)))
485                                                                    (set! done #t)
486                                                                    (throw 'sig 0))
487                                                             (begin (logf (string-append "transfer " (number->string (car ires)) " disappeared"))
488                                                                    (set! trans (assq-remove! trans (car ires)))))))))
489                                           ((620) ; Search rescheduled
490                                            (let ((ires (dc-intresp resp)))
491                                              (if (and ires (= (car ires) srchid))
492                                                  (begin (set! info-searcheta (+ (current-time) (cadr ires)))
493                                                         (logf (string-append "search rescheduled to T+" (number->string (cadr ires))))))))
494                                           ((621) ; Search committed
495                                            (let ((ires (dc-intresp resp)))
496                                              (if (and ires (= (car ires) srchid))
497                                                  (begin (logf "search committed")
498                                                         (set! info-searcheta 0)
499                                                         (set! lastsearch (current-time))))))
500                                           ((622) ; Search result
501                                            (let ((ires (list->vector (dc-intresp resp))))
502                                              (if (and ires (= (vector-ref ires 0) srchid)) (apply handlesr (map (lambda (n) (vector-ref ires n)) '(1 2 3 4 5 7))))))
503                                           
504                                           )
505                                         )
506                                        
507                                        )
508                                      )
509                                    #t)
510                                  #f)
511                              )
512                            #t
513                            )
514                     )
515              )
516            (lambda (key sig)
517              (logf (string-append "interrupted by signal " (number->string sig)))
518              (if (not done)
519                  (set! retval 1)))
520            )
521     (logf "quitting...")
522     (catch 'sig
523            (lambda ()
524              (if (dc-connected)
525                  (begin (for-each (lambda (o)
526                                     (dc-qcmd (list "cancel" (car o))))
527                                   trans)
528                         (if (assoc 'info-file session)
529                             (catch 'system-error
530                                    (lambda ()
531                                      (delete-file (cdr (assoc 'info-file session))))
532                                    (lambda (key . args) #t)))
533                         (if (and done (assoc 'tag session))
534                             (dc-qcmd (list "filtercmd" "rmtag" (cdr (assoc 'tag session)))))
535                         (if (not (= srchid -1))
536                             (dc-qcmd (list "cansrch" srchid)))
537                         (dc-qcmd '("quit"))
538                         (while (dc-connected) (dc-select))
539                         )))
540            (lambda (key sig)
541              (logf "forcing quit")))
542     (exit retval)
543     )
544   )
545
546 (setlocale LC_ALL "")
547 (autodl-main (command-line))