def main(): # parse commandline options parser = OptionParser() parser.add_option('-b', '--base_directory', dest='base_directory', help='base directory', default=BASE_DIRECTORY) parser.add_option('-c', '--config', dest='config', help='configuration_file') parser.add_option('-d', '--database-file', dest='database', help='database file') parser.add_option('-s', '--schedule-file', dest='schedule', help='schedule file') parser.add_option('-f', '--force-rescan', action='store_true', dest='force_rescan', help='force reconstruction of recordings', default=False) parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='add commentary to the process', default=False) (options, args) = parser.parse_args() if not options.config: options.config = os.path.join(options.base_directory, DEFAULT_CONFIG) if not options.database: options.database = os.path.join(options.base_directory, DEFAULT_DATABASE) if not options.schedule: options.schedule = os.path.join(options.base_directory, DEFAULT_SCHEDULE) if not os.path.exists(options.config): print >> sys.stderr, "fatal error: config file '%s' does not exist" % options.config sys.exit(1) config = ConfigParser(options.config) schedule = ScheduleParser(options.config, options.schedule) storage = BowerbirdStorage(options.database, config, schedule) # if requested, clear all recordings so they are re-constructed if options.force_rescan: storage.clearRecordingFiles() storage.clearRecordings() # call the update method storage.updateRecordingsFromFiles(options.force_rescan, options.verbose)
class Root(object): def __init__(self, path, database_file): self.path = path # parse config file(s) config_filename = cherrypy.config[CONFIG_KEY] if not os.path.exists(config_filename): raise IOError(errno.ENOENT, "Config file '%s' not found" % config_filename) if cherrypy.config.has_key(CONFIG_DEFAULTS_KEY): defaults_filename = cherrypy.config[CONFIG_DEFAULTS_KEY] # if defaults file doesn't exist, then don't use it if not os.path.exists(defaults_filename): sys.stderr.write("Warning: configured defaults file " " '%s' doesn't exist\n" % defaults_filename) defaults_filename = None else: defaults_filename = None self._config = ConfigParser(config_filename, defaults_filename) # initialise the schedule manager schedule_filename = cherrypy.config[SCHEDULE_KEY] self._schedule = ScheduleParser(config_filename, schedule_filename) # initialise storage self._storage = BowerbirdStorage(database_file, self._config, self._schedule) @cherrypy.expose def index(self, **ignored): # just redirect to status page raise cherrypy.HTTPRedirect('/status') @cherrypy.expose def name(self, **ignored): return self.getStationName() @cherrypy.expose @template.output('status.html') def status(self, **ignored): return template.render(station = self.getStationName(), uptime = self.getUptime(), disk_space = self.getDiskSpace(), local_time = self.getLocalTime(), last_recording = self.getLastRecording(), next_recording = self.getNextRecording()) @cherrypy.expose @template.output('config.html') def config(self, config_timestamp=0, load_defaults=False, cancel=False, apply=False, export_config=False, new_config=None, import_config=False, **data): error = None values = None if cancel: raise cherrypy.HTTPRedirect('/') elif export_config: # use cherrypy utility to push the file for download. This also # means that we don't have to move the config file into the # web-accessible filesystem hierarchy return cherrypy.lib.static.serve_file( os.path.realpath(self._config.filename), "application/x-download", "attachment", os.path.basename(self._config.filename)) elif apply: # if someone else has modified the config, then warn the user # before saving their changes (overwriting the other's changes) if int(config_timestamp) == self._config.getTimestamp(): self.updateConfigFromPostData(self._config, data) # update file try: self._config.saveToFile() self._config.exportForShell(self._config.filename + ".sh") # bounce back to homepage raise cherrypy.HTTPRedirect('/') except IOError as e: # if save failed, put up error message and stay on the page error = 'Error saving: %s' % e else: error = genshi.HTML('''WARNING: Configuration has been changed externally.<br /> If you wish to keep your changes and lose the external changes, then click on 'Apply' again. <br /> To lose your changes and preserve the external changes, click 'Cancel'.''') # load the post data into a temporary configparser to preserve # the user's changes when the page is loaded again. This means # we don't have to duplicate the horrible POST-to-config code temp_conf = ConfigParser() self.updateConfigFromPostData(temp_conf, data) values = temp_conf.getValues() # the page loading below will send the new config timestamp so # we don't have to anything else here. if load_defaults: values = self._config.getDefaultValues() elif import_config: if new_config.filename: try: values = self._config.parseFile(new_config.file) except Exception as e: values = None error = 'Unable to parse config file: "%s"' % e else: error = 'No filename provided for config import' if not values: values = self._config.getValues() return template.render(station=self.getStationName(), config_timestamp=self._config.getTimestamp(), error=error, using_defaults=load_defaults, values=values, file=self._config.filename, defaults_file=self._config.defaults_filename) @cherrypy.expose @template.output('schedule.html') def schedule(self, cancel=None, apply=None, add=None, **data): error = None if cancel: raise cherrypy.HTTPRedirect('/') elif apply: # use dictionary so we can untangle the POST data schedules = {} # we must check for uniqueness of titles titles = set() # just get the titles for key in data: # each schedule comes in three parts: ?.title, ?.start, ?.finish if key.endswith('title'): id = key.split('.')[0] title = data[key] if title in titles: error = ('Schedule titles must be unique. Found ' 'duplicate of "%s"' % title) return template.render(station=self.getStationName(), recording_specs=self._schedule.getScheduleSpecs(), error=error, file=self._schedule.filename, add=None) schedules[id] = title titles.add(title) # clear all schedules # and add them back in the order they were on the webpage self._schedule.clearScheduleSpecs() # sort the labels by their id, then add them in that order schedule_ids = schedules.keys() schedule_ids.sort() for id in schedule_ids: start_key = "%s.start" % id finish_key = "%s.finish" % id if data.has_key(start_key) and data.has_key(finish_key): self._schedule.setScheduleSpec( ScheduleParser.parseRecordingSpec(schedules[id], data[start_key], data[finish_key])) # update file try: self._schedule.saveToFile() # bounce back to homepage raise cherrypy.HTTPRedirect('/') except IOError as e: # if save failed, put up error message and stay on the page error = "Error saving: %s" % e elif not add: # this should only happen when a remove button has been clicked # find the remove key for key in data: if key.endswith('remove'): # get the schedule key from the title id = key.split('.')[0] title_key = "%s.title" % id if data.has_key(title_key): schedule_key = data[title_key] self._schedule.deleteScheduleSpec(schedule_key) return template.render(station=self.getStationName(), error=error, recording_specs=self._schedule.getScheduleSpecs(), add=add, file=self._schedule.filename) @cherrypy.expose @template.output('recordings.html') def recordings(self, view='month', year=None, month=None, day=None, recording_id=None, set_recording_id=False, clear_recording_id=False, go_to_start=False, go_to_finish=False, go_to_today=False, filter_title=None, filter_start=None, filter_finish=None, update_filter=None, reset_filter=None, clear_selected_date=False, set_filter_title=None, set_filter_start=None, set_filter_finish=None, export_recording=False, export_start_time=None, export_finish_time=None, export_selection=False, delete_cache=False, **ignored): # HACK: this helps development slightly if ignored: print "IGNORED",ignored # initialise error list errors = [] if delete_cache: # Clear all recordings. The cron job will do the scanning/refilling. self._storage.clearRecordings() # Clear all recording file entries self._storage.clearRecordingFiles() # ensure no other commands are enabled recording_id = None export_selection = None if reset_filter: filter_title = None clearSession(SESSION_FILTER_TITLE_KEY) filter_start = None clearSession(SESSION_FILTER_START_KEY) filter_finish = None clearSession(SESSION_FILTER_FINISH_KEY) if clear_selected_date: clearSession(SESSION_DATE_KEY) if clear_recording_id: clearSession(SESSION_RECORD_ID_KEY) # update filters if update_filter: # filtering on title if filter_title == NO_FILTER_TITLE: filter_title = None clearSession(SESSION_FILTER_TITLE_KEY) else: setSession(SESSION_FILTER_TITLE_KEY, filter_title) # filtering on start date & time if filter_start is not None: if filter_start: try: filter_start = parseDateUI(filter_start) setSession(SESSION_FILTER_START_KEY, filter_start) except ValueError, inst: errors.append('Errror parsing filter start time: %s' % inst) filter_start = getSession(SESSION_FILTER_START_KEY) else: clearSession(SESSION_FILTER_START_KEY) filter_start = None if filter_finish is not None: if filter_finish: try: filter_finish = parseDateUI(filter_finish) setSession(SESSION_FILTER_FINISH_KEY, filter_finish) except ValueError, inst: errors.append('Errror parsing filter finish time: %s' % inst) filter_finish = getSession(SESSION_FILTER_FINISH_KEY) else: clearSession(SESSION_FILTER_FINISH_KEY) filter_finish = None
class Root(object): def __init__(self, path, database_file): self.path = path # parse config file(s) config_filename = cherrypy.config[CONFIG_KEY] if not os.path.exists(config_filename): raise IOError(errno.ENOENT, "Config file '%s' not found" % config_filename) if cherrypy.config.has_key(CONFIG_DEFAULTS_KEY): defaults_filename = cherrypy.config[CONFIG_DEFAULTS_KEY] # if defaults file doesn't exist, then don't use it if not os.path.exists(defaults_filename): sys.stderr.write("Warning: configured defaults file " " '%s' doesn't exist\n" % defaults_filename) defaults_filename = None else: defaults_filename = None self._config = ConfigParser(config_filename, defaults_filename) # initialise the schedule manager schedule_filename = cherrypy.config[SCHEDULE_KEY] self._schedule = ScheduleParser(config_filename, schedule_filename) # initialise storage self._storage = BowerbirdStorage(database_file, self._config, self._schedule) @cherrypy.expose def index(self, **ignored): # just redirect to status page raise cherrypy.HTTPRedirect('/status') @cherrypy.expose def name(self, **ignored): return self.getStationName() @cherrypy.expose @template.output('status.html') def status(self, **ignored): return template.render(station=self.getStationName(), uptime=self.getUptime(), disk_space=self.getDiskSpace(), local_time=self.getLocalTime(), last_recording=self.getLastRecording(), next_recording=self.getNextRecording()) @cherrypy.expose @template.output('config.html') def config(self, config_timestamp=0, load_defaults=False, cancel=False, apply=False, export_config=False, new_config=None, import_config=False, **data): error = None values = None if cancel: raise cherrypy.HTTPRedirect('/') elif export_config: # use cherrypy utility to push the file for download. This also # means that we don't have to move the config file into the # web-accessible filesystem hierarchy return cherrypy.lib.static.serve_file( os.path.realpath(self._config.filename), "application/x-download", "attachment", os.path.basename(self._config.filename)) elif apply: # if someone else has modified the config, then warn the user # before saving their changes (overwriting the other's changes) if int(config_timestamp) == self._config.getTimestamp(): self.updateConfigFromPostData(self._config, data) # update file try: self._config.saveToFile() self._config.exportForShell(self._config.filename + ".sh") # bounce back to homepage raise cherrypy.HTTPRedirect('/') except IOError as e: # if save failed, put up error message and stay on the page error = 'Error saving: %s' % e else: error = genshi.HTML('''WARNING: Configuration has been changed externally.<br /> If you wish to keep your changes and lose the external changes, then click on 'Apply' again. <br /> To lose your changes and preserve the external changes, click 'Cancel'.''') # load the post data into a temporary configparser to preserve # the user's changes when the page is loaded again. This means # we don't have to duplicate the horrible POST-to-config code temp_conf = ConfigParser() self.updateConfigFromPostData(temp_conf, data) values = temp_conf.getValues() # the page loading below will send the new config timestamp so # we don't have to anything else here. if load_defaults: values = self._config.getDefaultValues() elif import_config: if new_config.filename: try: values = self._config.parseFile(new_config.file) except Exception as e: values = None error = 'Unable to parse config file: "%s"' % e else: error = 'No filename provided for config import' if not values: values = self._config.getValues() return template.render(station=self.getStationName(), config_timestamp=self._config.getTimestamp(), error=error, using_defaults=load_defaults, values=values, file=self._config.filename, defaults_file=self._config.defaults_filename) @cherrypy.expose @template.output('schedule.html') def schedule(self, cancel=None, apply=None, add=None, **data): error = None if cancel: raise cherrypy.HTTPRedirect('/') elif apply: # use dictionary so we can untangle the POST data schedules = {} # we must check for uniqueness of titles titles = set() # just get the titles for key in data: # each schedule comes in three parts: ?.title, ?.start, ?.finish if key.endswith('title'): id = key.split('.')[0] title = data[key] if title in titles: error = ('Schedule titles must be unique. Found ' 'duplicate of "%s"' % title) return template.render( station=self.getStationName(), recording_specs=self._schedule.getScheduleSpecs(), error=error, file=self._schedule.filename, add=None) schedules[id] = title titles.add(title) # clear all schedules # and add them back in the order they were on the webpage self._schedule.clearScheduleSpecs() # sort the labels by their id, then add them in that order schedule_ids = schedules.keys() schedule_ids.sort() for id in schedule_ids: start_key = "%s.start" % id finish_key = "%s.finish" % id if data.has_key(start_key) and data.has_key(finish_key): self._schedule.setScheduleSpec( ScheduleParser.parseRecordingSpec( schedules[id], data[start_key], data[finish_key])) # update file try: self._schedule.saveToFile() # bounce back to homepage raise cherrypy.HTTPRedirect('/') except IOError as e: # if save failed, put up error message and stay on the page error = "Error saving: %s" % e elif not add: # this should only happen when a remove button has been clicked # find the remove key for key in data: if key.endswith('remove'): # get the schedule key from the title id = key.split('.')[0] title_key = "%s.title" % id if data.has_key(title_key): schedule_key = data[title_key] self._schedule.deleteScheduleSpec(schedule_key) return template.render( station=self.getStationName(), error=error, recording_specs=self._schedule.getScheduleSpecs(), add=add, file=self._schedule.filename) @cherrypy.expose @template.output('recordings.html') def recordings(self, view='month', year=None, month=None, day=None, recording_id=None, set_recording_id=False, clear_recording_id=False, go_to_start=False, go_to_finish=False, go_to_today=False, filter_title=None, filter_start=None, filter_finish=None, update_filter=None, reset_filter=None, clear_selected_date=False, set_filter_title=None, set_filter_start=None, set_filter_finish=None, export_recording=False, export_start_time=None, export_finish_time=None, export_selection=False, delete_cache=False, **ignored): # HACK: this helps development slightly if ignored: print "IGNORED", ignored # initialise error list errors = [] if delete_cache: # Clear all recordings. The cron job will do the scanning/refilling. self._storage.clearRecordings() # Clear all recording file entries self._storage.clearRecordingFiles() # ensure no other commands are enabled recording_id = None export_selection = None if reset_filter: filter_title = None clearSession(SESSION_FILTER_TITLE_KEY) filter_start = None clearSession(SESSION_FILTER_START_KEY) filter_finish = None clearSession(SESSION_FILTER_FINISH_KEY) if clear_selected_date: clearSession(SESSION_DATE_KEY) if clear_recording_id: clearSession(SESSION_RECORD_ID_KEY) # update filters if update_filter: # filtering on title if filter_title == NO_FILTER_TITLE: filter_title = None clearSession(SESSION_FILTER_TITLE_KEY) else: setSession(SESSION_FILTER_TITLE_KEY, filter_title) # filtering on start date & time if filter_start is not None: if filter_start: try: filter_start = parseDateUI(filter_start) setSession(SESSION_FILTER_START_KEY, filter_start) except ValueError, inst: errors.append('Errror parsing filter start time: %s' % inst) filter_start = getSession(SESSION_FILTER_START_KEY) else: clearSession(SESSION_FILTER_START_KEY) filter_start = None if filter_finish is not None: if filter_finish: try: filter_finish = parseDateUI(filter_finish) setSession(SESSION_FILTER_FINISH_KEY, filter_finish) except ValueError, inst: errors.append('Errror parsing filter finish time: %s' % inst) filter_finish = getSession(SESSION_FILTER_FINISH_KEY) else: clearSession(SESSION_FILTER_FINISH_KEY) filter_finish = None