Exemplo n.º 1
0
def _ValidateData(data):
    """Check that all configuration data is valid.

  Args:
    data: Dictionary with the configuration data to validate.

  Raises:
    error.ConfigurationError: If there is any invalid field in the
      configuration.
  """
    # There should be an user and it cannot be the default value.
    if 'user' not in data:
        raise error.ConfigurationError('Username was not found in user data '
                                       'file.\n')
    if data['user'] == '*****@*****.**':
        raise error.ConfigurationError(
            'Remember to set your username in the user '
            'configuration file "{0}".\n'.format(constants.USER_CONFIG_FILE))

    # All input type names should not contain only lowercase letters and digits.
    if 'input_spec' not in data:
        raise error.ConfigurationError(
            'Input specification was not found in data '
            'files.\n')
    for input_name in data['input_spec']:
        if not _INPUT_NAME_REGEXP.match(input_name):
            raise error.ConfigurationError(
                'Input name "{0}" in input specification '
                'is invalid, it should contain only '
                'lowercase letters and digits.\n'.format(input_name))
Exemplo n.º 2
0
def _ValidateContestDataOrRaise(middleware_tokens, problems):
    """Validate that all contest information is complete.

  Args:
    middleware_tokens: Dictionary with all retrieved middleware tokens.
    problems: Sequence with all retrieved problem information.

  Raises:
    error.ConfigurationError: If the contest data is invalid or incomplete.
  """
    needed_tokens = {
        'GetInitialValues': ('Cannot find middleware token to get contest '
                             'status.\n'),
        'GetInputFile': ('Cannot find middleware token to get input files, '
                         'please login again.\n'),
        'GetUserStatus': ('Cannot find middleware token to get user status, '
                          'please login again.\n'),
        'SubmitAnswer':
        ('Cannot find middleware token to submit answers, please '
         'login again.\n'),
    }
    for token_name, user_reason in needed_tokens.iteritems():
        if token_name not in middleware_tokens:
            raise error.ConfigurationError(user_reason)

    if not problems:
        raise error.ConfigurationError('Cannot find problems in the contest, '
                                       'please check the contest id and try '
                                       'again.\n')
Exemplo n.º 3
0
def Login(password=None):
    """Renew contest cookie for the specified user in the host.

  Args:
    password: Password of the code jam contestant. If None, then the password
        will be retrieved using the GetUserPassword() function.

  Returns:
    The new contest cookie for the contestant.

  Raises:
    error.ConfigurationError: If the configuration file is missing or
      incomplete.
  """
    # Read the current configuration file and extract the host and username.
    try:
        contest_data = data_manager.ReadData()
        host = contest_data['host']
        user = contest_data['user']
    except KeyError as e:
        raise error.ConfigurationError(
            'No host or username was found in the user '
            'configuration file: {0}.\n'.format(e))

    # Before doing anything, check that this tool version is valid.
    CheckToolVersion(host, constants.VERSION)

    # Retrieve the password from elsewhere, as the user didn't provide one.
    if password is None:
        password = _GetUserPassword(user, contest_data)

    # Log in into Google servers using ClientLogin and show the cookie expiration
    # date in localtime.
    _, cookie = MakeLogin(host, user, password)
    cookie_expiration_time = google_login.GetCookieExpirationTime(cookie)
    if cookie_expiration_time is not None:
        sys.stdout.write('Login cookie will expire at {0}.\n'.format(
            _UTCToLocalDatetime(cookie_expiration_time,
                                '%a, %d-%b-%Y %H:%M:%S %Z',
                                '%Y-%m-%d %H:%M:%S')))
    else:
        sys.stdout.write('Login cookie expiration time not found.\n')

    # Get new middleware tokens and show the expiration date in localtime.
    middleware_tokens, tokens_expiration_time = _GetMiddlewareTokens(
        host, cookie)
    sys.stdout.write('Middleware tokens will expire at {0}.\n'.format(
        _UTCToLocalDatetime(tokens_expiration_time, '%Y-%m-%d %H:%M:%S')))

    # Store cookie and middleware tokens in the current configuration file.
    contest_data['cookie'] = cookie
    contest_data['middleware_tokens'] = middleware_tokens
    contest_data['tokens_expiration_time'] = tokens_expiration_time

    # Finally, write the contest data to the current data file and return the
    # cookie.
    data_manager.WriteData(contest_data)
    return cookie
Exemplo n.º 4
0
def _ValidateData(data):
  """Check that all configuration data is valid.

  Args:
    data: Dictionary with the configuration data to validate.

  Raises:
    error.ConfigurationError: If there is any invalid field in the
      configuration.
  """
  # There should be an user and it cannot be the default value.
  if 'user' not in data:
    raise error.ConfigurationError('Username was not found in user data '
                                   'file.\n')
  if data['user'] == '*****@*****.**':
    user_config_path = ParametrizeConfigPath(constants.USER_CONFIG_PATH)
    raise error.ConfigurationError('Remember to set your username in the user '
                                   'configuration file "{0}".\n'.format(
                                       user_config_path))
