def remove_old_audio(self, date_today, keep_days, quiet=False):
        if not self.exists(self._laftp_config['audio_path']):
            return

        import datetime
        import schedule
        pwd = self.pwd()
        self.cwd(self._laftp_config['audio_path'])
        remote_list = self.nlst()
        deleted = []
        for fname in remote_list:
            if fname.startswith('.'):
                continue
            schedule_data = schedule.schedule_from_audio_file_name(fname)
            if schedule_data is not None:
                keep_days_this_file = schedule_data.get('extra', {}).get('days', [])
                if keep_days_this_file:
                    try:
                        keep_days_this_file = int(keep_days_this_file[0])
                    except ValueError:
                        keep_days_this_file = keep_days
                else:
                    keep_days_this_file = keep_days
                earliest_keep_date = date_today - datetime.timedelta(days=keep_days_this_file-1)
                if schedule_data['date'] < earliest_keep_date:
                    if not quiet:
                        print 'Deleting remote file:', fname, '...',
                    self.delete(fname)
                    if not quiet:
                        print 'done.'
                    deleted.append(fname)
        self.cwd(pwd)
        return deleted
def make_playlist_item(audio_fname):
    import datetime
    import os
    import urllib

    from rsab.listenagain import config, ListenAgainConfigError
    import schedule
    import utils

    if not config.has_option('ftp', 'audio_path'):
        raise ListenAgainConfigError('Missing option', 'ftp', 'audio_path')
    audio_path = config.get('ftp', 'audio_path')
    if not audio_path.endswith('/'):
        audio_path += '/'

    details = schedule.schedule_from_audio_file_name(audio_fname)
    if details is None or details['show'].startswith('xx'):
        return ''
    template = Template('playlist-item')
    template.title = utils.get_show_title_string(
        details['show'],
        details.get('presenters'),
    )
    template.date = details['date'].strftime('%a %d %b %Y')
    template.date_compact = details['date'].strftime('%Y%m%d')
    template.day_short = details['date'].strftime('%a').lower()
    template.start_time = details['start'].strftime('%H:%M')
    template.start_compact = details['start'].strftime('%H%M')
    template.end_time = details['end'].strftime('%H:%M')
    template.end_compact = details['end'].strftime('%H%M')

    duration = (
        datetime.datetime.combine(details['date'], details['end'])
        - datetime.datetime.combine(details['date'], details['start'])
    ).seconds / 60.0 # approx minutes
    ROUND_TO_MINUTES = 1
    if duration % ROUND_TO_MINUTES < (ROUND_TO_MINUTES / 2.0):
        duration = divmod(duration, ROUND_TO_MINUTES)[0] * ROUND_TO_MINUTES
    else:
        duration = (divmod(duration, ROUND_TO_MINUTES)[0] + 1) * ROUND_TO_MINUTES
    duration_h, duration_m = divmod(duration, 60)
    if duration_m:
        duration_string = '%dh %dm' % (duration_h, duration_m)
    else:
        duration_string = '%dh' % (duration_h,)
    template.duration = duration_string

    hidden_input = '<input type="hidden" disabled="disabled" name="%s" value="%s" />'
    template.show_names = hidden_input % ('show', details['show'])
    template.presenter_names = '\n'.join([
        hidden_input % ('presenter', presenter)
        for presenter in details.get('presenters', [])
        if presenter and presenter != details['show']
    ])

    template.url = audio_path + urllib.quote(os.path.split(audio_fname)[1])
    return template
def make_index_file(date, audio_fname_list, output_fname=None):
    import datetime
    import os

    import schedule
    import utils
    from rsab.listenagain import config, ListenAgainConfigError
    if not config.has_option('DEFAULT', 'output'):
        raise ListenAgainConfigError('No [DEFAULT]/output config defined')

    if output_fname is None:
        output_fname = 'index.html'
    output_fname = os.path.join(config.get('DEFAULT', 'output'), output_fname)
    output_file = open(output_fname, 'w')

    template = Template('player')

    playlist_items = []
    details_for_audio_files = []
    show_name_mapping = {}
    presenter_name_mapping = {}
    for audio_fname in audio_fname_list:
        playlist_items.append(str(make_playlist_item(audio_fname)))
        details_for_audio_files.append(schedule.schedule_from_audio_file_name(audio_fname))

    live_schedule = schedule.get_schedule(date + datetime.timedelta(days=1))
    for details in details_for_audio_files + live_schedule:
        if details is None:
            continue
        show_name = details['show']
        show_title = utils.get_message(show_name, 'show', default=None)
        if show_title is None:
            show_title = utils.get_message(show_name, 'presenter', default=show_name)
        if show_name and show_name not in show_name_mapping:
            show_name_mapping[show_name] = show_title
        for presenter in details.get('presenters', []):
            if not presenter or presenter == show_name:
                continue
            if presenter not in presenter_name_mapping:
                presenter_name_mapping[presenter] = utils.get_message(presenter, 'presenter', default=presenter)

    template.playlist_items = '\n'.join(filter(None, playlist_items))
    hidden_input = '<input type="hidden" disabled="disabled" name="%s" value="%s" />'
    template.show_name_mapping = '\n'.join([
        hidden_input % ('showname', '%s:%s' % pair)
        for pair in show_name_mapping.items()
    ])
    template.presenter_name_mapping = '\n'.join([
        hidden_input % ('presentername', '%s:%s' % pair)
        for pair in presenter_name_mapping.items()
    ])
    template.live_schedule = '\n'.join([
        hidden_input % (
            'live_schedule',
            '%s:%s' % (
                details['start'].strftime('%H:%M'),
                ','.join([details['show']] + details.get('presenters', [])),
            ),
        )
        for details in schedule.get_schedule(date + datetime.timedelta(days=1))
    ])

    output_file.write(str(template))
    output_file.close()
    return output_fname
