Exemplo n.º 1
0
    def select_success(self, path):
        Logger.info('Modlist: User selected the directory: {}'.format(path))

        if not os.path.isdir(path):
            return 'Not a directory or unreadable:\n{}'.format(path)

        if not is_dir_writable(path):
            return 'Directory {} is not writable'.format(path)

        # Prevent idiot-loops (seriously, people doing this are idiots!)
        already_used_by = path_already_used_for_mod(path, self.owner.all_existing_mods)
        settings = kivy.app.App.get_running_app().settings
        if not path_can_be_a_mod(path, settings.get('launcher_moddir')) or \
            (already_used_by and already_used_by != self.mod.foldername):
            message = textwrap.dedent('''
                Really???

                You're now selecting the location where {} is ALREADY installed and you've chosen:
                {}

                You realize that selecting that directory will cause EVERYTHING
                inside that is not part of that mod to be DELETED?

                Think twice next time!
            ''').format(self.mod.foldername, path)
            return message

        if casefold(path) == casefold(self.mod.get_full_path()):
            Logger.info('select_success: Selected directory is the current one. Ignoring...')
            return

        self.set_new_path(path)
Exemplo n.º 2
0
def path_can_be_a_mod(path, mods_directory):
    """Check if a given path could be used by a mod.
    path - patch to be checked.
    mods_directory - the directory where mods are stored by the launcher.
    """

    launcher_moddir = os.path.realpath(mods_directory)
    launcher_moddir_casefold = unicode_helpers.casefold(launcher_moddir)
    path_casefold = unicode_helpers.casefold(os.path.realpath(path))

    # Loop to parent (infinite loop)
    if launcher_moddir_casefold == path_casefold or \
       launcher_moddir_casefold.startswith(path_casefold + os.path.sep):
        Logger.info("path_can_be_a_mod: Rejecting {}. Loop to parent.".format(
            path_casefold))
        return False

    directory_name = os.path.basename(path_casefold)
    if not directory_name:  # Path ends with a '\' or '/'
        directory_name = os.path.dirname(path_casefold)

    # All names must be changed to lowercase
    bad_directories = [
        'steam',
        'steamapps',
        'workshop',
        'content',
        '107410',
        'common',
        'arma 3',
        'desktop',
    ]
    if directory_name in bad_directories:
        Logger.info(
            "path_can_be_a_mod: Rejecting {}. Blacklisted directory.".format(
                path_casefold))
        return False

    if len(path_casefold) == 3 and path_casefold.endswith(':\\'):
        Logger.info("path_can_be_a_mod: Rejecting {}. Root directory.".format(
            path_casefold))
        return False

    if path_casefold == unicode_helpers.casefold(
            paths.get_user_home_directory()):
        Logger.info("path_can_be_a_mod: Rejecting {}. Home directory.".format(
            path_casefold))
        return False

    return True
Exemplo n.º 3
0
def path_already_used_for_mod(path, all_existing_mods):
    """Check if a given path is already used by a mod and return its name.
    Return None otherwise.
    """

    path = unicode_helpers.casefold(os.path.realpath(path))

    for mod in all_existing_mods:
        mod_full_path = unicode_helpers.casefold(mod.get_full_path())
        mod_real_full_path = unicode_helpers.casefold(mod.get_real_full_path())
        if path == mod_full_path or \
           path == mod_real_full_path or \
           path.startswith(mod_full_path + os.path.sep) or \
           path.startswith(mod_real_full_path + os.path.sep):
            return mod.foldername

    return None
Exemplo n.º 4
0
def keep_meaningful_data(name):
    """Return the name after it has been changed to lowercase and stripped of
    all letters that are not latin characters or digits or '@'.
    This is done for a pseudo-fuzzy comparison where "@Kunduz, Afghanistan" and
    "@Kunduz Afghanistan" will match.
    """

    no_case = casefold(name)
    allowed_chars = string.letters + string.digits + '@'
    filtered_name = re.sub('[^{}]'.format(re.escape(allowed_chars)), '', no_case)

    return filtered_name