Exemplo n.º 5
0
def main():
    """Main function for the contest initializer script.

  This script receives one positional argument, which is the contest id.
  """
    try:
        # Create an option parser and use it to parse the supplied arguments.
        program_version = 'GCJ contest crawler {0}'.format(constants.VERSION)
        parser = optparse.OptionParser(usage='%prog [options] contest_id',
                                       version=program_version)
        parser.add_option(
            '-p',
            '--passwd',
            action='store',
            dest='password',
            help=('Password used to login in the server, will be '
                  'asked if not specified'))
        options, args = parser.parse_args()

        # Check that the number of arguments is valid.
        if len(args) == 1:
            # Extract the contest id and initialize the contest.
            contest_id = args[0]
        else:
            # Attempt to read off contest_id from config file
            try:
                contest_data = data_manager.ReadData()
                contest_id = contest_data['contest_id']
            except KeyError as e:
                # Indicate that no host or cookie was configured and exit with error.
                raise error.ConfigurationError(
                    'No host or login cookie found in the '
                    'configuration file: {0}.\n'.format(e))
                raise error.OptionError('requires single argument: contest_id')

        contest_manager.Initialize(contest_id, options.password)

    except error.OptionError as e:
        parser.print_usage()
        program_basename = os.path.basename(sys.argv[0])
        sys.stderr.write('{0}: error: {1}\n'.format(program_basename, e))
        sys.exit(1)

    except error.UserError as e:
        sys.stderr.write(str(e))
        sys.exit(1)

    except error.CommandlineError as e:
        sys.stderr.write('{0}: {1}'.format(e.__class__.__name__, e))
        sys.exit(1)
Exemplo n.º 6
0
def Initialize(contest_id, password=None):
    """Initialize configuration for the specified contest, storing the retrieved
  data in the current configuration file.

  Args:
    contest_id: ID of the contest to initialize.
    password: Password specified by the user, if any.
  """
    # Reset the current configuration file with the one provided by the user and
    # renew the cookie, so the middleware tokens are retrieved correctly.
    try:
        shutil.copy(constants.USER_CONFIG_FILE, constants.CURRENT_CONFIG_FILE)
        code_jam_login.Login(password)
    except OSError as e:
        raise error.InternalError(
            'Configuration file {0} could not be created: '
            '{1}.\n'.format(constants.CURRENT_CONFIG_FILE, e))

    # Read the current configuration file and extract the host and the cookie.
    try:
        contest_data = data_manager.ReadData()
        host = contest_data['host']
        cookie = contest_data['cookie']
    except KeyError as e:
        # Indicate that no host or cookie was configured and exit with error.
        raise error.ConfigurationError('No host or login cookie found in the '
                                       'configuration file: {0}.\n'.format(e))

    # Retrieve the problem list and validate the extracted contest data and exit
    # if there is any error.
    problems = _GetProblems(host, cookie, contest_id)
    _ValidateContestDataOrRaise(contest_data['middleware_tokens'], problems)

    # Add the contest id, the problems and the middleware tokens to the contest
    # data.
    contest_data['contest_id'] = contest_id
    contest_data['problems'] = problems

    # Finally, write the contest data to the current data file, and then
    # renew the cookie stored in the configuration.
    data_manager.WriteData(contest_data)
    sys.stdout.write('Contest {0} initialized successfully, {1} problem(s) '
                     'retrieved.\n'.format(contest_id, len(problems)))
