def GetUserStatus(host, cookie, middleware_token, contest_id, problems): """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. problems: List with all problems in the contest. Returns: An UserStatus object with the current user's status. Raises: 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') # Parse the JSON response and return an object with the user status. try: json_response = json.loads(response) return UserStatus.FromJsonResponse(json_response, problems) 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))
def GetContestStatus(host, cookie, get_initial_values_token, contest_id): """Get the contest status of the specified contest. Args: host: Host where the contest is running. cookie: Cookie that the user received when authenticating. get_initial_values_token: Middleware token used to make the request. contest_id: ID of the contest whose problems must be read. Returns: The contest status. Raises: 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 problem list from the server. sys.stdout.write('Getting status of 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': 'GetInitialValues', 'contest': contest_id, 'zx': str(int(time.time())), 'csrfmiddlewaretoken': str(get_initial_values_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 retrieving contest 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 contest status. Check that the host, username ' 'and contest id are valid.\n') # Parse the JSON response and extract the contest status from it. try: json_response = json.loads(response) return json_response['cs'] except (KeyError, ValueError) as e: raise error.ServerError( 'Invalid response received from the server, cannot ' 'get contest status. Check that the contest id is ' 'valid: {0}.\n'.format(e))
def _GetMiddlewareTokens(host, cookie): """Get needed middleware tokens for the specified host. Args: host: Host where the contest is running. cookie: Cookie that the user received when authenticating. Returns: A tuple two elements: a dictionary containing all the middleware tokens, and the tokens expiration date. Raises: 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 problem list from the server. sys.stdout.write('Getting middleware tokens from "{0}"...\n'.format(host)) request_referer = 'http://{0}/codejam'.format(host) request_arguments = { 'cmd': 'GetMiddlewareTokens', 'actions': 'GetInitialValues,GetInputFile,GetUserStatus,SubmitAnswer', } request_headers = { 'Referer': request_referer, 'Cookie': cookie, } try: status, reason, response = http_interface.Get(host, '/codejam/middleware', request_arguments, request_headers) except httplib.HTTPException as e: raise error.NetworkError('HTTP exception while retrieving middleware ' 'tokens 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 middleware tokens. Check that the host, ' 'username and contest id are valid.\n') # Extract token information from server response. try: tokens_info = json.loads(response) return tokens_info['tokens'], tokens_info['expire'] except (KeyError, ValueError) as e: raise error.ServerError( 'Invalid response received from the server, cannot ' 'initialize contest. Check that the contest id is ' 'valid: {0}.\n'.format(e))
def GetCurrentContestId(host, cookie, tournament_id): # Send an HTTP request to get the problem list from the server. sys.stdout.write('Getting current contest of tournament {0} from ' '"{1}"...\n'.format(tournament_id, host)) request_arguments = { 't': tournament_id, 'zx': str(int(time.time())), } request_headers = { 'Referer': 'http://{0}/codejam', 'Cookie': cookie, } try: status, reason, response = http_interface.Get( host, '/codejam/contest/microsite-info', request_arguments, request_headers) except httplib.HTTPException as e: raise error.NetworkError( 'HTTP exception while retrieving current contest ' '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 current contest. Check that the host and ' 'username are valid.\n') # Parse the JSON response and extract the contest status from it. try: json_response = json.loads(response) return json_response.get('contestId', None) except ValueError as e: raise error.ServerError( 'Invalid response received from the server, cannot ' 'get current contest. Check that the tournament id ' 'is valid: {0}.\n'.format(e))
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))
def Submit(self, input_id, output_name, source_patterns, io_set_public, gzip_body=True, zip_sources=False, add_ignored_zips=False): """Submit the specified output and sources file to the problem. Args: input_id: Identifier of the output to submit ('0' for the small output, '1' for the large output). output_name: Name of the file with the output data. source_patterns: Name patterns of the source files to be included with the output. These patterns will be expanded using Python's glob module. io_set_public: Boolean indicating whether the answer is public or not. gzip_body: Boolean indicating whether the body has to be gzipped or not. zip_sources: Boolean indicating whether all sources should be put inside a zip file or not. add_ignored_zips: Boolean indicating whether zip files that are not included directly but are inside a included directory should be submitted or not. Raises: error.InternalError: If an error occurs while copying ignored zip files to the final location. error.NetworkError: If a network error occurs while communicating with the server. error.ServerError: If the server answers code distinct than 200. """ # Prepare the source files (zipping all directories). After this, # source_files will only contain text files and zip files specified directly # or by compressing a directory. source_files, ignored_zips = self._PrepareSourceFiles( set(source_patterns)) # Check if the user requested to zip source files. if zip_sources: # Extract all files to zip into a list and remove them from the # source files list. sources_to_zip = [ filename for filename, file_data in source_files if file_data is None ] source_files = [(filename, file_data) for filename, file_data in source_files if file_data is not None] # Generate a zip file with all the flat source files. sys.stdout.write('Compressing files "{0}"...\n'.format( ', '.join(sources_to_zip))) zipped_sources, _ = zip_utils.MakeZipFileInMemory( sources_to_zip, ignore_exts=['.zip']) # Generate a random name for the zipped source files and add it to the # source files to send. zip_filename = '__plain_files_{0}.zip'.format( random.randrange(0, 2**31 - 1)) source_files.append((zip_filename, zipped_sources)) # Check if the user requested to add the ignored zip files inside included # directories. if add_ignored_zips: # Copy and add each ignored file to the source list. for ignored_zip in ignored_zips: # Read the contents of the ignored zip file. try: zip_file = open(ignored_zip, 'rb') zip_file_contents = zip_file.read() zip_file.close() except IOError as e: raise error.InternalError( 'I/O error happened while read zip file ' '"{0}": {1}.\n'.format(ignored_zip, e)) # Generate the zip flat filename by substituting path with underscores # and adding a random number to prevent collisions. Then use it to add # the zip file and its contents into the source files to send. path, ext = os.path.splitext(ignored_zip) zip_flat_filename = '{1}_{0}{2}'.format( random.randrange(0, 2**31 - 1), path.replace('\\', '_').replace('/', '_'), ext) source_files.append((zip_flat_filename, zip_file_contents)) # Print message and check that at least one source file was included. if source_files: sources_str = ', '.join('"{0}"'.format(filename) for filename, _ in source_files) sys.stdout.write( 'Sending output file "{0}" and source(s) {1} to "{2}"' '...\n'.format(output_name, sources_str, self.host)) else: # Print warning saying that no source file is being included, this might # cause disqualification in a real contest. sys.stdout.write( 'Warning, no source files are being sent for output ' 'file "{0}"!\n'.format(output_name)) sys.stdout.write('Sending output file "{0}" to "{1}"...\n'.format( output_name, self.host)) # Generate a random boundary string to separate multipart data and # create a multipart data object with it. Then fill it with the necessary # arguments. multipart_boundary = '----gcjMultipartBoundary{0}'.format( random.randrange(0, 2**31 - 1)) body_data = multipart_data.MultipartData(multipart_boundary) body_data.AddString('csrfmiddlewaretoken', self.middleware_token) body_data.AddFile('answer', output_name) for i, (filename, file_data) in enumerate(source_files): body_data.AddFile('source-file{0}'.format(i), filename, file_data) body_data.AddString('source-file-name{0}'.format(i), filename) body_data.AddString('cmd', 'SubmitAnswer') body_data.AddString('contest', self.contest_id) body_data.AddString('problem', self.problem_id) body_data.AddString('input_id', input_id) body_data.AddString('num_source_files', str(len(source_files))) body_data.AddString('agent', constants.CODEJAM_AGENT_NAME) # Get the message body and check if compression was requested. request_body = str(body_data) if gzip_body: compressed_body = zip_utils.ZipData(request_body) sys.stdout.write('Sending {0} bytes to server ({1} uncompressed)' '...\n'.format(len(compressed_body), len(request_body))) request_body = compressed_body else: sys.stdout.write('Sending {0} bytes to server...\n'.format( len(request_body))) # Send an HTTP request with the output file and the source files. request_url = '/codejam/contest/dashboard/do' request_referer = 'http://{0}/codejam/contest/dashboard?c={1}'.format( self.host, self.contest_id) request_arguments = {} request_headers = { 'Content-Encoding': 'gzip' if gzip_body else 'text/plain', 'Content-Type': 'multipart/form-data; boundary={0}'.format(multipart_boundary), 'Referer': request_referer, 'Cookie': self.cookie, } try: status, reason, response = http_interface.Post( self.host, request_url, request_arguments, request_headers, request_body) except httplib.HTTPException as e: raise error.NetworkError( 'HTTP exception while sending solution to 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 submit solution. Check that the host, ' 'username and contest id are valid.\n') # Check if the server accepted the input or just ignored it. If it # accepted it, parse the response and print the submission result. if response: submit_result = self._ParseResult(response, io_set_public) sys.stdout.write('{0}\n'.format(submit_result)) else: raise error.ServerError( 'No response received from the server. This generally happens if ' 'you try to submit the large output before solving the small ' 'input.\n')
def Download(self, input_id, filename): """Download the specified input and store it in the specified file. Args: input_id: Identifier of the input to download ('0' for the small input and '1' for the large input). filename: Name of the file where the input data must be stored. """ # Send an HTTP request to get the input file from the server. sys.stdout.write('Getting input file "{0}" from "{1}"...\n'.format( filename, self.host)) request_url = '/codejam/contest/dashboard/do/{0}'.format(filename) request_referer = 'http://{0}/codejam/contest/dashboard?c={1}'.format( self.host, self.contest_id) request_arguments = { 'cmd': 'GetInputFile', 'contest': self.contest_id, 'problem': str(self.problem_id), 'input_id': input_id, 'filename': filename, 'input_file_type': '0', 'csrfmiddlewaretoken': self.middleware_token, 'agent': constants.CODEJAM_AGENT_NAME, } request_headers = { 'Referer': request_referer, 'Cookie': self.cookie, } try: status, reason, response = http_interface.Get( self.host, request_url, request_arguments, request_headers) except httplib.HTTPException as e: raise error.NetworkError( 'HTTP exception while getting input file: ' '{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 download input. Check that the host, ' 'username and contest id are valid.\n') # No response from the server, output warning. if not response: raise error.ServerError( 'No response received from the server. This generally happens when:\n' ' - You try to download a small input but it is already solved.\n' ' - You try to download the large input before solving the small ' 'input.\n' ' - You try to redownload the large but its timer already ' 'expired.\n') # Write response to the desired file. try: input_file = open(filename, 'wt') input_file.write(response) input_file.close() sys.stdout.write( 'File "{0}" downloaded successfully.\n'.format(filename)) except IOError as e: raise error.InternalError('I/O error while writing file "{0}": ' '{1}.\n'.format(filename, e))
def _MakeLogin(host, user, password): """Retrieve the authentication token and cookie from the code jam server, using the given user and password to authenticate. Args: host: Name of the host that runs the competition. user: User to authenticate in the Code Jam servers. password: Password of the user. Returns: A tuple with the authentication token and the login cookie. Raises: error.AuthenticationError: If the server answers with an authentication error, as specified in the GoogleLogin protocol. error.NetworkError: If there was a problem while communicating with the server. """ try: # Get the authentication token and gae cookie using the GoogleLogin module. sys.stdout.write('Logging into "{0}" with user "{1}"...\n'.format( host, user)) application_name = 'gcj_commandline-{0}'.format(constants.VERSION) auth_token, cookie = google_login.Login( host, 'HOSTED_OR_GOOGLE', user, password, 'ah', application_name, False) sys.stdout.write('Successfully logged into "{0}" with user "{1}".\n'.format( host, user)) return auth_token, cookie except google_login.AuthenticationError as e: # Return a exception with a human-readable error based on the error and exit # with an error code. if e.reason == 'BadAuthentication': raise error.AuthenticationError('Invalid username or password.\n') elif e.reason == 'CaptchaRequired': raise error.AuthenticationError( 'Please go to https://www.google.com/accounts/DisplayUnlockCaptcha ' 'and verify you are a human. Then try again.\n') elif e.reason == 'NotVerified': raise error.AuthenticationError('Account not verified.') elif e.reason == 'TermsNotAgreed': raise error.AuthenticationError('User has not agreed to TOS.') elif e.reason == 'AccountDeleted': raise error.AuthenticationError('The user account has been deleted.') elif e.reason == 'AccountDisabled': raise error.AuthenticationError('The user account has been disabled.') elif e.reason == 'ServiceDisabled': raise error.AuthenticationError('The user\'s access to the service has ' 'been disabled.') elif e.reason == 'ServiceUnavailable': raise error.AuthenticationError('The service is not available, try again ' 'later.') else: raise error.AuthenticationError('Unrecognized authentication error. ' 'Reason: %s' % e.reason) except urllib2.HTTPError as e: explanation = BaseHTTPServer.BaseHTTPRequestHandler.responses[e.code][0] raise error.NetworkError('HTTP error while logging into the Google Code ' 'Jam server ({0}): {1}\n'.format(e.code, explanation))
def GetUserSubmissions(host, cookie, contest_id, problems): """Get the current user's submissions for the current contest. Args: host: Domain name of the server where the contest is running. cookie: Cookie for the current user. contest_id: Id of the contest where the user is participating. problems: Iterable with all problems in the current contest. Returns: A list of UserSubmission objects with the user submissions for the current contest. Raises: 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 contest events. sys.stdout.write('Getting events of 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': 'GetEvents', 'contest': contest_id, 'zx': str(int(time.time())), } 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 retrieving user submissions 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 contest events. Check that the host, username ' 'and contest id are valid.\n') # Parse the JSON response and extract the user submissions (or attempts). try: json_response = json.loads(response) submissions = json_response.get('a') if submissions is None: return None except ValueError as e: raise error.ServerError('Cannot parse JSON from server response: ' '{0}.\n'.format(e)) # Process each user submission and return them in a list. return [ UserSubmission.FromJsonResponse(submission, problems) for submission in submissions ]
def CheckToolVersion(host, tool_version): """Check if the specified tool_version is accepted by the host. Args: host: Host where the contest is running. tool_version: String with this tool's version. Raises: error.InternalError: If the tool's version is not accepted by the host. 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 problem list from the server. sys.stdout.write('Checking tool version for "{0}"...\n'.format(host)) request_referer = 'http://{0}/codejam'.format(host) request_arguments = { 'cmd': 'CheckVersion', 'version': tool_version, } request_headers = { 'Referer': request_referer, } try: status, reason, response = http_interface.Get(host, '/codejam/cmdline', request_arguments, request_headers) except httplib.HTTPException as e: raise error.NetworkError( 'HTTP exception while checking tool version with ' 'the Google Code Jam server: {0}\n'.format(e)) # The current server version might not support this yet. Print a warning # message and skip validation. if status == 404: print 'WARNING: Cannot check commandline version with the server.' return # Check if the status is not good. if status != 200 or reason != 'OK': raise error.ServerError( 'Error while communicating with the server, cannot ' 'check tool version. Check that the host is ' 'valid.\n') # Extract token information from server response. try: validation_info = json.loads(response) if validation_info['msg']: print validation_info['msg'] if not validation_info['valid']: raise error.InternalError( 'This tool\'s version is not accepted by host ' '{0}, please update to the latest ' 'version.\n'.format(host, tool_version)) except (KeyError, ValueError) as e: raise error.ServerError( 'Invalid response received from the server, cannot ' 'initialize contest. Check that the contest id is ' 'valid: {0}.\n'.format(e))
def _GetProblems(host, cookie, contest_id): """Read the problems of the specified contest. Args: host: Host where the contest is running. cookie: Cookie that the user received when authenticating. contest_id: ID of the contest whose problems must be read. Returns: A tuple with two lists, the first with the problem IDs and the second with the problem names. Raises: 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 problem list from the server. sys.stdout.write( 'Getting problem list of 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': 'GetProblems', 'contest': contest_id, } 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 retrieving problem ' 'information 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 problem information. Check that the host, ' 'username and contest id are valid.\n') # Parse the JSON response and extract each problem from it. try: problems = [] json_response = json.loads(response) for problem in json_response: problems.append({ 'key': problem['key'], 'id': problem['id'], 'name': problem['name'] }) return problems except (KeyError, ValueError) as e: raise error.ServerError( 'Invalid response received from the server, cannot ' 'initialize contest. Check that the contest id is ' 'valid: {0}.\n'.format(e))