def check_mod_directories(files_list, base_directory, check_subdir='',
                          on_superfluous='warn', checksums=None,
                          case_sensitive=False):
    """Check if all files and directories present in the mod directories belong
    to the torrent file. If not, remove those if on_superfluous=='remove' or return False
    if on_superfluous=='warn'.

    base_directory is the directory to which mods are downloaded.
    For example: if the mod directory is C:\Arma\@MyMod, base_directory should be C:\Arma.

    check_subdir tells the function to only check if files contained in the
    subdirectory are properly created and existing.

    on_superfluous is the action to perform when superfluous files are found:
        'warn': return False
        'remove': remove the file or directory
        'ignore': do nothing

    To prevent accidental file removal, this function will only remove files
    that are at least one directory deep in the file structure!
    As all multi-file torrents *require* one root directory that holds those
    files, this should not be an issue.
    This function will skip files or directories that match the 'WHITELIST_NAME' variable.

    If the dictionary checksums is not None, the files' checksums will be checked.

    Returns if the directory has been cleaned sucessfully or if all files present
    are supposed to be there. Do not ignore this value!
    If unsuccessful at removing files, the mod should NOT be considered ready to play."""

    if on_superfluous not in ('warn', 'remove', 'ignore'):
        raise Exception('Unknown action: {}'.format(on_superfluous))

    top_dirs, dirs, file_paths, checksums = parse_files_list(files_list, checksums, check_subdir)

    # Remove whitelisted items from the lists
    dirs = filter_out_whitelisted(dirs)
    file_paths = filter_out_whitelisted(file_paths)

    # If not case sensitive, rewrite data so it may be used in a case insensitive
    # comparisons
    if not case_sensitive:
        file_paths = set(casefold(filename) for filename in file_paths)
        dirs = set(casefold(directory) for directory in dirs)
        top_dirs = set(casefold(top_dir) for top_dir in top_dirs)

        if checksums:
            checksums = {casefold(key): value for (key, value) in checksums.iteritems()}

        # Set conditional casefold function
        ccf = lambda x: casefold(x)
    else:
        ccf = lambda x: x

    base_directory = os.path.realpath(base_directory)
    Logger.debug('check_mod_directories: Verifying base_directory: {}'.format(base_directory))
    success = True

    try:
        for directory_nocase in top_dirs:
            with ignore_exceptions(KeyError):
                dirs.remove(directory_nocase)

            if directory_nocase in WHITELIST_NAME:
                continue

            full_base_path = os.path.join(base_directory, directory_nocase)
            _unlink_safety_assert(base_directory, full_base_path, action='enter')
            # FIXME: on OSError, this might indicate a broken junction or symlink on windows
            # Must act accordingly then.
            for (dirpath, dirnames, filenames) in walker.walk(full_base_path, topdown=True, onerror=_raiser, followlinks=True):
                relative_path = os.path.relpath(dirpath, base_directory)
                Logger.debug('check_mod_directories: In directory: {}'.format(relative_path))

                # First check files in this directory
                for file_name in filenames:
                    relative_file_name_nocase = ccf(os.path.join(relative_path, file_name))

                    if file_name in WHITELIST_NAME:
                        Logger.debug('check_mod_directories: File {} in WHITELIST_NAME, skipping...'.format(file_name))

                        with ignore_exceptions(KeyError):
                            file_paths.remove(relative_file_name_nocase)
                        continue

                    full_file_path = os.path.join(dirpath, file_name)

                    Logger.debug('check_mod_directories: Checking file: {}'.format(relative_file_name_nocase))
                    if relative_file_name_nocase in file_paths:
                        file_paths.remove(relative_file_name_nocase)
                        Logger.debug('check_mod_directories: {} present in torrent metadata'.format(relative_file_name_nocase))

                        if checksums and sha1(full_file_path) != checksums[relative_file_name_nocase]:
                            Logger.debug('check_mod_directories: File {} exists but its hash differs from expected.'.format(relative_file_name_nocase))
                            Logger.debug('check_mod_directories: Expected: {}, computed: {}'.format(checksums[relative_file_name_nocase].encode('hex'), sha1(full_file_path).encode('hex')))
                            return False

                        continue  # File present in the torrent, nothing to see here

                    if on_superfluous == 'remove':
                        Logger.debug('check_mod_directories: Removing file: {}'.format(full_file_path))
                        _safer_unlink(full_base_path, full_file_path)

                    elif on_superfluous == 'warn':
                        Logger.debug('check_mod_directories: Superfluous file: {}'.format(full_file_path))
                        return False

                    elif on_superfluous == 'ignore':
                        pass

                # Now check directories
                # Iterate over a copy because we'll be deleting items from the original
                for dir_name in dirnames[:]:
                    relative_dir_path = ccf(os.path.join(relative_path, dir_name))

                    if dir_name in WHITELIST_NAME:
                        dirnames.remove(dir_name)

                        with ignore_exceptions(KeyError):
                            dirs.remove(relative_dir_path)

                        continue

                    Logger.debug('check_mod_directories: Checking dir: {}'.format(relative_dir_path))
                    if relative_dir_path in dirs:
                        dirs.remove(relative_dir_path)
                        continue  # Directory present in the torrent, nothing to see here

                    full_directory_path = os.path.join(dirpath, dir_name)

                    if on_superfluous == 'remove':
                        Logger.debug('check_mod_directories: Removing directory: {}'.format(full_directory_path))
                        dirnames.remove(dir_name)

                        _safer_rmtree(full_base_path, full_directory_path)

                    elif on_superfluous == 'warn':
                        Logger.debug('check_mod_directories: Superfluous directory: {}'.format(full_directory_path))
                        return False

                    elif on_superfluous == 'ignore':
                        pass

        # Check for files missing on disk
        # file_paths contains all missing files OR files outside of any directory.
        # Such files will not exist with regular torrents but may happen if using
        # check_subdir != ''.
        # We just check if they exist. No deleting!
        for file_entry_nocase in file_paths:
            full_path = os.path.join(base_directory, file_entry_nocase)

            if not os.path.isfile(full_path):
                Logger.debug('check_mod_directories: File paths missing on disk, setting retval to False')
                Logger.debug('check_mod_directories: ' + full_path)
                success = False
                break

            if checksums and sha1(full_path) != checksums[file_entry_nocase]:
                Logger.debug('check_mod_directories: File {} exists but its hash differs from expected.'.format(file_entry_nocase))
                Logger.debug('check_mod_directories: Expected: {}, computed: {}'.format(checksums[file_entry_nocase].encode('hex'), sha1(full_path).encode('hex')))
                success = False
                break

        if dirs:
            Logger.debug('check_mod_directories: Dirs missing on disk, setting retval to False')
            Logger.debug('check_mod_directories: ' + ', '.join(dirs))
            success = False

    except OSError:
        success = False

    return success