Exemplo n.º 7
0
def GetUserStatus(host, cookie, middleware_token, contest_id, input_spec):
    """Get the current user's status from the server.

  Args:
    host: Domain name of the server where the contest is running.
    cookie: Cookie for the current user.
    middleware_token: Middleware authentication token for the current user.
    contest_id: Id of the contest where the user is participating.
    input_spec: Dictionary with the input specification, mapping from input name
        to another dictionary with a 'time_limit' key.

  Returns:
    An UserStatus object with the current user's status.

  Raises:
    error.ConfigurationError: If there is an input specification without time
      limit.
    error.NetworkError: If a network error occurs while communicating with the
      server.
    error.ServerError: If the server answers code distinct than 200 or the
      response is a malformed JSON.
  """
    # Send an HTTP request to get the user status.
    sys.stdout.write(
        'Getting user status at contest {0} from "{1}"...\n'.format(
            contest_id, host))
    request_referer = 'http://{0}/codejam/contest/dashboard?c={1}'.format(
        host, contest_id)
    request_arguments = {
        'cmd': 'GetUserStatus',
        'contest': contest_id,
        'zx': str(int(time.time())),
        'csrfmiddlewaretoken': str(middleware_token),
    }
    request_headers = {
        'Referer': request_referer,
        'Cookie': cookie,
    }
    try:
        status, reason, response = http_interface.Get(
            host, '/codejam/contest/dashboard/do', request_arguments,
            request_headers)
    except httplib.HTTPException as e:
        raise error.NetworkError(
            'HTTP exception while user status from the Google '
            'Code Jam server: {0}.\n'.format(e))

    # Check if the status is not good.
    if status != 200 or reason != 'OK':
        raise error.ServerError(
            'Error while communicating with the server, cannot '
            'get user status. Check that the host, username '
            'and contest id are valid.\n')

    # Sort and extract information from the input specification.
    try:
        parsed_input_spec = [
            (input_data['input_id'], input_name, input_data['time_limit'])
            for input_name, input_data in input_spec.iteritems()
        ]
        parsed_input_spec.sort()
        input_spec = [input_data[1:] for input_data in parsed_input_spec]
    except KeyError:
        raise error.ConfigurationError(
            'Wrong input specification, "time_limit" '
            'key not found.\n')

    # Parse the JSON response and return an object with the user status.
    try:
        json_response = json.loads(response)
        return UserStatus.FromJsonResponse(json_response, input_spec)
    except ValueError as e:
        raise error.ServerError(
            'Invalid response received from the server, cannot '
            'get user status. Check that the contest id is '
            'valid: {0}.\n'.format(e))
