Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python3
- # -*- coding: utf-8 -*-
- '''
- Create a new recording rule for the title passed on the command line.
- Use: add_recording_rule.py --help to get started.
- The --title <title> must match a program name exactly and it will also
- become the name of the new recording rule. If the rule already exists,
- the program will abort.
- This is pep8 and pylint compliant. Tested using Python 3.
- Install as: add_recording_rule.py, chmod a+rx.
- '''
- import argparse
- import json
- import logging
- import sys
- try:
- from MythTV.services_api import (send as api, utilities as util)
- except ImportError:
- sys.exit("Missing python bindings?")
- TYPES = (
- "Single Record",
- "Record All",
- "Record One",
- "Record Daily",
- "Record Weekly",
- )
- # pylint: disable=consider-using-f-string
- def process_command_line():
- '''All command line processing is done here.'''
- parser = argparse.ArgumentParser(description='Add a recording rule',
- epilog='Default values are in ()s')
- mandatory = parser.add_argument_group('required arguments')
- parser.add_argument('--debug', action='store_true',
- help='turn on debug messages (%(default)s)')
- parser.add_argument('--digest', type=str, metavar='<user:pass>',
- help='digest username:password (%(default)s)')
- mandatory.add_argument('--host', type=str, required=True,
- metavar='<hostname>', help='backend hostname')
- parser.add_argument('--port', type=int, default=6544, metavar='<port>',
- help='port number of the Services API (%(default)s)')
- parser.add_argument('--quiet', action='store_true',
- help='suppress progress messages (%(default)s)')
- parser.add_argument('--template', type=str, required=False,
- default='Default', metavar='<temp>',
- help='template name, (%(default)s)')
- mandatory.add_argument('--title', type=str, required=True,
- metavar='<title>',
- help='full program name, no wild cards/regex')
- parser.add_argument('--type', type=str, required=False, choices=(TYPES),
- default='Record All', metavar='<type>',
- help='Record <type>, (%(default)s)')
- parser.add_argument('--version', action='version', version='%(prog)s 0.14')
- parser.add_argument('--wrmi', action='store_true',
- help='allow data to be changed (%(default)s)')
- return vars(parser.parse_args())
- def setup(backend, opts):
- '''
- Make sure the backend is up (GetHostName) and then set the backend's UTC
- offset for other methods to use.
- '''
- try:
- backend.send(endpoint='Myth/GetHostName', opts=opts)
- int(util.get_utc_offset(backend=backend, opts=opts))
- except ValueError:
- sys.exit('\nExit, non integer response from get_utc_offset.')
- except RuntimeError as error:
- sys.exit('\nExit on fatal API error: "{}"'.format(error))
- def schedule_already_exists(backend, args, opts):
- '''
- See if there's already a rule for the title.
- '''
- endpoint = 'Dvr/GetRecordScheduleList'
- try:
- resp_dict = backend.send(endpoint=endpoint, opts=opts)
- except RuntimeError as error:
- sys.exit('\nExit, Get Existing Rule: Fatal error; "{}"'.format(error))
- if int(resp_dict['RecRuleList']['Count']) < 1:
- sys.exit('\nExit, no recording rules found.\n')
- for rule in resp_dict['RecRuleList']['RecRules']:
- if rule['Title'] == args['title']:
- if args['debug']:
- print(json.dumps(rule, sort_keys=True, indent=4,
- separators=(',', ': ')))
- return True
- return False
- def get_template(backend, args, opts):
- '''
- Gets the requested (or default) template. This will be modified
- with guide data for the title of interest, then send to the
- backend in a POST. Misspelled template names return the Default
- template.
- Only the template name is used, not the trailing: (Template) text.
- '''
- endpoint = 'Dvr/GetRecordSchedule'
- rest = 'Template={}'.format(args['template'])
- try:
- resp_dict = backend.send(endpoint=endpoint, rest=rest, opts=opts)
- except RuntimeError as error:
- sys.exit('\nExit, Get Template: Fatal error; "{}"'.format(error))
- if args['debug']:
- print(json.dumps(resp_dict['RecRule'], sort_keys=True, indent=4,
- separators=(',', ': ')))
- # Templates are always Id -1, just double checking here...
- if resp_dict['RecRule']['Id'] != '-1':
- return None
- return resp_dict['RecRule']
- def get_program_data(backend, args, opts):
- '''
- Find matching program(s) from the guide. Note that if --title=Blah,
- then any title with the string Blah in it will be returned by
- GetProgramList.
- '''
- endpoint = 'Guide/GetProgramList'
- rest = 'Details=True&TitleFilter={}'.format(args['title'])
- try:
- resp_dict = backend.send(endpoint=endpoint, rest=rest, opts=opts)
- except RuntimeError as error:
- sys.exit('\nExit, Get Upcoming: Fatal error; "{}"'.format(error))
- count = int(resp_dict['ProgramList']['TotalAvailable'])
- if args['debug']:
- print('\nDebug: Programs matching --title {} = {}'
- .format(args['title'], count))
- if count < 1:
- sys.exit('\nExit, No programs in the guide matching: {}.\n'
- .format(args['title']))
- for program in resp_dict['ProgramList']['Programs']:
- if args['debug']:
- print('Comparing {} to {}'.format(args['title'], program['Title']))
- if program['Title'] == args['title']:
- if args['debug']:
- print(json.dumps(program, sort_keys=True, indent=4,
- separators=(',', ': ')))
- return program
- continue
- return None
- def update_template(template, guide_data, args):
- '''
- Put selected guide information into the template to be sent as
- postdata for the new rule.
- '''
- try:
- template['StartTime'] = guide_data['StartTime']
- template['EndTime'] = guide_data['EndTime']
- template['Title'] = guide_data['Title']
- template['Type'] = args['type']
- template['Station'] = guide_data['Channel']['CallSign']
- template['ChanId'] = guide_data['Channel']['ChanId']
- template['SearchType'] = 'None'
- template['Category'] = guide_data['Category']
- template['SeriesId'] = guide_data['SeriesId']
- template['ProgramId'] = guide_data['ProgramId']
- template['FindTime'] = util.create_find_time(guide_data['StartTime'])
- template['Description'] = 'Rule created by add_recording_rule.py'
- except KeyError:
- return False
- return True
- def add_rule(backend, template, args, opts):
- '''
- Send the changed data to the backend.
- '''
- endpoint = 'Dvr/AddRecordSchedule'
- params_not_sent = ('AverageDelay', 'CallSign', 'Id', 'LastDeleted',
- 'LastRecorded', 'NextRecording', 'ParentId')
- for param in params_not_sent:
- try:
- del template[param]
- except KeyError:
- pass
- opts['wrmi'] = args['wrmi']
- try:
- resp_dict = backend.send(endpoint=endpoint, postdata=template,
- opts=opts)
- except RuntimeWarning as error:
- sys.exit('Exit, Unable to add rule: {}. Warning was: {}.'
- .format(template['Title'], error))
- except RuntimeError as error:
- sys.exit('\nExit, Fatal API Error response: {}\n'.format(error))
- opts['wrmi'] = False
- if isinstance(resp_dict, dict) and isinstance(resp_dict['uint'], str):
- recording_rule = int(resp_dict['uint'])
- if recording_rule < 4294967295:
- vprint('\nAdded: "{}" (RecordId {}).'
- .format(template['Title'], recording_rule), args)
- else:
- recording_rule = -1
- vprint('Backend failed to add: "{}" (RecordId {}).'
- .format(template['Title'], recording_rule), args)
- else:
- vprint('Expected a "uint: int" dictionary response, but got {}'
- .format(resp_dict), args)
- def vprint(message, args):
- '''
- Verbose Print: print recording rule information unless --quiet
- was used. Not fully implemented, as there are still lots of
- print()s here.
- The intention is that if run out of some other program, this
- will can remain quiet. sys.exit()s will return 1 for failures.
- This may get expanded to put messages in a log...
- '''
- if not args['quiet']:
- print(message)
- def main():
- '''
- The primary job of main is to get the arguments from the command line,
- setup logging (and possibly) handle the digest user/password then:
- • Create an instance of the Send class
- • See if a rule exists for --title
- • Get the selected template
- • Get data for command line title from the guide
- • Update the template with the guide data
- • Add the rule on the backend.
- '''
- args = process_command_line()
- opts = {}
- logging.basicConfig(level=logging.DEBUG if args['debug'] else logging.INFO)
- logging.getLogger('requests.packages.urllib3').setLevel(logging.WARNING)
- logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)
- try:
- opts['user'], opts['pass'] = args['digest'].split(':', 1)
- except (AttributeError, ValueError):
- pass
- backend = api.Send(host=args['host'], port=args['port'])
- setup(backend, opts)
- if schedule_already_exists(backend, args, opts):
- sys.exit('\nExit, rule for: {} already exists.'.format(args['title']))
- template = get_template(backend, args, opts)
- if not template:
- sys.exit('\nExit, no template found for: {}.'.format(args['template']))
- guide_data = get_program_data(backend, args, opts)
- if not guide_data:
- sys.exit('\nExit, no match in guide for: {}'.format(args['title']))
- if update_template(template, guide_data, args):
- add_rule(backend, template, args, opts)
- else:
- sys.exit('\nExit, error while copying guide data to template.')
- if __name__ == '__main__':
- main()
- # vim: set expandtab tabstop=4 shiftwidth=4 smartindent noai colorcolumn=80:
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement