예제 #1
0
def process_update(item, parser=settings.OPENRA_VERSIONS[0]):
    """Create a new revision of a map by running the OpenRA.Utility
       update command using a given parser version

       Returns the updated map revision or raises an InvalidMapException on error
    """
    print('Starting map update action on map {}. Using parser: {}'.format(item.id, parser))
    if item.next_rev != 0:
        raise InvalidMapException('A newer revision of this map already exists')

    # Find the oramap file in the data directory
    source_path = os.path.join(settings.BASE_DIR, 'openra', 'data', 'maps', str(item.id))
    oramap_filename = misc.first_oramap_in_directory(source_path)
    if not oramap_filename:
        raise InvalidMapException('Map directory does not contain an .oramap package')

    # Create a temporary working directory and copy the oramap to it
    # Directory is automatically deleted when exiting the context manager
    with tempfile.TemporaryDirectory() as working_path:
        print('Temporary working directory: {}'.format(working_path))
        oramap_path = os.path.join(working_path, oramap_filename)
        shutil.copy(os.path.join(source_path, oramap_filename), oramap_path)

        # Run OpenRA.Utility to do the actual update
        print('Running --update-map')
        update_retcode, update_output = utility.run_utility_command(parser, item.game_mod, [
            '--update-map',
            oramap_path,
            item.parser,
            '--apply'
        ])

        # Error code if the command crashed, usage line on invalid arguments
        if update_retcode != 0 or update_output.startswith('--update-map MAP SOURCE'):
            raise InvalidMapException('OpenRA.Utility --update-map returned an error')

        if not update_output:
            raise InvalidMapException('OpenRA.Utility --update-map returned no output')

        if update_output.startswith('No such command'):
            raise InvalidMapException('OpenRA.Utility --update-map command missing')

        policy = {
            'cc': item.policy_cc,
            'commercial': item.policy_commercial,
            'adaptations': item.policy_adaptations
        }

        return add_map_revision(oramap_path, item.user,
                                parser, item.game_mod,
                                item.info, policy,
                                item.posted, # preserve original date to avoid reordering map list
                                item.revision + 1, item.id)