Exemplo n.º 8
0
def main():
  """Main function for the input downloader script.

  This script receives three positional arguments, the problem letter, the input
  size and the submit id.
  """
  try:
    # Create an option parser and use it to parse the supplied arguments.
    program_version = 'GCJ input downloader {0}'.format(
        constants.VERSION)
    parser = optparse.OptionParser(usage='%prog [options] problem input id',
                                   version=program_version)
    parser.add_option('-l', '--login', action='store_true', dest='renew_cookie',
                      help='Ignore the stored cookie, and log in again')
    parser.add_option('-p', '--passwd', action='store', dest='password',
                      help=('Password used to log in. You will be prompted for '
                            'a password if one is required and this flag is '
                            'left empty and there is no password in the '
                            'configuration files'))
    parser.add_option('-f', '--force', action='store_true', dest='force',
                      help=('Skip check to verify that user wants to start a '
                            'new timer'))
    parser.add_option('-d', '--data-directory', action='store',
                      dest='data_directory',
                      help='Directory with the I/O files and main source files')
    parser.add_option('-i', '--input-name', action='store', dest='input_name',
                      help='Name of the file where the input should be stored')
    parser.add_option('--base_dir', action='store', dest='base_dir',
                      help=('Base directory used to parametrize configuration '
                            'file paths'))
    parser.set_defaults(renew_cookie=False, force=False,
                        base_dir=os.path.dirname(os.path.realpath(__file__)))
    options, args = parser.parse_args()

    # Store the script location in a runtime constant, so it can be used by
    # the library to locate the configuration files.
    constants.SetRuntimeConstant('base_dir', options.base_dir)

    # Check that the number of arguments is valid.
    if len(args) != 3:
      raise error.OptionError('need 3 positional arguments')

    # Check that the problem idenfier is valid.
    problem_letter = args[0].upper()
    if len(problem_letter) != 1 or not problem_letter.isupper():
      raise error.OptionError('invalid problem {0}, must be one letter'.format(
          problem_letter))

    # Check that the submit id is a valid identifier.
    id = args[2]
    if not re.match('^\w+$', id):
      raise error.OptionError('invalid id {0}, can only have numbers, letters '
                              'and underscores'.format(id))

    # Check that the contest has been initialized.
    if not contest_manager.IsInitialized():
      raise error.ConfigurationError(
          'Contest is not initialized, please initialize the contest before '
          'trying to download input files.\n');

    # Read user and input information from the config file.
    try:
      current_config = data_manager.ReadData()
      host = current_config['host']
      user = current_config['user']
      input_spec = current_config['input_spec']
    except KeyError as e:
      raise error.ConfigurationError(
          'Cannot find all required user data in the configuration files: {0}. '
          'Please fill the missing fields in the user configuration '
          'file.\n'.format(e))

    # Check that the input type is valid.
    input_type = args[1].lower()
    if input_type not in input_spec:
      raise error.OptionError(
          'invalid input type {0}, must be one of ({1})'.format(
              input_type, ','.join(input_spec)))

    # Read current contest information from the config file.
    try:
      middleware_tokens = current_config['middleware_tokens']
      cookie = None if options.renew_cookie else current_config['cookie']
      contest_id = current_config['contest_id']
      problems = current_config['problems']
    except KeyError as e:
      raise error.ConfigurationError(
          'Cannot find all required contest data in configuration files: {0}. '
          'Reinitializing the contest might solve this error.\n'.format(e))

    # Get the needed middleware tokens to request the file and check for running
    # attempts.
    try:
      get_initial_values_token = middleware_tokens['GetInitialValues']
      download_input_token = middleware_tokens['GetInputFile']
      user_status_token = middleware_tokens['GetUserStatus']
    except KeyError as e:
      raise error.ConfigurationError(
          'Cannot find {0} token in configuration file. Reinitializing the '
          'contest might solve this error.\n'.format(e))

    # Calculate the problem index and check if it is inside the range.
    problem_index = ord(problem_letter) - ord('A')
    if problem_index < 0 or problem_index >= len(problems):
      raise error.UserError(
          'Cannot find problem {0}, there are only {1} problem(s).\n'.format(
              problem_letter, len(problems)))

    # Get the problem specification and the input id from the configuration.
    problem = problems[problem_index]
    try:
      input_id = input_spec[input_type]['input_id']
    except KeyError:
      raise error.ConfigurationError(
          'Input specification for "{1}" has no input_id.\n'.format(input_type))

    # Get the data directory from the options, if not defined, get it from the
    # configuration, using './source' as the default value if not found. In the
    # same way, get the input filename format.
    data_directory = (options.data_directory or
                      current_config.get('data_directory', './source'))
    input_name_format = (options.input_name or
                         current_config.get('input_name_format',
                                            '{problem}-{input}-{id}.in'))

    # Generate the input file name using the specified format and then return.
    try:
      input_basename = input_name_format.format(
          problem=problem_letter, input=input_type, id=id)
      input_filename = os.path.normpath(os.path.join(data_directory,
                                                     input_basename))
    except KeyError as e:
      # Print error message and exit.
      raise error.ConfigurationError(
          'Invalid input name format {0}, {1} is an invalid key, only use '
          '"problem", "input" and "id".\n'.format(input_name_format, e))

    # Print message indicating that an input is going to be downloaded.
    print '-' * 79
    print '{0} input for "{1} - {2}" at "{3}"'.format(
        input_type.capitalize(), problem_letter, problem['name'],
        input_filename)
    print '-' * 79

    # Renew the cookie if the user requested a new login or the cookie has
    # expired.
    if google_login.CookieHasExpired(cookie):
      print 'Cookie has expired, logging into the Code Jam servers...'
      cookie = None
    if not cookie or options.renew_cookie:
      cookie = code_jam_login.Login(options.password)

    # Get the contest status and check if it is accepting submissions.
    contest_status = contest_manager.GetContestStatus(
        host, cookie, get_initial_values_token, contest_id)
    if not options.force and not contest_manager.CanSubmit(contest_status):
      raise error.UserError('Cannot download inputs from this contest, its not '
                            'active or in practice mode.\n')

    # Get the user status and check if it is participating or not.
    input_index = utils.GetIndexFromInputId(input_spec, input_id)
    current_user_status = user_status.GetUserStatus(
        host, cookie, user_status_token, contest_id, input_spec)
    if (contest_status == contest_manager.ACTIVE and
        current_user_status is not None):
      # Check if this problem input can be downloaded or not. An input can be
      # downloaded as long as it has not been solved yet; a non-public input
      # also cannot have wrong tries.
      problem_inputs = current_user_status.problem_inputs
      problem_input_state = problem_inputs[problem_index][input_index]
      input_public = input_spec[input_type]['public']
      can_download = problem_input_state.solved_time == -1
      if not input_public:
        can_download = can_download and problem_input_state.wrong_tries == 0
      if not options.force and not can_download:
        raise error.UserError(
            'You cannot download {0}-{1}, it is already {2}.\n'.format(
                problem_letter, input_type,
                'solved' if input_public else ('submitted and the timer '
                                               'expired')))

      # Check if there is a pending attempt for this problem input. If there is
      # no pending attempt, show warning indicating that a new timer has
      # started.
      if problem_input_state.current_attempt == -1:
        # Show a warning message to the user indicating that a new input is
        # being downloaded, including the time available to solve it.
        remaining_time = input_spec[input_type]['time_limit']
        download_message = ('You will have {0} to submit your answer for '
                            '{1}-{2}.'.format(utils.FormatHumanTime(
                                remaining_time), problem_letter, input_type))
        utils.AskConfirmationOrDie(download_message, 'Download', options.force)
        print 'Downloading new input file.'
      else:
        # Show a message to the user indicating that the same input file is
        # being downloaded, including the time left to solve it.
        remaining_time = problem_input_state.current_attempt
        print ('You still have {0} to submit your answer for {1}-{2}.'.format(
            utils.FormatHumanTime(remaining_time), problem_letter, input_type))
        print 'Redownloading previous input file.'
    else:
      print 'Downloading practice input file.'

    # Create the input downloader and request the file.
    downloader = input_downloader.InputDownloader(
        host, cookie, download_input_token, contest_id, problem['id'])
    downloader.Download(input_id, input_filename)

  except error.OptionError as e:
    parser.print_usage()
    program_basename = os.path.basename(sys.argv[0])
    sys.stderr.write('{0}: error: {1}\n'.format(program_basename, e))
    sys.exit(1)

  except error.UserError as e:
    sys.stderr.write(str(e))
    sys.exit(1)

  except error.CommandlineError as e:
    sys.stderr.write('{0}: {1}'.format(e.__class__.__name__, e))
    sys.exit(1)
