+(: follower->peer (-> Follower Peer))
+(define/match (follower->peer f)
+ [((Follower n u _ _)) (Peer n u)])
+
+(: weblog-line->follower (-> String (Option Peer)))
+(define weblog-line->follower
+ (let ([re #px"([^/]+)/([^ ]+) +\\(\\+([a-z]+://[^;]+); *@([^\\)]+)\\)"])
+ (λ (log-line)
+ (match (regexp-match re log-line)
+ [(list _ client version uri nick)
+ (let ([f (Follower nick (string->url uri) client version)])
+ (log-debug "Found follower: ~v" f)
+ f) ]
+ [_ #f]))))
+
+(define (weblog-file->peers file-path)
+ (define size (/ (file-size file-path) 1000000.0))
+ (log-info "BEGIN parsing ~a MB from file: ~v" size (path->string file-path))
+ (define t0 (current-inexact-milliseconds))
+ (define peers
+ (let* ([prefilter-cmd-path
+ (build-path tt-home-dir "hooks" "web-log-prefilter")]
+ [lines
+ (match (process* prefilter-cmd-path file-path)
+ [(list in _out pid err ctrl)
+ (ctrl 'wait)
+ (match (ctrl 'exit-code)
+ [(or 0 1) ; Assuming grep's: 0: found, 1: not found, 2: error
+ (port->lines in)]
+ [_
+ (log-warning "Prefilter hook failed: ~a" (port->string err))
+ (file->lines file-path)])])])
+ (map follower->peer (filter-map weblog-line->follower lines))))
+ (define t1 (current-inexact-milliseconds))
+ (log-info "END parsing ~a MB in ~a seconds from file: ~v."
+ size
+ (* 0.001 (- t1 t0))
+ (path->string file-path))
+ (when (empty? peers)
+ (log-warning "No peers found in ~a" (path->string file-path)))
+ (uniq peers))
+
+(define (weblog-dir->peers dir-path)
+ (uniq (append*
+ (map weblog-file->peers
+ (filter-map
+ (λ (filename)
+ (define file-path (build-path dir-path filename))
+ (if (equal? 'file (file-or-directory-type file-path))
+ file-path
+ #f))
+ (if (directory-exists? dir-path)
+ (directory-list dir-path)
+ '()))))))
+
+(define (follower-peers-in-web-logs log-dirs)
+ (uniq (append* (map weblog-dir->peers log-dirs))))
+