diff --git a/BUILDING.md b/BUILDING.md index d768c83e..9db2980e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -4,15 +4,16 @@ CDDA Game Launcher is developed using Python. In order to run or build the launc ## Requirements -The full list of requirements is available in [requirements.txt](requirements.txt). Most of these requirements are Python packages that can be installed using [pip](https://en.wikipedia.org/wiki/Pip_%28package_manager%29). Unfortunately, there is no easy to use and easy to install build tools on Windows to compile and install a few of these requirements: +The full list of requirements is available in [requirements.txt](requirements.txt). Most of these requirements are Python packages that can be installed using [pip](https://en.wikipedia.org/wiki/Pip_%28package_manager%29). Unfortunately, some of these requirements need build tools which are not easy to use nor easy to install on Windows. Here are those special requirements: * PyQt5 * scandir * lxml +* pylzma I suggest you download and install already compiled binaries for these. At this time of writing, [the PyQt5 binaries](https://www.riverbankcomputing.com/software/pyqt/download5) are only available for Python 3.4 which means you should be using that version of Python as well. -Compiled binaries for lxml and scandir can be found on [Christoph Gohlke's Unofficial Windows Binaries](http://www.lfd.uci.edu/~gohlke/pythonlibs/). If you are using Python 3.5+, scandir should already be included. +Compiled binaries for lxml, scandir and pylzma can be found on [Christoph Gohlke's Unofficial Windows Binaries](http://www.lfd.uci.edu/~gohlke/pythonlibs/). If you are using Python 3.5+, scandir should already be included. ## Running the launcher @@ -32,7 +33,7 @@ The resulting launcher executable should be in the `dist` directory. * By default, Python 3.4 is installed in `C:\Python34`. To setup your PATH, type `set PATH=%PATH%;C:\Python34;C:\Python34\Scripts` in your command line window and press `↵ Enter`. 3. Install most requirements by typing the following `pip` command in your command line window: `pip install SQLAlchemy alembic PyInstaller html5lib cssselect arrow rarfile` and press `↵ Enter`. 4. Download and install the PyQt5 binaries from [Riverbank Computing's website](https://www.riverbankcomputing.com/software/pyqt/download5). Make sure to download the same platform version (either 32-bit or 64-bit). It should match the same platform version you got in Step 1. -5. Download and install the scandir and lxml packages from [Christoph Gohlke's Unofficial Windows Binaries](http://www.lfd.uci.edu/~gohlke/pythonlibs/). It should match the same Python version and platform version you got in Step 1. `cp34` means CPython 3.4, `win32` means 32-bit and `win_amd64` means 64-bit in Christoph Gohlke's packages naming convention. To install `.whl` packages from Christoph Gohlke's Unofficial Windows Binaries page, you can using pip. In your command line window, type: `pip install [path to .whl]` and press `↵ Enter`. +5. Download and install the scandir, the lxml and the pylzma packages from [Christoph Gohlke's Unofficial Windows Binaries](http://www.lfd.uci.edu/~gohlke/pythonlibs/). It should match the same Python version and platform version you got in Step 1. `cp34` means CPython 3.4, `win32` means 32-bit and `win_amd64` means 64-bit in Christoph Gohlke's packages naming convention. To install `.whl` packages from Christoph Gohlke's Unofficial Windows Binaries page, you can using pip. In your command line window, type: `pip install [path to .whl]` and press `↵ Enter`. 6. Download the CDDA Game Launcher source code. If you have git installed, you can type the following command in your command line window: `git clone https://github.com/remyroy/CDDA-Game-Launcher.git`. You can also download the source code from [https://github.com/remyroy/CDDA-Game-Launcher/archive/master.zip](https://github.com/remyroy/CDDA-Game-Launcher/archive/master.zip). Make sure to extract the zip file somewhere before trying to run the code. 7. In your command line window, change directory to the source code directory. Type `cd [path to source code]` and press `↵ Enter`. 8. See if you can run the launcher by typing the following command in your command line window: `python cddagl\launcher.py` and press `↵ Enter`. If you have everything setuped correctly, you should see the launcher running. diff --git a/cddagl/ui.py b/cddagl/ui.py index a30d8b72..0508bd98 100644 --- a/cddagl/ui.py +++ b/cddagl/ui.py @@ -27,6 +27,7 @@ from urllib.parse import urljoin, urlencode import rarfile +from py7zlib import Archive7z, NoPasswordGivenError, FormatError from distutils.version import LooseVersion @@ -3682,7 +3683,14 @@ def size_query_finished(self): content_length = int(self.http_reply.rawHeader(b'Content-Length')) self.current_repo_info['size'] = content_length - self.size_le.setText(sizeof_fmt(content_length)) + + selection_model = self.repository_lv.selectionModel() + if selection_model is not None and selection_model.hasSelection(): + selected = selection_model.currentIndex() + selected_info = self.repo_soundpacks[selected.row()] + + if selected_info is self.current_repo_info: + self.size_le.setText(sizeof_fmt(content_length)) else: selection_model = self.repository_lv.selectionModel() if selection_model is not None and selection_model.hasSelection(): @@ -3852,6 +3860,10 @@ def __init__(self): self.downloading_new_mod = False self.extracting_new_mod = False + self.install_type = None + self.extracting_file = None + self.extracting_zipfile = None + self.close_after_install = False self.game_dir = None @@ -4262,32 +4274,51 @@ def install_new(self): status_bar.showMessage('Testing downloaded file ' 'archive') - if self.downloaded_file.lower().endswith('.zip'): - archive_class = zipfile.ZipFile - archive_exception = zipfile.BadZipFile - test_method = 'testzip' - elif self.downloaded_file.lower().endswith('.rar'): - archive_class = rarfile.RarFile - archive_exception = rarfile.BadRarFile - test_method = 'testrar' - - try: - with archive_class(self.downloaded_file) as z: - test = getattr(z, test_method) - if test() is not None: - status_bar.clearMessage() - status_bar.showMessage( - 'Downloaded archive is invalid') - - self.finish_install_new_mod() - return - except archive_exception: - status_bar.clearMessage() - status_bar.showMessage('Selected file is a bad ' - 'archive file') + if self.downloaded_file.lower().endswith('.7z'): + try: + with open(self.downloaded_file, 'rb') as f: + archive = Archive7z(f) + except FormatError: + status_bar.clearMessage() + status_bar.showMessage('Selected file is a bad ' + 'archive file') - self.finish_install_new_mod() - return + self.finish_install_new_mod() + return + except NoPasswordGivenError: + status_bar.clearMessage() + status_bar.showMessage('Selected file is a ' + 'password protected archive file') + + self.finish_install_new_mod() + return + else: + if self.downloaded_file.lower().endswith('.zip'): + archive_class = zipfile.ZipFile + archive_exception = zipfile.BadZipFile + test_method = 'testzip' + elif self.downloaded_file.lower().endswith('.rar'): + archive_class = rarfile.RarFile + archive_exception = rarfile.BadRarFile + test_method = 'testrar' + + try: + with archive_class(self.downloaded_file) as z: + test = getattr(z, test_method) + if test() is not None: + status_bar.clearMessage() + status_bar.showMessage( + 'Downloaded archive is invalid') + + self.finish_install_new_mod() + return + except archive_exception: + status_bar.clearMessage() + status_bar.showMessage('Selected file is a bad ' + 'archive file') + + self.finish_install_new_mod() + return status_bar.clearMessage() self.extract_new_mod() @@ -4309,10 +4340,12 @@ def install_new(self): self.extracting_new_mod = False - self.extracting_zipfile.close() + if self.extracting_zipfile is not None: + self.extracting_zipfile.close() - download_dir = os.path.dirname(self.downloaded_file) - retry_rmtree(download_dir) + if self.install_type == 'direct_download': + download_dir = os.path.dirname(self.downloaded_file) + retry_rmtree(download_dir) if os.path.isdir(self.extract_dir): retry_rmtree(self.extract_dir) @@ -4458,13 +4491,21 @@ def download_dl_progress(self, bytes_read, total_bytes): def extract_new_mod(self): self.extracting_new_mod = True - if self.downloaded_file.lower().endswith('.zip'): - archive_class = zipfile.ZipFile - elif self.downloaded_file.lower().endswith('.rar'): - archive_class = rarfile.RarFile + if self.downloaded_file.lower().endswith('.7z'): + self.extracting_zipfile = open(self.downloaded_file, 'rb') + self.extracting_archive = Archive7z(self.extracting_zipfile) - z = archive_class(self.downloaded_file) - self.extracting_zipfile = z + self.extracting_infolist = self.extracting_archive.getmembers() + else: + if self.downloaded_file.lower().endswith('.zip'): + archive_class = zipfile.ZipFile + elif self.downloaded_file.lower().endswith('.rar'): + archive_class = rarfile.RarFile + + z = archive_class(self.downloaded_file) + self.extracting_zipfile = z + + self.extracting_infolist = z.infolist() self.extract_dir = os.path.join(self.game_dir, 'newmod') while os.path.exists(self.extract_dir): @@ -4472,7 +4513,6 @@ def extract_new_mod(self): 'newmod-{0}'.format('%08x' % random.randrange(16**8))) os.makedirs(self.extract_dir) - self.extracting_infolist = z.infolist() self.extracting_index = 0 main_window = self.get_main_window() @@ -4510,6 +4550,10 @@ def timeout(): self.extracting_new_mod = False self.extracting_zipfile.close() + self.extracting_zipfile = None + + if self.downloaded_file.lower().endswith('.7z'): + self.extracting_archive = None if self.install_type == 'direct_download': download_dir = os.path.dirname(self.downloaded_file) @@ -4520,11 +4564,21 @@ def timeout(): else: extracting_element = self.extracting_infolist[ self.extracting_index] + self.extracting_label.setText('Extracting {0}'.format( extracting_element.filename)) - - self.extracting_zipfile.extract(extracting_element, - self.extract_dir) + + if self.downloaded_file.lower().endswith('.7z'): + destination = os.path.join(self.extract_dir, + *extracting_element.filename.split('/')) + dest_dir = os.path.dirname(destination) + if not os.path.isdir(dest_dir): + os.makedirs(dest_dir) + with open(destination, 'wb') as f: + f.write(extracting_element.read()) + else: + self.extracting_zipfile.extract(extracting_element, + self.extract_dir) self.extracting_index += 1 @@ -4767,7 +4821,14 @@ def size_query_finished(self): content_length = int(self.http_reply.rawHeader(b'Content-Length')) self.current_repo_info['size'] = content_length - self.size_le.setText(sizeof_fmt(content_length)) + + selection_model = self.repository_lv.selectionModel() + if selection_model is not None and selection_model.hasSelection(): + selected = selection_model.currentIndex() + selected_info = self.repo_soundpacks[selected.row()] + + if selected_info is self.current_repo_info: + self.size_le.setText(sizeof_fmt(content_length)) else: selection_model = self.repository_lv.selectionModel() if selection_model is not None and selection_model.hasSelection(): diff --git a/data/mods.json b/data/mods.json index f3acfdfa..f078feb6 100644 --- a/data/mods.json +++ b/data/mods.json @@ -56,6 +56,17 @@ "expected_filename": "Guns Equipment Mods Expansion Mode v1.5 experimental.rar", "homepage": "http://smf.cataclysmdda.com/index.php?topic=11823.0" }, + { + "type": "browser_download", + "ident": "jury_rigged_robots", + "name": "Jury-Rigged Robots", + "description": "Options for salvaging, jury rigging, and reprogramming broken robots.", + "author": "Sunshine", + "size": 10625, + "url": "https://mega.co.nz/#!rQkklSKQ!l_2XXv7YtnK8On668dqHH-MKA5THufUulGoXr-YRvjA", + "expected_filename": "Jury_Rigged_Robots.7z", + "homepage": "http://smf.cataclysmdda.com/index.php?topic=10667.0" + }, { "type": "browser_download", "ident": "Remove_Nether", diff --git a/requirements.txt b/requirements.txt index 4948ffc3..62980548 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ lxml cssselect arrow scandir -rarfile \ No newline at end of file +rarfile +pylzma \ No newline at end of file