Exemplo n.º 9
0
def main():
    """Main function for the status grabber script.

  This script receives no positional arguments.
  """
    try:
        # Create an option parser and use it to parse the supplied arguments.
        program_version = 'GCJ status grabber {0}'.format(constants.VERSION)
        parser = optparse.OptionParser(usage='%prog [options]',
                                       version=program_version)
        parser.add_option('-l',
                          '--login',
                          action='store_true',
                          dest='renew_cookie',
                          help='Ignore the stored cookie, and log in again')
        parser.add_option(
            '-p',
            '--passwd',
            action='store',
            dest='password',
            help=('Password used to log in. You will be prompted for '
                  'a password if one is required and this flag is '
                  'left empty and there is no password in the '
                  'configuration files'))
        parser.add_option(
            '--base_dir',
            action='store',
            dest='base_dir',
            help=('Base directory used to parametrize configuration '
                  'file paths'))
        parser.set_defaults(renew_cookie=False,
                            base_dir=os.path.dirname(
                                os.path.realpath(__file__)))
        options, args = parser.parse_args()

        # Store the script location in a runtime constant, so it can be used by
        # the library to locate the configuration files.
        constants.SetRuntimeConstant('base_dir', options.base_dir)

        # Check that the number of arguments is valid.
        if len(args) != 0:
            raise error.OptionError('need no positional arguments')

        # Read user information from the config file.
        try:
            current_config = data_manager.ReadData()
            host = current_config['host']
            middleware_tokens = current_config['middleware_tokens']
            cookie = current_config['cookie']
            contest_id = current_config['contest_id']
            problems = current_config['problems']
        except KeyError as e:
            raise error.ConfigurationError(
                'Cannot find field {0} in the configuration files. Please fill the '
                'missing fields in the user configuration file.\n'.format(e))

        # Get the get middleware token used to request the user status.
        try:
            user_status_token = middleware_tokens['GetUserStatus']
        except KeyError:
            raise error.ConfigurationError(
                'Cannot find "GetUserStatus" token in configuration file. '
                'Reinitializing the contest might solve this error.\n')

        # Renew the cookie if the user requested a new login or the cookie has
        # expired.
        if google_login.CookieHasExpired(cookie):
            print 'Cookie has expired, logging into the Code Jam servers...'
            cookie = None
        if not cookie or options.renew_cookie:
            cookie = code_jam_login.Login(options.password)

        # Get user's status and check if he/she is participating.
        status = user_status.GetUserStatus(host, cookie, user_status_token,
                                           contest_id, problems)
        if status is not None:
            # Get user's submissions and use them to fix the user's status.
            submissions = user_submissions.GetUserSubmissions(
                host, cookie, contest_id, problems)
            user_status.FixStatusWithSubmissions(status, submissions)

            # Show each problem input status to the user.
            print '-' * 79
            print 'User status at contest {0}'.format(contest_id)
            print '-' * 79
            print 'Rank: {0} ({1} points)'.format(status.rank, status.points)
            for problem, problem_status in itertools.izip(
                    problems, status.problem_inputs):
                print 'Problem: {0}'.format(problem['name'])
                for input_status in problem_status:
                    print '  {0}'.format(
                        FormatProblemInputStatus(input_status))
        else:
            # Show error message, user is not participating in the contest.
            print 'User status not found at contest {0}'.format(contest_id)

    except error.OptionError as e:
        parser.print_usage()
        program_basename = os.path.basename(sys.argv[0])
        sys.stderr.write('{0}: error: {1}\n'.format(program_basename, e))
        sys.exit(1)

    except error.UserError as e:
        sys.stderr.write(str(e))
        sys.exit(1)

    except error.CommandlineError as e:
        sys.stderr.write('{0}: {1}'.format(e.__class__.__name__, e))
        sys.exit(1)
