Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle file tasks case-insensitively #690

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion legendary/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,8 @@ def verify_game(self, args, print_command=True, repair_mode=False, repair_online

logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
repair_file = []
for result, path, result_hash, bytes_read in validate_files(igame.install_path, file_list):
for result, path, result_hash, bytes_read in validate_files(igame.install_path, file_list,
case_insensitive=igame.platform.startswith('Win')):
processed += bytes_read
percentage = (processed / total_size) * 100.0
num += 1
Expand Down
10 changes: 7 additions & 3 deletions legendary/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,7 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =

dlm = DLManager(install_path, base_url, resume_file=resume_file, status_q=status_q,
max_shared_memory=max_shm * 1024 * 1024, max_workers=max_workers,
dl_timeout=dl_timeout, bind_ip=bind_ip)
dl_timeout=dl_timeout, bind_ip=bind_ip, case_insensitive=platform.startswith('Win'))
anlres = dlm.run_analysis(manifest=new_manifest, old_manifest=old_manifest,
patch=not disable_patching, resume=not force,
file_prefix_filter=file_prefix_filter,
Expand Down Expand Up @@ -1697,7 +1697,8 @@ def uninstall_game(self, installed_game: InstalledGame, delete_files=True, delet
fm.filename for fm in manifest.file_manifest_list.elements if
not fm.install_tags or any(t in installed_game.install_tags for t in fm.install_tags)
]
if not delete_filelist(installed_game.install_path, filelist, delete_root_directory):
if not delete_filelist(installed_game.install_path, filelist, delete_root_directory,
case_insensitive=installed_game.platform.startswith('Win')):
self.log.error(f'Deleting "{installed_game.install_path}" failed, please remove manually.')
except Exception as e:
self.log.error(f'Deleting failed with {e!r}, please remove {installed_game.install_path} manually.')
Expand All @@ -1717,7 +1718,10 @@ def uninstall_tag(self, installed_game: InstalledGame):
and os.path.exists(os.path.join(installed_game.install_path, fm.filename))
]

if not delete_filelist(installed_game.install_path, filelist):
if not delete_filelist(
installed_game.install_path, filelist,
case_insensitive=installed_game.platform.startswith('Win')
):
self.log.warning(f'Deleting some deselected files failed, please check/remove manually.')

def prereq_installed(self, app_name):
Expand Down
6 changes: 4 additions & 2 deletions legendary/downloader/mp/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
class DLManager(Process):
def __init__(self, download_dir, base_url, cache_dir=None, status_q=None,
max_workers=0, update_interval=1.0, dl_timeout=10, resume_file=None,
max_shared_memory=1024 * 1024 * 1024, bind_ip=None):
max_shared_memory=1024 * 1024 * 1024, bind_ip=None, case_insensitive=True):
super().__init__(name='DLManager')
self.log = logging.getLogger('DLM')
self.proc_debug = False
Expand All @@ -42,6 +42,7 @@ def __init__(self, download_dir, base_url, cache_dir=None, status_q=None,
self.max_workers = max_workers or min(cpu_count() * 2, 16)
self.dl_timeout = dl_timeout
self.bind_ips = [] if not bind_ip else bind_ip.split(',')
self.case_insensitive = case_insensitive

# Analysis stuff
self.analysis = None
Expand Down Expand Up @@ -672,7 +673,8 @@ def run_real(self):

self.log.info('Starting file writing worker...')
writer_p = FileWorker(self.writer_queue, self.writer_result_q, self.dl_dir,
self.shared_memory.name, self.cache_dir, self.logging_queue)
self.shared_memory.name, self.cache_dir, self.logging_queue,
self.case_insensitive)
self.children.append(writer_p)
writer_p.start()

Expand Down
19 changes: 13 additions & 6 deletions legendary/downloader/mp/workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import requests
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK

