+(define (timeline-print out-format timeline)
+ (void (foldl (match-lambda**
+ [((and m (msg _ _ nick _ _)) (cons prev-nick i))
+ (let ([i (if (string=? prev-nick nick) i (+ 1 i))])
+ (msg-print out-format i m)
+ (cons nick i))])
+ (cons "" 0)
+ timeline)))
+
+; feed->msgs : Feed -> (Listof Msg)
+(define (feed->msgs f)
+ (match-define (feed nick uri) f)
+ (log-info "Reading feed nick:~a uri:~v" nick uri)
+ (str->msgs nick uri (uri-read-cached uri)))
+
+; feed-download : Feed -> Void
+(define (feed-download f)
+ (match-define (feed nick uri) f)
+ (log-info "Downloading feed nick:~a uri:~a" nick (url->string uri))
+ (with-handlers
+ ([exn:fail?
+ (λ (e)
+ (log-error "Network error nick:~a uri:~v exn:~v" nick uri e)
+ #f)]
+ [integer?
+ (λ (status)
+ (log-error "HTTP error nick:~a uri:~a status:~a" nick uri status)
+ #f)])
+ (uri-download uri)))
+
+; timeline-download : Integer -> (Listof Feed) -> Void
+(define (timeline-download num_workers feeds)
+ ; TODO No need for map - can just iter
+ (void (concurrent-filter-map num_workers feed-download feeds)))
+
+; TODO timeline contract : time-sorted list of messages
+; timeline-read : (U 'old->new 'new->old) -> (Listof Feeds) -> (Listof Msg)
+(define (timeline-read order feeds)
+ (define cmp (match order
+ ['old->new <]
+ ['new->old >]))
+ (sort (append* (filter-map feed->msgs feeds))
+ (λ (a b) (cmp (msg-ts_epoch a) (msg-ts_epoch b)))))
+
+(define (start-logger level)
+ (let* ([logger (make-logger #f #f level #f)]