Exemplo n.º 10
0
def Initialize(tournament_id, contest_id, password=None):
    """Initialize configuration for the specified tournament or contest.

  This function initializes the tool for a contest. If the contest is None,
  the tool will be initialized for the current contest of the specified
  tournament.

  Either one of tournament_id or contest_id must be not None.

  The retrieved data is stored in the current configuration file.

  Args:
    tournament_id: ID of the tournament whose current contest must be
      initialized.
    contest_id: ID of the contest to initialize. If None, the server will ask
      for the current contest of the specified tournament.
    password: Password specified by the user, if any.

  Raises:
    error.ConfigurationError: If the contest data is invalid or incomplete.
    error.UserError: If no contest was specified and there is no running contest
      for the specified tournament.
  """
    # Reset the current configuration file with the one provided by the user and
    # renew the cookie, so the middleware tokens are retrieved correctly.
    try:
        user_config_path = data_manager.ParametrizeConfigPath(
            constants.USER_CONFIG_PATH)
        current_config_path = data_manager.ParametrizeConfigPath(
            constants.CURRENT_CONFIG_PATH)
        shutil.copy(user_config_path, current_config_path)
        code_jam_login.Login(password)
    except OSError as e:
        raise error.InternalError(
            'Configuration file {0} could not be created: '
            '{1}.\n'.format(current_config_path, e))

    # Read the current configuration file and extract the host and the cookie.
    try:
        contest_data = data_manager.ReadData()
        host = contest_data['host']
        cookie = contest_data['cookie']
    except KeyError as e:
        # Indicate that no host or cookie was configured and exit with error.
        raise error.ConfigurationError('No host or login cookie found in the '
                                       'configuration file: {0}.\n'.format(e))

    # Get the current contest if no contest id was specified. If there is no
    # running contest show an error to the user.
    if contest_id is None:
        contest_id = GetCurrentContestId(host, cookie, tournament_id)
        if contest_id is None:
            raise error.UserError(
                'No contest is running for tournament %s and no '
                'contest id was specified.\n' % tournament_id)
        sys.stdout.write(
            'Initializing tool for current contest with id %s.\n' % contest_id)

    # Retrieve the problem list and validate the extracted contest data and exit
    # if there is any error.
    problems = _GetProblems(host, cookie, contest_id)
    _ValidateContestDataOrRaise(contest_data['middleware_tokens'], problems)

    # Add the contest id, the problems and the middleware tokens to the contest
    # data.
    contest_data['contest_id'] = contest_id
    contest_data['problems'] = problems

    # Finally, write the contest data to the current data file, and then
    # renew the cookie stored in the configuration.
    data_manager.WriteData(contest_data)
    sys.stdout.write('Contest {0} initialized successfully, {1} problem(s) '
                     'retrieved.\n'.format(contest_id, len(problems)))
