#!/usr/bin/env python
# Record all the UDS streams
# Copyright (c) 2010, Stefano Rivera <stefanor@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.

import csv
import datetime
import logging
import urlparse

import dateutil.parser
from twisted.internet import protocol, reactor, task
from twisted.protocols.shoutcast import ShoutcastClient
from twisted.web import client

CSV_URL = 'http://summit.ubuntu.com/uds-n.csv'
CSV_REFRESH = 300
SCHED_INTERVAL = 1
URLS = {
    'Antigua 1': 'http://icecast.ubuntu.com:8000/antigua1.ogg',
    'Antigua 2': 'http://icecast.ubuntu.com:8000/antigua2.ogg',
    'Antigua 3': 'http://icecast.ubuntu.com:8000/antigua3.ogg',
    'Antigua 4': 'http://icecast.ubuntu.com:8000/antigua4.ogg',
    'Bonaire 1': 'http://icecast.ubuntu.com:8000/bonaire1.ogg',
    'Bonaire 2': 'http://icecast.ubuntu.com:8000/bonaire2.ogg',
    'Bonaire 3': 'http://icecast.ubuntu.com:8000/bonaire3.ogg',
    'Bonaire 4': 'http://icecast.ubuntu.com:8000/bonaire4.ogg',
    'Bonaire 5': 'http://icecast.ubuntu.com:8000/bonaire5.ogg',
    'Bonaire 6': 'http://icecast.ubuntu.com:8000/bonaire6.ogg',
    'Bonaire 7': 'http://icecast.ubuntu.com:8000/bonaire7.ogg',
    'Bonaire 8': 'http://icecast.ubuntu.com:8000/bonaire8.ogg',
    'Curacao 1+2': 'http://icecast.ubuntu.com:8000/curacao12.ogg',
    'Curacao 3+4': 'http://icecast.ubuntu.com:8000/curacao34.ogg',
    'Grand Sierra D (Plenaries)': 'http://icecast.ubuntu.com:8000/grandsierrad.ogg',
}
EXT = 'ogg'

# Debugging :)
if False:
    CSV_URL = 'http://mirrors.tumbleweed.org.za/uds-n/test.csv'
    URLS.update({
        'Classic Rock': 'http://ogg2.as34763.net:80/vc160.ogg',
        'Absolute Radio': 'http://ogg2.as34763.net/vr160.ogg',
        'Absolute 80s': 'http://ogg2.as34763.net/a8160.ogg',
    })

schedule = None
recording = {}


class StreamRecorder(ShoutcastClient):

    def gotMetaData(self, data):
        pass

    def gotMP3Data(self, data):
        self.factory.resetDelay()
        self.factory.outstream.write(data)
        #self.factory.outstream.flush()

    def handleResponseEnd(self):
        pass


class StreamRecorderFactory(protocol.ReconnectingClientFactory):

    protocol = StreamRecorder
    maxDelay = 60

    def __init__(self, filename, path):
        self.filename = filename
        self.path = path
        self.log = logging.getLogger(path)

    def doStart(self):
        self.outstream = file(self.filename, 'ab')

    def doStop(self):
        self.outstream.close()

    def stop(self):
        self.log.info('Shutting down cleanly')
        self.stopTrying()
        self.connector.disconnect()

    def buildProtocol(self, addr):
        self.log.info('Connected')
        p = self.protocol(self.path)
        p.factory = self
        return p

    def clientConnectionLost(self, connector, reason):
        self.log.info('Connection lost')
        protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)


def start_recordings():
    log = logging.getLogger('loop')
    log.debug('Checking schedule...')
    if schedule is None:
        return
    now = datetime.datetime.utcnow()
    for talk in schedule:
        start, end, room, track, name, title = talk
        if not (start <= now < end):
            continue
        if room in recording:
            continue
        if room not in URLS:
            log.error('Unknown room: %s', room)
            continue
        log.info('Recording: "%s" in %s', title, room)
        url = URLS[room]
        fn = '%s-%s.%s' % (
                start.strftime('%Y-%m-%d-%H-%M'),
                name or title.replace(' ', '_'),
                EXT,
        )
        url = urlparse.urlparse(url)
        factory = StreamRecorderFactory(fn, url[2])
        hostname = url[1]
        port = 80
        if ':' in hostname:
            hostname, port = hostname.split(':')
            port = int(port)
        factory.connector = reactor.connectTCP(hostname, port, factory)
        recording[room] = factory
        reactor.callLater((end - now).seconds, end_stream, title, room, factory,
                          log)

def end_stream(title, room, factory, log):
    log.info('Stopping Recording: "%s" in %s', title, room)
    factory.stop()
    del recording[room]

def refresh_schedule():
    client.getPage(CSV_URL).addCallback(do_refresh_schedule)

def do_refresh_schedule(data):
    global schedule
    log = logging.getLogger('loop')
    log.info('Updating Schedule...')
    temp_sched = []
    reader = csv.reader(data.split('\n'))
    # Skip header:
    reader.next()
    for row in reader:
        if not row:
            continue
        start, end, room, track, name, title = row
        start = dateutil.parser.parse(start).replace(tzinfo=None)
        end = dateutil.parser.parse(end).replace(tzinfo=None)
        temp_sched.append((start, end, room, track, name, title))
    schedule = temp_sched
    log.info('Updated Schedule')

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    task.LoopingCall(refresh_schedule).start(CSV_REFRESH)
    task.LoopingCall(start_recordings).start(SCHED_INTERVAL)
    reactor.run()