예제 #2
0
def add_map_revision(oramap_path,
                     user,
                     parser,
                     game_mod,
                     info,
                     policy_options,
                     posted_date,
                     revision=1,
                     previous_revision_id=0):
    """ Parse and save a given oramap into the database.
        The input file is not modified, and must be cleaned up afterwards by the caller.
        Returns a Maps model or raises an InvalidMapException on error
    """
    print('Running --map-hash')
    hash_retcode, map_hash = utility.run_utility_command(
        parser, game_mod, ['--map-hash', oramap_path])

    if hash_retcode != 0 or not map_hash:
        raise InvalidMapException('Hash calculation failed.')

    # Require unique map hashes - allowing maps to have multiple RC entries
    # makes it difficult to return consistent data through the map API
    if Maps.objects.filter(map_hash=map_hash).exists():
        raise InvalidMapException("Map has already been uploaded.")

    # Parse metadata
    print('Parsing map.yaml metadata')
    metadata = utility.parse_map_metadata(oramap_path)
    if not metadata:
        misc.send_email_to_admin_OnMapFail(oramap_path)
        raise InvalidMapException('Unable to parse map metadata.')

    if int(metadata['mapformat']) < 10:
        raise InvalidMapException(
            'Unable to import maps older than map format 10.')

    # Only attempt to parse rules for the default mods
    # This will be fixed properly once we move to mod-based parsing
    is_known_mod = metadata['game_mod'] in ['ra', 'cnc', 'd2k', 'ts']

    if is_known_mod:
        rules_retcode, custom_rules = utility.run_utility_command(
            parser, metadata['game_mod'], ['--map-rules', oramap_path])

        if rules_retcode != 0:
            misc.send_email_to_admin_OnMapFail(oramap_path)
            raise InvalidMapException('Failed to parse custom rules.')

        # TODO: Check against the game's Ruleset.DefinesUnsafeCustomRules code instead of line count
        advanced = len(custom_rules.split("\n")) > 8
        base64_rules = base64.b64encode(custom_rules.encode()).decode()
    else:
        base64_rules = ''
        advanced = False

    expect_metadata_keys = [
        'title', 'author', 'categories', 'players', 'game_mod', 'width',
        'height', 'bounds', 'mapformat', 'spawnpoints', 'tileset',
        'base64_players', 'lua'
    ]

    keyargs = {}
    for key in expect_metadata_keys:
        if key not in metadata:
            raise InvalidMapException('Map metadata missing required key: ' +
                                      key + '.')
        keyargs[key] = metadata[key]

    # Add record to Database
    item = Maps(
        map_hash=map_hash,
        revision=revision,
        pre_rev=previous_revision_id,
        next_rev=0,
        posted=posted_date,
        viewed=0,
        base64_rules=base64_rules,
        parser=parser,
        user=user,
        info=info,
        downloading=True,
        requires_upgrade=not is_known_mod,
        advanced_map=advanced,
        policy_cc=policy_options['cc'],
        policy_commercial=policy_options['commercial'],
        policy_adaptations=policy_options['adaptations'],
        description='',  # Obsolete field
        map_type='',  # Obsolete field
        shellmap=False,  # Obsolete field
        legacy_map=False,  # Obsolete field
        **keyargs)
    item.save()

    # Copy the updated map to its new data location
    item_path = os.path.join(settings.BASE_DIR, 'openra', 'data', 'maps',
                             str(item.id))
    item_content_path = os.path.join(item_path, 'content')
    if not os.path.exists(item_content_path):
        os.makedirs(item_content_path)

    item_map_path = os.path.join(item_path, os.path.basename(oramap_path))
    shutil.copy(oramap_path, item_map_path)

    if previous_revision_id:
        previous_item = Maps.objects.get(id=previous_revision_id)
        previous_item.next_rev = item.id
        previous_item.save()

    # Extract the oramap contents
    # TODO: Why do we need this?
    with zipfile.ZipFile(item_map_path, mode='a') as oramap:
        try:
            oramap.extractall(item_content_path)
        except Exception:
            pass

    if is_known_mod:
        print('Running --check-yaml')
        lint_retcode, lint_output = utility.run_utility_command(
            parser, item.game_mod, ['--check-yaml', item_map_path])

        if lint_output:
            Lints(
                item_type='maps',
                map_id=item.id,
                version_tag=parser,
                pass_status=lint_retcode == 0,
                lint_output=lint_output,
                posted=timezone.now(),
            ).save()

        if lint_retcode == 0:
            item.requires_upgrade = False
            item.save()

    print("--- New revision: %s" % item.id)
    return item