Exemplo n.º 11
0
def main():
    """Main function for the output submitter script.

  This script receives three positional arguments, the problem letter, the
  input size and the submit id.
  """
    try:
        # Create an option parser and use it to parse the supplied arguments.
        program_version = 'GCJ solution submitter {0}'.format(
            constants.VERSION)
        parser = optparse.OptionParser(
            usage='%prog [options] problem input id', version=program_version)
        parser.add_option('-l',
                          '--login',
                          action='store_true',
                          dest='renew_cookie',
                          help='Ignore the stored cookie and log in again')
        parser.add_option(
            '-p',
            '--passwd',
            action='store',
            dest='password',
            help=('Password used to log in. You will be prompted for '
                  'a password if one is required and this flag is '
                  'left empty and there is no password in the '
                  'configuration files'))
        parser.add_option(
            '-f',
            '--force',
            action='store_true',
            dest='force',
            help=('Skip check to verify if there is a running timer '
                  'and there is no submission if the input is large'))
        parser.add_option('-d',
                          '--data-directory',
                          action='store',
                          dest='data_directory',
                          help=('Directory with the I/O files and main source '
                                'files [default: ./source]'))
        parser.add_option('-o',
                          '--output-name',
                          action='store',
                          dest='output_name',
                          help='Name of the file with the solution\'s output')
        parser.add_option(
            '-a',
            '--add-source',
            action='append',
            dest='extra_sources',
            help='Add EXTRA_SOURCE to the submitted source files',
            metavar='EXTRA_SOURCE')
        parser.add_option('-z',
                          '--zip-sources',
                          action='store_true',
                          dest='zip_sources',
                          help=('Put the source files into a zip file before '
                                'submitting'))
        parser.add_option(
            '--ignore-zip',
            action='store_true',
            dest='ignore_zip',
            help=('Ignore source zip files not specified directly '
                  'using the -a option'))
        parser.add_option(
            '--ignore-default-source',
            action='store_true',
            dest='ignore_def_source',
            help=('Ignore files in the default source directory, '
                  'except for those specified using the -a option'))
        parser.add_option('--gzip-content',
                          action='store_true',
                          dest='gzip_content',
                          help=('Send the output and source code using gzip '
                                'encoding (faster)'))
        parser.add_option(
            '--nogzip-content',
            action='store_false',
            dest='gzip_content',
            help=('Send the output and sources using plain encoding '
                  '(slower)'))
        parser.add_option(
            '--base_dir',
            action='store',
            dest='base_dir',
            help=('Base directory used to parametrize configuration '
                  'file paths'))
        parser.set_defaults(renew_login=False,
                            force=False,
                            gzip_content=True,
                            zip_sources=False,
                            ignore_zip=False,
                            ignore_def_source=False,
                            base_dir=os.path.dirname(
                                os.path.realpath(__file__)))
        options, args = parser.parse_args()

        # Store the script location in a runtime constant, so it can be used by
        # the library to locate the configuration files.
        constants.SetRuntimeConstant('base_dir', options.base_dir)

        # Check that the number of arguments is valid.
        if len(args) != 3:
            raise error.OptionError('need 3 positional arguments')

        # Check that the problem idenfier is valid.
        problem_letter = args[0].upper()
        if len(problem_letter) != 1 or not problem_letter.isupper():
            raise error.OptionError(
                'invalid problem {0}, must be one uppercase letter'.format(
                    problem_letter))

        # Check that the submit id is a valid identifier.
        id = args[2]
        if not re.match('^\w+$', id):
            raise error.OptionError(
                'invalid id {0}, can only have numbers, letters '
                'and underscores'.format(id))

        # Check that the contest has been initialized.
        if not contest_manager.IsInitialized():
            raise error.ConfigurationError(
                'Contest is not initialized, please initialize the contest before '
                'trying to submit output files.\n')

        # Read user and input information from the config file.
        try:
            current_config = data_manager.ReadData()
            host = current_config['host']
            user = current_config['user']
            input_spec = current_config['input_spec']
        except KeyError as e:
            raise error.ConfigurationError(
                'Cannot find all required user data in the configuration files: {0}. '
                'Please fill the missing fields in the user configuration '
                'file.\n'.format(e))

        # Read current contest information from the config file.
        try:
            middleware_tokens = current_config['middleware_tokens']
            cookie = None if options.renew_cookie else current_config['cookie']
            contest_id = current_config['contest_id']
            problems = current_config['problems']
        except KeyError as e:
            raise error.ConfigurationError(
                'Cannot find all required contest data in configuration files: {0}. '
                'Reinitializing the contest might solve this error.\n'.format(
                    e))

        # Check that the input type is valid.
        input_type = args[1].lower()
        if input_type not in input_spec:
            raise error.OptionError('invalid input type {0}, must be one of '
                                    '({1})'.format(input_type,
                                                   ','.join(input_spec)))

        # Get the needed middleware tokens to submit solutions and check for running
        # attempts.
        try:
            get_initial_values_token = middleware_tokens['GetInitialValues']
            user_status_token = middleware_tokens['GetUserStatus']
            submit_output_token = middleware_tokens['SubmitAnswer']
        except KeyError as e:
            raise error.ConfigurationError(
                'Cannot find {0} token in configuration file. Reinitializing the '
                'contest might solve this error.\n'.format(e))

        # Calculate the problem index and check if it is inside the range.
        problem_index = ord(problem_letter) - ord('A')
        if problem_index < 0 or problem_index >= len(problems):
            raise error.UserError(
                'Cannot find problem {0}, there are only {1} '
                'problem(s).\n'.format(problem_letter, len(problems)))

        # Get the problem specification and the input id from the configuration.
        problem = problems[problem_index]
        try:
            input_id = input_spec[input_type]['input_id']
        except KeyError:
            raise error.ConfigurationError(
                'Input specification for "{1}" has no '
                'input_id.\n'.format(input_type))

        # Get the data directory from the options, if not defined, get it from the
        # configuration, using './source' as the default value if not found. In the
        # same way, get the output filename format and the main source code filename
        # format.
        data_directory = (options.data_directory
                          or current_config.get('data_directory', './source'))
        output_name_format = (options.output_name or current_config.get(
            'output_name_format', '{problem}-{input}-{id}.out'))
        source_names_format = current_config.get('source_names_format')

        # There is no sensible default for the main source, so exit with error if no
        # value is found and it wasn't ignored.
        if not options.ignore_def_source and source_names_format is None:
            raise error.UserError(
                'No format found for the default sources file name. Specify '
                '"source_name_format" in the configuration file or ignore it passing '
                '--ignore-default-source.\n')

        # Generate the output file name using the specified format and then return.
        try:
            output_basename = output_name_format.format(problem=problem_letter,
                                                        input=input_type,
                                                        id=id)
            output_filename = os.path.normpath(
                os.path.join(data_directory, output_basename))
        except KeyError as e:
            raise error.ConfigurationError(
                'Invalid output name format {0}, {1} is an invalid key, only use '
                '"problem", "input" and "id".\n'.format(input_name_format, e))

        # Create the list with all the source files and add the default source file
        # if it was requested.
        source_names = []
        if not options.ignore_def_source:
            try:
                # Process each source name in the source formats list.
                for source_name_format in source_names_format:
                    # Generate the source file name using the specified format and append
                    # it to the source list.
                    def_source_basename = source_name_format.format(
                        problem=problem_letter, input=input_type, id=id)
                    def_source_filename = os.path.normpath(
                        os.path.join(data_directory, def_source_basename))
                    source_names.append(def_source_filename)
            except KeyError as e:
                raise error.ConfigurationError(
                    'Invalid output name format {0}, {1} is an invalid key, only '
                    'use "problem", "input" and "id".\n'.format(
                        input_name_format, e))

        # Append any extra source file to the source list, normalizing their paths
        # for the current operative system.
        if options.extra_sources is not None:
            for extra_source_format in options.extra_sources:
                extra_source_file = extra_source_format.format(
                    problem=problem_letter, input=input_type, id=id)
                source_names.append(os.path.normpath(extra_source_file))

        # Print message indicating that an output is going to be submitted.
        print '-' * 79
        print '{0} output for "{1} - {2}" at "{3}"'.format(
            input_type.capitalize(), problem_letter, problem['name'],
            output_filename)
        print '-' * 79

        # Renew the cookie if the user requested a new login or the cookie has
        # expired.
        if google_login.CookieHasExpired(cookie):
            print 'Cookie has expired, logging into the Code Jam servers...'
            cookie = None
        if not cookie or options.renew_cookie:
            cookie = code_jam_login.Login(options.password)

        # Get the contest status and check if it is accepting submissions.
        contest_status = contest_manager.GetContestStatus(
            host, cookie, get_initial_values_token, contest_id)
        if not options.force and not contest_manager.CanSubmit(contest_status):
            raise error.UserError(
                'Cannot submit solutions to this contest, its not '
                'active or in practice mode.\n')

        # All problem inputs have public answers in practice mode.
        input_public = (input_spec[input_type]['public']
                        or contest_status == contest_manager.PRACTICE)

        # Get the user status and check if it is participating or not.
        input_index = utils.GetIndexFromInputId(input_spec, input_id)
        current_user_status = user_status.GetUserStatus(
            host, cookie, user_status_token, contest_id, input_spec)
        if (contest_status == contest_manager.ACTIVE
                and current_user_status is not None):
            # Check that there is a running timer for this problem input.
            problem_inputs = current_user_status.problem_inputs
            problem_input_state = problem_inputs[problem_index][input_index]
            if not options.force and problem_input_state.current_attempt == -1:
                raise error.UserError(
                    'You cannot submit {0}-{1}, the timer expired or you did not '
                    'download this input.\n'.format(problem_letter,
                                                    input_type))

            # Ask for confirmation if user is trying to resubmit a non-public output.
            if not input_public and problem_input_state.submitted:
                submit_message = (
                    'You already have submitted an output for {0}-{1}. '
                    'Resubmitting will override the previous one.'.format(
                        problem_letter, input_type))
                utils.AskConfirmationOrDie(submit_message, 'Submit',
                                           options.force)
                print 'Submitting new output and source files.'
            else:
                print 'Submitting output and source files.'
        else:
            print 'Submitting practice output and source files.'

        # Check if the contest is running and no source file is being included, show
        # a warning to the user indicating that he/she might be disqualified because
        # of this. This confirmation cannot be skipped by using --force.
        if contest_status == contest_manager.ACTIVE and not source_names:
            submit_message = (
                'You did not include source code files for this '
                'attempt. Submitting output files without source code '
                'can lead to disqualification.'.format(problem_letter,
                                                       input_type))
            utils.AskConfirmationOrDie(submit_message, 'Are you sure', False)
            print 'Submitting without source files.'

        # Create the output submitter and send the files.
        submitter = output_submitter.OutputSubmitter(host, cookie,
                                                     submit_output_token,
                                                     contest_id, problem['id'])
        submitter.Submit(input_id,
                         output_filename,
                         source_names,
                         input_public,
                         gzip_body=options.gzip_content,
                         zip_sources=options.zip_sources,
                         add_ignored_zips=not options.ignore_zip)

    except error.OptionError as e:
        parser.print_usage()
        program_basename = os.path.basename(sys.argv[0])
        sys.stderr.write('{0}: error: {1}\n'.format(program_basename, e))
        sys.exit(1)

    except error.UserError as e:
        sys.stderr.write(str(e))
        sys.exit(1)

    except error.CommandlineError as e:
        sys.stderr.write('{0}: {1}'.format(e.__class__.__name__, e))
        sys.exit(1)