def notify_all(uploaded_files):
    from rsab.listenagain import config
    import html
    import schedule

    from email.MIMEText import MIMEText
    import os
    import sets

    if uploaded_files is None:
        return

    mapping = { None: sets.Set(uploaded_files), }
    for fname in uploaded_files:
        details = schedule.schedule_from_audio_file_name(fname)
        for name in [details['show']] + details.get('presenters', []):
            if name not in mapping:
                mapping[name] = sets.Set()
            mapping[name].add(fname)

    config_sections = [
        sname
        for sname in config.sections()
        if sname == 'notify' or sname.startswith('notify.')
    ]

    if config.has_option('ftp', 'keep_days'):
        keep_days = config.getint('ftp', 'keep_days')
    else:
        keep_days = 7
    if config.has_option('main', 'userfacingserveraddress'):
        remote_server = config.get('main', 'userfacingserveraddress')
    else:
        remote_server = 'http://listenagain.rsab.org'
    if config.has_option('ftp', 'audio_path'):
        remote_path = config.get('ftp', 'audio_path')
    else:
        remote_path = '/audio'
    if config.has_option('email', 'from'):
        email_from = config.get('email', 'from')
    else:
        email_from = DEFAULT_FROM


    print
    print 'Notification emails:'

    messages = []
    for section in config_sections:
        print
        print 'Section name:', section
        if config.has_option(section, 'email'):
            addresses = filter(None, [x.strip() for x in config.get(section, 'email').split(',')])
        else:
            addresses = []
        print 'Addresses:', (addresses and ', '.join(addresses) or '(none)')
        if not addresses:
            print 'Skipping'
            continue

        if config.has_option(section, 'match'):
            match_names = filter(None, [x.strip() for x in config.get(section, 'match').split(',')])
        else:
            match_names = []
        print 'Requested:', ('*' in match_names and '(all)' or ', '.join(match_names))

        if '*' in match_names:
            notify_files = list(mapping[None])
        elif match_names:
            notify_files = sets.Set()
            for name in match_names:
                notify_files.update(mapping.get(name, []))
            notify_files = list(notify_files)
        else:
            notify_files = []

        if not notify_files:
            print 'No files matched.'
            continue

        notify_files.sort()

        if config.has_option(section, 'subject'):
            subject = config.get(section, 'subject')
        else:
            subject = 'New files uploaded by Radio St Austell Bay'

        if config.has_option(section, 'template'):
            template_name = config.get(section, 'template')
        else:
            template_name = 'mail-notify-default.txt'

        template = html.Template(template_name) # Not actually HTML!

        template.keep_days = keep_days
        files_string = '\n'.join([
            '    %s%s/%s' % (
                remote_server,
                remote_path,
                os.path.split(fname)[1],
            )
            for fname in notify_files
        ])
        template.files = files_string
        print 'Files:'
        print files_string

        message = MIMEText(str(template))
        message['Subject'] = subject
        message['From'] = email_from
        message['To'] = ', '.join(addresses)
        messages.append(message)

    print
    print 'Sending messages...'
    print
    sent = _send(messages)
    print
    print 'Done.  Sent %s of %s messages' % (sent, len(messages))