from legendary.lfs.wine_helpers import case_insensitive_file_search
from legendary.models.chunk import Chunk
from legendary.models.downloading import (
DownloaderTask, DownloaderTaskResult,
Expand Down Expand Up @@ -147,7 +148,8 @@ def run(self):


class FileWorker(Process):
def __init__(self, queue, out_queue, base_path, shm, cache_path=None, logging_queue=None):
def __init__(self, queue, out_queue, base_path, shm, cache_path=None, logging_queue=None,
case_insensitive: bool = True):
super().__init__(name='FileWorker')
self.q = queue
self.o_q = out_queue
Expand All @@ -156,6 +158,7 @@ def __init__(self, queue, out_queue, base_path, shm, cache_path=None, logging_qu
self.shm = SharedMemory(name=shm)
self.log_level = logging.getLogger().level
self.logging_queue = logging_queue
self.case_insensitive = case_insensitive

def run(self):
# we have to fix up the logger before we can start
Expand Down Expand Up @@ -187,11 +190,14 @@ def run(self):
break

# make directories if required
path = os.path.split(j.filename)[0]
if not os.path.exists(os.path.join(self.base_path, path)):
os.makedirs(os.path.join(self.base_path, path))
path, filename = os.path.split(j.filename)
file_dir = os.path.join(self.base_path, path)
if self.case_insensitive:
file_dir = case_insensitive_file_search(file_dir)
if not os.path.exists(file_dir):
os.makedirs(file_dir)

full_path = os.path.join(self.base_path, j.filename)
full_path = os.path.join(file_dir, filename)

if j.flags & TaskFlags.CREATE_EMPTY_FILE: # just create an empty file
open(full_path, 'a').close()
Expand Down Expand Up @@ -230,7 +236,8 @@ def run(self):
continue

try:
os.rename(os.path.join(self.base_path, j.old_file), full_path)
old_path = case_insensitive_file_search(os.path.join(self.base_path, j.old_file))
os.rename(old_path, full_path)
except OSError as e:
logger.error(f'Renaming file failed: {e!r}')
self.o_q.put(WriterTaskResult(success=False, **j.__dict__))
Expand Down
18 changes: 14 additions & 4 deletions legendary/lfs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from filelock import FileLock

from legendary.lfs.wine_helpers import case_insensitive_file_search
from legendary.models.game import VerifyResult

logger = logging.getLogger('LFS Utils')
Expand All @@ -34,7 +35,7 @@ def delete_folder(path: str, recursive=True) -> bool:

def delete_filelist(path: str, filenames: List[str],
delete_root_directory: bool = False,
silent: bool = False) -> bool:
silent: bool = False, case_insensitive: bool = True) -> bool:
dirs = set()
no_error = True

Expand All @@ -45,7 +46,10 @@ def delete_filelist(path: str, filenames: List[str],
dirs.add(_dir)

try:
os.remove(os.path.join(path, _dir, _fn))
full_path = os.path.join(path, _dir, _fn)
if case_insensitive:
full_path = case_insensitive_file_search(full_path)
os.remove(full_path)
except Exception as e:
if not silent:
logger.error(f'Failed deleting file {filename} with {e!r}')
Expand All @@ -61,7 +65,10 @@ def delete_filelist(path: str, filenames: List[str],
# remove all directories
for _dir in sorted(dirs, key=len, reverse=True):
try:
os.rmdir(os.path.join(path, _dir))
dir_path = os.path.join(path, _dir)
if case_insensitive:
dir_path = case_insensitive_file_search(dir_path)
os.rmdir(dir_path)
except FileNotFoundError:
# directory has already been deleted, ignore that
continue
Expand All @@ -81,14 +88,15 @@ def delete_filelist(path: str, filenames: List[str],


def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1',
large_file_threshold=1024 * 1024 * 512) -> Iterator[tuple]:
large_file_threshold=1024 * 1024 * 512, case_insensitive: bool = True) -> Iterator[tuple]:
"""
Validates the files in filelist in path against the provided hashes

:param base_path: path in which the files are located
:param filelist: list of tuples in format (path, hash [hex])
:param hash_type: (optional) type of hash, default is sha1
:param large_file_threshold: (optional) threshold for large files, default is 512 MiB
:param case_insensitive: (optional) whether to search for files case insensitively
:return: yields tuples in format (VerifyResult, path, hash [hex], bytes read)
"""

Expand All @@ -100,6 +108,8 @@ def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1',

for file_path, file_hash in filelist:
full_path = os.path.join(base_path, file_path)
if case_insensitive:
full_path = case_insensitive_file_search(full_path)
# logger.debug(f'Checking "{file_path}"...')

if not os.path.exists(full_path):
Expand Down
Loading