예제 #3
0
def process_upload(user_id, file, post, revision=1, previous_revision=0):
    """Upload a new revision of a map
       Returns the updated map revision or raises an InvalidMapException on error
    """
    parser = settings.OPENRA_VERSIONS[0]
    if post.get("parser", None) is not None:
        if post['parser'] not in settings.OPENRA_VERSIONS:
            raise InvalidMapException('Invalid parser.')
        parser = post['parser']

    user = User.objects.get(pk=user_id)

    # Check whether we can upload a new revision
    # and propagate the license info
    policy = {'cc': False, 'commercial': False, 'adaptations': ''}

    if previous_revision:
        query = Maps.objects.filter(id=previous_revision, user_id=user.id)
        if not query:
            raise InvalidMapException('Map is owned by another user.')

        previous_item = query[0]
        if previous_item.next_rev != 0:
            raise InvalidMapException('Map already has a later revision.')

        policy['cc'] = previous_item.policy_cc
        policy['commercial'] = previous_item.policy_commercial
        policy['adaptations'] = previous_item.policy_adaptations
    else:
        if post['policy_cc'] == 'cc_yes':
            policy['cc'] = True
            if post['commercial'] == "com_yes":
                policy['commercial'] = True
            if post['adaptations'] == "adapt_yes":
                policy['adaptations'] = "yes"
            elif post['adaptations'] == "adapt_no":
                policy['adaptations'] = "no"
            else:
                policy['adaptations'] = "yes and shared alike"

    # Create a temporary working directory and copy the oramap to it
    # Directory is automatically deleted when exiting the context manager
    with tempfile.TemporaryDirectory() as working_path:
        print('Temporary working directory: {}'.format(working_path))

        # Django's get_valid_filename makes the name safe
        upload_path = os.path.join(working_path, get_valid_filename(file.name))
        upload_ext = os.path.splitext(upload_path)[1].lower()
        with open(upload_path, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

        mime_retcode, mime_type = utility.detect_mimetype(upload_path)
        if mime_retcode != 0 or not (
            (mime_type == 'application/zip' and upload_ext == '.oramap') or
            (mime_type == 'text/plain' and upload_ext in ['.mpr', '.ini'])):
            raise InvalidMapException('Unsupported file type: ' + mime_type)

        # TODO: Support importing from cnc/d2k/ts.
        # Note that cnc maps contain *two* files which must both be present
        if mime_type == 'text/plain':
            import_path = os.path.splitext(upload_path)[0] + '.oramap'

            # Ignore command output and retcode, we know it worked if an oramap is saved
            utility.run_utility_command(parser,
                                        'ra', ['--import-ra-map', upload_path],
                                        cwd=working_path)

            if os.path.exists(import_path):
                upload_path = import_path
            else:
                misc.send_email_to_admin_OnMapFail(upload_path)
                raise InvalidMapException('Failed to import legacy map.')

        # TODO: Replace hardcoded RA with the parser mod when that rewrite happens
        return add_map_revision(upload_path, user, parser,
                                'ra', post['info'].strip(), policy,
                                timezone.now(), revision, previous_revision)
예제 #4
0
def add_map_revision(oramap_path, user,
                     parser, game_mod,
                     info, policy_options, posted_date,
                     revision=1, previous_revision_id=0):
    """ Parse and save a given oramap into the database.
        The input file is not modified, and must be cleaned up afterwards by the caller.
        Returns a Maps model or raises an InvalidMapException on error
    """
    print('Running --map-hash')
    hash_retcode, map_hash = utility.run_utility_command(parser, game_mod, [
        '--map-hash', oramap_path])

    if hash_retcode != 0 or not map_hash:
        raise InvalidMapException('Hash calculation failed.')

    # Require unique map hashes - allowing maps to have multiple RC entries
    # makes it difficult to return consistent data through the map API
    if Maps.objects.filter(map_hash=map_hash).exists():
        raise InvalidMapException("Map has already been uploaded.")

    # Parse metadata
    print('Parsing map.yaml metadata')
    metadata = utility.parse_map_metadata(oramap_path)
    if not metadata:
        misc.send_email_to_admin_OnMapFail(oramap_path)
        raise InvalidMapException('Unable to parse map metadata.')

    if int(metadata['mapformat']) < 10:
        raise InvalidMapException('Unable to import maps older than map format 10.')

    # Only attempt to parse rules for the default mods
    # This will be fixed properly once we move to mod-based parsing
    is_known_mod = metadata['game_mod'] in ['ra', 'cnc', 'd2k', 'ts']

    if is_known_mod:
        rules_retcode, custom_rules = utility.run_utility_command(parser, metadata['game_mod'], [
            '--map-rules', oramap_path])

        if rules_retcode != 0:
            misc.send_email_to_admin_OnMapFail(oramap_path)
            raise InvalidMapException('Failed to parse custom rules.')

        # TODO: Check against the game's Ruleset.DefinesUnsafeCustomRules code instead of line count
        advanced = len(custom_rules.split("\n")) > 8
        base64_rules = base64.b64encode(custom_rules.encode()).decode()
    else:
        base64_rules = ''
        advanced = False

    expect_metadata_keys = [
        'title', 'author', 'categories', 'players', 'game_mod',
        'width', 'height', 'bounds', 'mapformat', 'spawnpoints',
        'tileset', 'base64_players', 'lua'
    ]

    keyargs = {}
    for key in expect_metadata_keys:
        if key not in metadata:
            raise InvalidMapException('Map metadata missing required key: ' + key + '.')
        keyargs[key] = metadata[key]

    # Add record to Database
    item = Maps(
        map_hash=map_hash,
        revision=revision,
        pre_rev=previous_revision_id,
        next_rev=0,
        posted=posted_date,
        viewed=0,
        base64_rules=base64_rules,
        parser=parser,
        user=user,
        info=info,
        downloading=True,
        requires_upgrade=not is_known_mod,
        advanced_map=advanced,
        policy_cc=policy_options['cc'],
        policy_commercial=policy_options['commercial'],
        policy_adaptations=policy_options['adaptations'],
        description='', # Obsolete field
        map_type='', # Obsolete field
        shellmap=False, # Obsolete field
        legacy_map=False, # Obsolete field
        **keyargs
    )
    item.save()

    # Copy the updated map to its new data location
    item_path = os.path.join(settings.BASE_DIR, 'openra', 'data', 'maps', str(item.id))
    item_content_path = os.path.join(item_path, 'content')
    if not os.path.exists(item_content_path):
        os.makedirs(item_content_path)

    item_map_path = os.path.join(item_path, os.path.basename(oramap_path))
    shutil.copy(oramap_path, item_map_path)

    if previous_revision_id:
        previous_item = Maps.objects.get(id=previous_revision_id)
        previous_item.next_rev = item.id
        previous_item.save()

    # Extract the oramap contents
    # TODO: Why do we need this?
    with zipfile.ZipFile(item_map_path, mode='a') as oramap:
        try:
            oramap.extractall(item_content_path)
        except Exception:
            pass

    if is_known_mod:
        print('Running --check-yaml')
        lint_retcode, lint_output = utility.run_utility_command(parser, item.game_mod, [
            '--check-yaml',
            item_map_path
        ])

        if lint_output:
            Lints(
                item_type='maps',
                map_id=item.id,
                version_tag=parser,
                pass_status=lint_retcode == 0,
                lint_output=lint_output,
                posted=timezone.now(),
            ).save()

        if lint_retcode == 0:
            item.requires_upgrade = False
            item.save()

    print("--- New revision: %s" % item.id)
    return item
예제 #5
0
def process_upload(user_id, file, post, revision=1, previous_revision=0):
    """Upload a new revision of a map
       Returns the updated map revision or raises an InvalidMapException on error
    """
    parser = settings.OPENRA_VERSIONS[0]
    if post.get("parser", None) is not None:
        if post['parser'] not in settings.OPENRA_VERSIONS:
            raise InvalidMapException('Invalid parser.')
        parser = post['parser']

    user = User.objects.get(pk=user_id)

    # Check whether we can upload a new revision
    # and propagate the license info
    policy = {
        'cc': False,
        'commercial': False,
        'adaptations': ''
    }

    if previous_revision:
        query = Maps.objects.filter(id=previous_revision, user_id=user.id)
        if not query:
            raise InvalidMapException('Map is owned by another user.')

        previous_item = query[0]
        if previous_item.next_rev != 0:
            raise InvalidMapException('Map already has a later revision.')

        policy['cc'] = previous_item.policy_cc
        policy['commercial'] = previous_item.policy_commercial
        policy['adaptations'] = previous_item.policy_adaptations
    else:
        if post['policy_cc'] == 'cc_yes':
            policy['cc'] = True
            if post['commercial'] == "com_yes":
                policy['commercial'] = True
            if post['adaptations'] == "adapt_yes":
                policy['adaptations'] = "yes"
            elif post['adaptations'] == "adapt_no":
                policy['adaptations'] = "no"
            else:
                policy['adaptations'] = "yes and shared alike"

    # Create a temporary working directory and copy the oramap to it
    # Directory is automatically deleted when exiting the context manager
    with tempfile.TemporaryDirectory() as working_path:
        print('Temporary working directory: {}'.format(working_path))

        # Django's get_valid_filename makes the name safe
        upload_path = os.path.join(working_path, get_valid_filename(file.name))
        upload_ext = os.path.splitext(upload_path)[1].lower()
        with open(upload_path, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)

        mime_retcode, mime_type = utility.detect_mimetype(upload_path)
        if mime_retcode != 0 or not (
                (mime_type == 'application/zip' and upload_ext == '.oramap') or
                (mime_type == 'text/plain' and upload_ext in ['.mpr', '.ini'])):
            raise InvalidMapException('Unsupported file type: ' + mime_type)

        # TODO: Support importing from cnc/d2k/ts.
        # Note that cnc maps contain *two* files which must both be present
        if mime_type == 'text/plain':
            import_path = os.path.splitext(upload_path)[0] + '.oramap'

            # Ignore command output and retcode, we know it worked if an oramap is saved
            utility.run_utility_command(parser, 'ra', [
                '--import-ra-map', upload_path], cwd=working_path)

            if os.path.exists(import_path):
                upload_path = import_path
            else:
                misc.send_email_to_admin_OnMapFail(upload_path)
                raise InvalidMapException('Failed to import legacy map.')

        # TODO: Replace hardcoded RA with the parser mod when that rewrite happens
        return add_map_revision(upload_path, user, parser, 'ra',
                                post['info'].strip(), policy, timezone.now(),
                                revision, previous_revision)