def run():
    from optparse import OptionParser
    import datetime
    import glob
    import os
    import sys
    import time

    import audio
    import html
    import notify
    import utils
    import recorder
    import remote
    import schedule

    option_parser = OptionParser()
    option_parser.add_option(
        "-p",
        "--print",
        dest="print_schedule",
        action="store_true",
        default=False,
        help="Print information about the date but don't do anything",
    )

    option_parser.add_option("-w", "--wav", dest="wavs", action="store_true", default=False, help="Construct WAVs")

    option_parser.add_option("-e", "--encode", dest="encode", action="store_true", default=False, help="Encode MP3s")

    option_parser.add_option(
        "-i", "--index", dest="index", action="store_true", default=False, help="Generate index pages"
    )

    option_parser.add_option(
        "-u", "--upload", dest="upload", action="store_true", default=False, help="Upload data to web server"
    )

    option_parser.add_option(
        "-c", "--config", dest="config_file", help="Specify alternative config file", metavar="FILE"
    )

    option_parser.add_option(
        "-f",
        "--filter",
        dest="filter",
        action="append",
        help="Filter schedule to items containing at least one of the given show/presenter",
        metavar="NAME",
    )

    options, args = option_parser.parse_args()

    task_options = ["print_schedule", "wavs", "encode", "index", "upload"]
    num_task_options_supplied = len([None for option_name in task_options if getattr(options, option_name, False)])
    # No specific do-something options were given.  Do everything.
    if num_task_options_supplied == 0:
        for option_name in task_options:
            setattr(options, option_name, True)

    config_files = utils.default_config_files()
    if options.config_file is not None:
        config_files.append(options.config_file)
    config = utils.init_config(config_files)

    date_string = "yesterday"
    if len(args) == 1:
        date_string = args[0]
    date = utils.interpret_date_string(date_string)

    start_time = time.time()

    recorder.init_module()
    bounds_and_files = recorder.get_bounds_and_files_for_date(date)
    schedule_list = schedule.get_schedule(date, filter_items=options.filter)

    if options.wavs or options.print_schedule:
        if options.filter:
            print "Schedule (filtered):"
        else:
            print "Schedule:"
        schedule.print_schedule(schedule_list)
        print
        print "Recordings:"
        recorder.print_bounds_and_files(bounds_and_files)

    wav_files = None
    if options.wavs:
        wav_files = audio.make_wav_files(bounds_and_files, schedule_list)

    mp3_files = None
    if options.encode:
        # Always rebuild WAVs list in case any are hanging around from before.
        if True:  # wav_files is None:
            if config.has_option("main", "wavs"):
                wavs_dir = config.get("main", "wavs")
            else:
                wavs_dir = os.getcwd()
            wav_files = glob.glob(os.path.join(wavs_dir, "*.wav"))
        # XXX Delete working WAVs?  Only if MP3 was created for it.
        mp3_files = [audio.encode_file(path) for path in wav_files]
        if True:  # XXX look for no-delete option later
            print "Deleting local copies of WAVs..."
            for (wav, mp3) in zip(wav_files, mp3_files):
                if mp3 is not None and os.path.isfile(wav):
                    os.unlink(wav)
                    print "   ", wav
            print "done."
            print

    ftp_conn = None
    remote_audio_files = []
    if options.upload or options.index:
        if config.has_option("ftp", "keep_days"):
            keep_days = config.getint("ftp", "keep_days")
        else:
            keep_days = 7
        earliest_keep_date = date - datetime.timedelta(days=keep_days - 1)
        ftp_conn = remote.connect()
        remote_audio_files = ftp_conn.get_list_of_audio_files()

        # First make an index with no old files:
        # XXX For now this ignores per-file limits, so will remove everything
        # over N days old from the index temporarily.  If a file has a higher
        # number of days defined, it will be restored to the index later when
        # it's not deleted -- but for a file with a lower number of days
        # defined, it'll disappear later than it should.
        audio_files_for_first_index = [
            fname
            for (fname, details) in [
                (fname, schedule.schedule_from_audio_file_name(fname)) for fname in remote_audio_files
            ]
            if details is not None and details["date"] >= earliest_keep_date
        ]

        index_fname = html.make_index_file(date, audio_files_for_first_index)
        if options.upload:
            ftp_conn.storlines("STOR index.html", open(index_fname, "r"))

    if options.upload:
        ftp_conn.remove_old_audio(date, keep_days)

        # XXX Here we should delete local copies of MP3s that are more than N
        # days old, in case the upload has failed for more than N days.
        pass

        # Always build the list again as we can pick up files we missed before.
        if True:  # mp3_files is None:
            if config.has_option("main", "mp3s"):
                mp3s_dir = config.get("main", "mp3s")
            else:
                mp3s_dir = os.getcwd()
            mp3_files = glob.glob(os.path.join(mp3s_dir, "*.mp3"))

        try:
            uploaded = remote.upload_audio(ftp_conn, mp3_files)
        except:
            import traceback

            print "Exception uploading files"
            traceback.print_exc(file=sys.stdout)
            print "Continuing..."

        # Reconnect (or grab the cached connection) in case there were failures
        # during the upload.  A better structure would see us making this
        # completely transparent across all remote calls, but for now we focus
        # on the big upload.
        ftp_conn = remote.connect()

        if True:  # XXX look for no-delete option later
            print "Deleting local copies of MP3s..."
            for mp3_path in mp3_files:
                if os.path.split(mp3_path)[1] in uploaded and os.path.isfile(mp3_path):
                    print "   ", mp3_path
                    os.unlink(mp3_path)
            print "done."
            print

        notify.notify_all(mp3_files)

    if options.index:
        # Second index file: whatever's on the server.
        remote_audio_files = ftp_conn.get_list_of_audio_files()

        index_fname = html.make_index_file(date, remote_audio_files)
        if options.upload:
            ftp_conn.storlines("STOR index.html", open(index_fname, "r"))
            # XXX Now also sync up anything that's in the www directory
            # (resource files such as JS, CSS, images, jPlayer...).
            pass

    if ftp_conn is not None:
        ftp_conn.quit()

    end_time = time.time()
    if not options.print_schedule:
        duration = end_time - start_time
        print "Took %2.2dm %2.2ds" % divmod(duration, 60)

    return 0