#!/usr/bin/env python
# Record all the UDS streams
# Copyright (c) 2010 - 2011, 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 re
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-o.csv'
CSV_REFRESH = 300
OVERLAP = 300
SCHED_INTERVAL = 1
URLS = {
    'Grand Ballroom': 'http://icecast.ubuntu.com:8000/ballroom.ogg',
    'Arany': 'http://icecast.ubuntu.com:8000/arany.ogg',
    'Dery': 'http://icecast.ubuntu.com:8000/dery.ogg',
    'Elod': 'http://icecast.ubuntu.com:8000/elod.ogg',
    'Erkel': 'http://icecast.ubuntu.com:8000/erkel.ogg',
    'Huba': 'http://icecast.ubuntu.com:8000/huba.ogg',
    'Jokai': 'http://icecast.ubuntu.com:8000/jokai.ogg',
    'Jozsef': 'http://icecast.ubuntu.com:8000/jozsef.ogg',
    'Kazinczy': 'http://icecast.ubuntu.com:8000/kazinczy.ogg',
    'Kond': 'http://icecast.ubuntu.com:8000/kond.ogg',
    'Krudy': 'http://icecast.ubuntu.com:8000/krudy.ogg',
    'Lehar': 'http://icecast.ubuntu.com:8000/lehar.ogg',
    'Mikszath': 'http://icecast.ubuntu.com:8000/mikszath.ogg',
    'Ond': 'http://icecast.ubuntu.com:8000/ond.ogg',
    'Petofi': 'http://icecast.ubuntu.com:8000/petofi.ogg',
    'Tas': 'http://icecast.ubuntu.com:8000/tas.ogg',
    'Tohotom': 'http://icecast.ubuntu.com:8000/tohotom.ogg',
    'Ady': 'http://icecast.ubuntu.com:8000/ady.ogg',
    'Almos': 'http://icecast.ubuntu.com:8000/almos.ogg',
    'Kolcsey': 'http://icecast.ubuntu.com:8000/kolcsey.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, start) 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, start)] = factory
        reactor.callLater((end - now).seconds, end_stream, title, room, factory,
                          log, start)

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

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)
        start -= datetime.timedelta(seconds=OVERLAP)
        end = dateutil.parser.parse(end).replace(tzinfo=None)
        end += datetime.timedelta(seconds=OVERLAP)
        m = re.match(r'^(.+?)\s*\d+$', room)
        if m:
            room = m.group(1)
        if title.lower().strip() == 'private meeting':
            continue
        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()
