Compare commits

..

6 Commits

Author SHA1 Message Date
Petr Mrázek
7b85d7c0de GH-1876 Fork and update quazip
* It is added as a new submodule: https://github.com/MultiMC/quazip/tree/multimc-1
* Its build system has been entirely replaced to remove the existing issues with it
* It now has working unit tests
* No more patches needed
* It has a static linking exception in its license now, but we use it shared anyway
2017-10-26 01:29:50 +02:00
Petr Mrázek
1eb6e77f75 GH-2026 fix bug with loading minecraft versions from Mojang 2017-10-26 00:37:12 +02:00
Petr Mrázek
f49f8b875a GH-2026 fix test failure on macOS 2017-10-26 00:26:19 +02:00
Petr Mrázek
23d2a99619 GH-2026 blacklist new Minecraft snapshots and releases some more 2017-10-25 23:42:54 +02:00
Petr Mrázek
170bd677fd GH-2026 blacklist new Minecraft snapshots and releases 2017-10-25 22:48:58 +02:00
Petr Mrázek
84e23e2e7a NOISSUE fix build issues 2017-10-25 21:31:51 +02:00
859 changed files with 65105 additions and 66859 deletions

View File

@@ -1,4 +1,4 @@
UseTab: false UseTab: true
IndentWidth: 4 IndentWidth: 4
TabWidth: 4 TabWidth: 4
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4

View File

@@ -9,7 +9,6 @@ Before submitting this issue, please make sure you have:
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored. - We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
4. Given the issue a descriptive title. 4. Given the issue a descriptive title.
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!". - A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
5. Place all information below the ---- of lines. 5. Place all information below the ---- of lines.
- It makes the issue look pretty - It makes the issue look pretty

2
.gitignore vendored
View File

@@ -15,8 +15,6 @@ CMakeLists.txt.user
CMakeLists.txt.user.* CMakeLists.txt.user.*
/.project /.project
/.settings /.settings
/.idea
cmake-build-*/
# Build dirs # Build dirs
build build

View File

@@ -2,28 +2,29 @@
language: cpp language: cpp
cache: apt cache: apt
# Build matrix set up
compiler:
- gcc
# - clang
os:
- linux
# - osx
env:
- QT_VERSION=5.4.2
- QT_VERSION=5.5.1
# - QT_VERSION=5.6.2
matrix: matrix:
include: exclude:
- os: linux # only use clang on OS X
dist: precise - os: osx
sudo: required
compiler: gcc compiler: gcc
env: TRAVIS_DIST=precise QT_VERSION=5.4.2 # only use the qt available from homebrew
- os: linux - os: osx
dist: precise env: QT_VERSION=5.4.2
sudo: required - os: osx
compiler: gcc env: QT_VERSION=5.5.1
env: TRAVIS_DIST=precise QT_VERSION=5.6.2 # - os: osx
- os: linux # env: QT_VERSION=5.6
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
- os: linux
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
# Install dependencies # Install dependencies
install: install:

View File

@@ -32,11 +32,11 @@ git submodule update
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution. Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
## Build dependencies ## Build dependencies
* A C++ compiler capable of building C++11 code. * Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
* Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version. * Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution.
* cmake 3.1 or newer * cmake 3.1 or newer
* zlib (for example, `zlib1g-dev`) * zlib (for example, `zlib1g-dev`)
* Java JDK 8 (for example, `openjdk-8-jdk`) * java (for example, `openjdk-8-jdk`)
* GL headers (for example, `libgl1-mesa-dev`) * GL headers (for example, `libgl1-mesa-dev`)
### Building from command line ### Building from command line
@@ -64,7 +64,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
1. Run the Qt installer. 1. Run the Qt installer.
2. Choose a place to install Qt. 2. Choose a place to install Qt.
3. Choose the components you want to install. 3. Choose the components you want to install.
- You need Qt 5.6.x 64-bit ticked. - You need Qt 5.4.1/gcc 64-bit ticked.
- You need Tools/Qt Creator ticked. - You need Tools/Qt Creator ticked.
- Other components are selected by default, you can untick them if you don't need them. - Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements. 4. Accept the license agreements.
@@ -77,7 +77,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt. 3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
4. Read the instructions that just popped up about a build location and choose one. 4. Read the instructions that just popped up about a build location and choose one.
5. You should see "Run CMake" in the window. 5. You should see "Run CMake" in the window.
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)". - Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)".
- Hit the "Run CMake" button. - Hit the "Run CMake" button.
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window. - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully. - Hit "Finish" if CMake ran successfully.
@@ -91,13 +91,13 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only. Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
## Dependencies ## Dependencies
* [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows * [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light * [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download. - Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though. - We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine * [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer) * [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
Put it somewhere on the `PATH`, so that it is accessible from the console. Put it somewhere on the `PATH`, so that it is accessible from the console.
@@ -107,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
1. Run the Qt installer 1. Run the Qt installer
2. Choose a place to install Qt (C:\Qt is the default), 2. Choose a place to install Qt (C:\Qt is the default),
3. Choose the components you want to install 3. Choose the components you want to install
- You need Qt 5.6 (32 bit) ticked, - You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked,
- You need Tools/Qt Creator ticked, - You need Tools/Qt Creator ticked,
- Other components are selected by default, you can untick them if you don't need them. - Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements, 4. Accept the license agreements,
@@ -132,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it, 5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- Otherwise you can skip this step. - Otherwise you can skip this step.
6. You should see "Run CMake" in the window, 6. You should see "Run CMake" in the window,
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)", - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)",
- Hit the "Run CMake" button, - Hit the "Run CMake" button,
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window. - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully. - Hit "Finish" if CMake ran successfully.

View File

@@ -13,7 +13,7 @@ endif()
project(MultiMC) project(MultiMC)
enable_testing() enable_testing()
##################################### Set CMake options ##################################### ######## Set CMake options ########
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -32,61 +32,22 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS " -Wall -D_GLIBCXX_USE_CXX11_ABI=0 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif() endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
##################################### Set Application options #####################################
######## Set URLs ########
set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
set(MultiMC_VERSION_HOTFIX 6)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
# Build platform.
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
# Channel list URL
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
# Notification URL
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
# paste.ee API key
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
# Google analytics ID
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
#### Custom target to just print the version.
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
################################ 3rd Party Libs ################################ ################################ 3rd Party Libs ################################
# Find the required Qt parts # Find the required Qt parts
find_package(Qt5Core REQUIRED) find_package(Qt5Core)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets)
find_package(Qt5Concurrent REQUIRED) find_package(Qt5Concurrent)
find_package(Qt5Network REQUIRED) find_package(Qt5Network)
find_package(Qt5Test REQUIRED) find_package(Qt5Test)
find_package(Qt5Xml REQUIRED) find_package(Qt5Xml)
# The Qt5 cmake files don't provide its install paths, so ask qmake. # The Qt5 cmake files don't provide its install paths, so ask qmake.
include(QMakeQuery) include(QMakeQuery)
@@ -101,133 +62,6 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON) SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif() endif()
####################################### Install layout #######################################
# How to install the build results
set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
if(MultiMC_LAYOUT STREQUAL "auto")
if(UNIX AND APPLE)
set(MultiMC_LAYOUT_REAL "mac-bundle")
elseif(UNIX)
set(MultiMC_LAYOUT_REAL "lin-nodeps")
elseif(WIN32)
set(MultiMC_LAYOUT_REAL "win-bundle")
else()
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
endif()
else()
set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
endif()
if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
set(BUNDLE_DEST_DIR ".")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
# Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
# Add the icon
install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "bin/jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "bin/jars")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
set(BINARY_DEST_DIR ".")
set(LIBRARY_DEST_DIR ".")
set(PLUGIN_DEST_DIR ".")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
else()
message(FATAL_ERROR "No sensible install layout set.")
endif()
################################ Included Libs ################################ ################################ Included Libs ################################
include(ExternalProject) include(ExternalProject)
@@ -237,7 +71,6 @@ option(NBT_BUILD_SHARED "Build NBT shared library" ON)
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF) option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests. option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++) set(NBT_NAME MultiMC_nbt++)
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/ganalytics) # google analytics library add_subdirectory(libraries/ganalytics) # google analytics library
@@ -251,12 +84,11 @@ add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
############################### Built Artifacts ############################### ############################### Built Artifacts ###############################
add_subdirectory(api/logic) add_subdirectory(api/logic)
add_subdirectory(api/gui) add_subdirectory(api/gui)
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
add_subdirectory(application) add_subdirectory(application)
add_subdirectory(wonkoclient)

View File

@@ -1,6 +1,6 @@
# MultiMC #MultiMC
Copyright 2012-2019 MultiMC Contributors Copyright 2012-2017 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
# MinGW runtime (Windows) #MinGW runtime (Windows)
Copyright (c) 2012 MinGW.org project Copyright (c) 2012 MinGW.org project
@@ -35,14 +35,14 @@
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
# Qt 5 #Qt 5
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
Contact: http://www.qt-project.org/legal Contact: http://www.qt-project.org/legal
Licensed under LGPL v2.1 Licensed under LGPL v2.1
# libnbt++ #libnbt++
libnbt++ - A library for the Minecraft Named Binary Tag format. libnbt++ - A library for the Minecraft Named Binary Tag format.
Copyright (C) 2013, 2015 ljfa-ag Copyright (C) 2013, 2015 ljfa-ag
@@ -60,7 +60,27 @@
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with libnbt++. If not, see <http://www.gnu.org/licenses/>. along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
# rainbow (KGuiAddons) #Group View
Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
Copyright (C) 2007 John Tapsell <tapsell@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
#rainbow (KGuiAddons)
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org> Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
@@ -83,7 +103,7 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. Boston, MA 02110-1301, USA.
# Hoedown #Hoedown
Copyright (c) 2008, Natacha Porté Copyright (c) 2008, Natacha Porté
Copyright (c) 2011, Vicent Martí Copyright (c) 2011, Vicent Martí
@@ -101,7 +121,7 @@
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# Batch icon set #Batch icon set
You are free to use Batch (the "icon set") or any part thereof (the "icons") You are free to use Batch (the "icon set") or any part thereof (the "icons")
in any personal, open-source or commercial work without obligation of payment in any personal, open-source or commercial work without obligation of payment
@@ -117,18 +137,7 @@
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
# Material Design Icons #Pack200
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
with Reserved Font Name Material Design Icons.
Copyright (c) 2014, Google (http://www.google.com/design/)
uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
# Pack200
The GNU General Public License (GPL) The GNU General Public License (GPL)
@@ -157,7 +166,7 @@
the library, but you are not obligated to do so. If you do not wish to do the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version. so, delete this exception statement from your version.
# Quazip #Quazip
Copyright (C) 2005-2011 Sergey A. Tachenov Copyright (C) 2005-2011 Sergey A. Tachenov
@@ -180,7 +189,7 @@
Original ZIP package is copyrighted by Gilles Vollant, see Original ZIP package is copyrighted by Gilles Vollant, see
quazip/(un)zip.h files for details, basically it's zlib license. quazip/(un)zip.h files for details, basically it's zlib license.
# xz-minidec #xz-minidec
XZ decompressor XZ decompressor
@@ -190,65 +199,7 @@
This file has been put into the public domain. This file has been put into the public domain.
You can do whatever you want with this file. You can do whatever you want with this file.
# ColumnResizer #ColumnResizer
Copyright (c) 2011-2016 Aurélien Gâteau and contributors. Copyright 2011 Aurélien Gâteau <agateau@kde.org>
License: LGPL v2.1 or later (see COPYING)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the
disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* The name of the contributors may not be used to endorse or
promote products derived from this software without specific prior
written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# lionshead
Code has been taken from https://github.com/natefoo/lionshead and loosely
translated to C++ laced with Qt.
MIT License
Copyright (c) 2017 Nate Coraor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,4 @@
<p align="center"> ![MultiMC](http://i.imgur.com/QJXbz.png)
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
</p>
MultiMC 5 MultiMC 5
========= =========
@@ -13,7 +11,7 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features. We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many. If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [![Stories in Ready](https://badge.waffle.io/MultiMC/MultiMC5.svg?label=ready&title=Ready)](http://waffle.io/MultiMC/MultiMC5)
### Building ### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions. If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
@@ -27,7 +25,9 @@ We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the
## Translations ## Translations
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC). Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5) or using our [translation server](http://translate.multimc.org). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
Currently, MultiMC is [![Translation Status](http://translate.multimc.org/widgets/multimc/-/shields-badge.svg)](http://translate.multimc.org/engage/multimc/?utm_source=widget)
## Forking/Redistributing ## Forking/Redistributing
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license. We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
@@ -38,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License ## License
Copyright &copy; 2013-2019 MultiMC Contributors Copyright &copy; 2013-2017 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).

View File

@@ -21,14 +21,8 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
generate_export_header(MultiMC_gui) generate_export_header(MultiMC_gui)
# Link # Link
target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui) target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
qt5_use_modules(MultiMC_gui Gui)
# Mark and export headers # Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
# Install it
install(
TARGETS MultiMC_gui
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -34,4 +34,4 @@ namespace DesktopServices
* Open the URL, most likely in a browser. Maybe. * Open the URL, most likely in a browser. Maybe.
*/ */
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url); MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
} };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -187,7 +187,8 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction; return Qt::CopyAction;
} }
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{ {
if (action == Qt::IgnoreAction) if (action == Qt::IgnoreAction)
return true; return true;
@@ -260,7 +261,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix(); QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
continue; continue;
if (!QFile::copy(file, target)) if (!QFile::copy(file, target))
@@ -268,17 +269,6 @@ void IconList::installIcons(const QStringList &iconFiles)
} }
} }
void IconList::installIcon(const QString &file, const QString &name)
{
QFileInfo fileinfo(file);
if(!fileinfo.isReadable() || !fileinfo.isFile())
return;
QString target = FS::PathCombine(m_dir.dirName(), name);
QFile::copy(file, target);
}
bool IconList::iconFileExists(const QString &key) const bool IconList::iconFileExists(const QString &key) const
{ {
auto iconEntry = icon(key); auto iconEntry = icon(key);
@@ -341,7 +331,7 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
{ {
// replace the icon even? is the input valid? // replace the icon even? is the input valid?
QIcon icon(path); QIcon icon(path);
if (icon.isNull()) if (!icon.availableSizes().size())
return false; return false;
auto iter = name_index.find(key); auto iter = name_index.find(key);
if (iter != name_index.end()) if (iter != name_index.end())
@@ -402,6 +392,20 @@ QIcon IconList::getIcon(const QString &key) const
return QIcon(); return QIcon();
} }
QIcon IconList::getBigIcon(const QString &key) const
{
int icon_index = getIconIndex(key);
// Fallback for icons that don't exist.
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
if (icon_index == -1)
return QIcon();
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
return QIcon(bigone);
}
int IconList::getIconIndex(const QString &key) const int IconList::getIconIndex(const QString &key) const
{ {
auto iter = name_index.find(key == "default" ? "infinity" : key); auto iter = name_index.find(key == "default" ? "infinity" : key);
@@ -411,9 +415,4 @@ int IconList::getIconIndex(const QString &key) const
return -1; return -1;
} }
QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
//#include "IconList.moc" //#include "IconList.moc"

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -38,8 +38,8 @@ public:
virtual ~IconList() {}; virtual ~IconList() {};
QIcon getIcon(const QString &key) const; QIcon getIcon(const QString &key) const;
QIcon getBigIcon(const QString &key) const;
int getIconIndex(const QString &key) const; int getIconIndex(const QString &key) const;
QString getDirectory() const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -56,7 +56,6 @@ public:
virtual Qt::ItemFlags flags(const QModelIndex &index) const override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void installIcons(const QStringList &iconFiles) override; void installIcons(const QStringList &iconFiles) override;
void installIcon(const QString &file, const QString &name) override;
const MMCIcon * icon(const QString &key) const; const MMCIcon * icon(const QString &key) const;
@@ -80,7 +79,7 @@ protected slots:
void fileChanged(const QString &path); void fileChanged(const QString &path);
void SettingChanged(const Setting & setting, QVariant value); void SettingChanged(const Setting & setting, QVariant value);
private: private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher; std::shared_ptr<QFileSystemWatcher> m_watcher;
bool is_watching; bool is_watching;
QMap<QString, int> name_index; QMap<QString, int> name_index;
QVector<MMCIcon> icons; QVector<MMCIcon> icons;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -102,17 +102,3 @@ void MMCIcon::replace(IconType new_type, const QString& key)
m_images[new_type].filename = QString(); m_images[new_type].filename = QString();
m_images[new_type].key = key; m_images[new_type].key = key;
} }
QString MMCIcon::getFilePath() const
{
if(m_current_type == IconType::ToBeDeleted){
return QString();
}
return m_images[m_current_type].filename;
}
bool MMCIcon::isBuiltIn() const
{
return m_current_type == IconType::Builtin;
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -46,6 +46,4 @@ struct MULTIMC_GUI_EXPORT MMCIcon
void remove(IconType rm_type); void remove(IconType rm_type);
void replace(IconType new_type, QIcon icon, QString path = QString()); void replace(IconType new_type, QIcon icon, QString path = QString());
void replace(IconType new_type, const QString &key); void replace(IconType new_type, const QString &key);
bool isBuiltIn() const;
QString getFilePath() const;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,19 +16,19 @@
#include <QFile> #include <QFile>
#include "BaseInstaller.h" #include "BaseInstaller.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/onesix/OneSixInstance.h"
BaseInstaller::BaseInstaller() BaseInstaller::BaseInstaller()
{ {
} }
bool BaseInstaller::isApplied(MinecraftInstance *on) bool BaseInstaller::isApplied(OneSixInstance *on)
{ {
return QFile::exists(filename(on->instanceRoot())); return QFile::exists(filename(on->instanceRoot()));
} }
bool BaseInstaller::add(MinecraftInstance *to) bool BaseInstaller::add(OneSixInstance *to)
{ {
if (!patchesDir(to->instanceRoot()).exists()) if (!patchesDir(to->instanceRoot()).exists())
{ {
@@ -46,7 +46,7 @@ bool BaseInstaller::add(MinecraftInstance *to)
return true; return true;
} }
bool BaseInstaller::remove(MinecraftInstance *from) bool BaseInstaller::remove(OneSixInstance *from)
{ {
return QFile::remove(filename(from->instanceRoot())); return QFile::remove(filename(from->instanceRoot()));
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class MinecraftInstance; class OneSixInstance;
class QDir; class QDir;
class QString; class QString;
class QObject; class QObject;
@@ -32,12 +32,12 @@ class MULTIMC_LOGIC_EXPORT BaseInstaller
public: public:
BaseInstaller(); BaseInstaller();
virtual ~BaseInstaller(){}; virtual ~BaseInstaller(){};
bool isApplied(MinecraftInstance *on); bool isApplied(OneSixInstance *on);
virtual bool add(MinecraftInstance *to); virtual bool add(OneSixInstance *to);
virtual bool remove(MinecraftInstance *from); virtual bool remove(OneSixInstance *from);
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
protected: protected:
virtual QString id() const = 0; virtual QString id() const = 0;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
#include "settings/Setting.h" #include "settings/Setting.h"
#include "settings/OverrideSetting.h" #include "settings/OverrideSetting.h"
#include "minecraft/MinecraftVersionList.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Commandline.h" #include "Commandline.h"
@@ -102,6 +103,13 @@ void BaseInstance::invalidate()
qDebug() << "Instance" << id() << "has been invalidated."; qDebug() << "Instance" << id() << "has been invalidated.";
} }
void BaseInstance::nuke()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
FS::deletePath(instanceRoot());
}
void BaseInstance::changeStatus(BaseInstance::Status newStatus) void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{ {
Status status = currentStatus(); Status status = currentStatus();
@@ -175,6 +183,11 @@ QString BaseInstance::instanceRoot() const
return m_rootDir; return m_rootDir;
} }
InstancePtr BaseInstance::getSharedPtr()
{
return shared_from_this();
}
SettingsObjectPtr BaseInstance::settings() const SettingsObjectPtr BaseInstance::settings() const
{ {
return m_settings; return m_settings;
@@ -185,7 +198,7 @@ bool BaseInstance::canLaunch() const
return (!hasVersionBroken() && !isRunning()); return (!hasVersionBroken() && !isRunning());
} }
bool BaseInstance::reloadSettings() bool BaseInstance::reload()
{ {
return m_settings->reload(); return m_settings->reload();
} }
@@ -202,6 +215,31 @@ void BaseInstance::setLastLaunch(qint64 val)
emit propertiesChanged(this); emit propertiesChanged(this);
} }
void BaseInstance::setGroupInitial(QString val)
{
if(m_group == val)
{
return;
}
m_group = val;
emit propertiesChanged(this);
}
void BaseInstance::setGroupPost(QString val)
{
if(m_group == val)
{
return;
}
setGroupInitial(val);
emit groupChanged();
}
QString BaseInstance::group() const
{
return m_group;
}
void BaseInstance::setNotes(QString val) void BaseInstance::setNotes(QString val)
{ {
//FIXME: if no change, do not set. setting involves saving a file. //FIXME: if no change, do not set. setting involves saving a file.
@@ -239,16 +277,31 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const QString BaseInstance::windowTitle() const
{ {
return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " "); return "MultiMC: " + name();
} }
// FIXME: why is this here? move it to MinecraftInstance!!!
QStringList BaseInstance::extraArguments() const QStringList BaseInstance::extraArguments() const
{ {
return Commandline::splitArgs(settings()->get("JvmArgs").toString()); return Commandline::splitArgs(settings()->get("JvmArgs").toString());
} }
shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask() std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{ {
return m_launchProcess; return m_launchProcess;
} }
void BaseInstance::setProvider(BaseInstanceProvider* provider)
{
// only once.
assert(!m_provider);
if(m_provider)
{
qWarning() << "Provider set more than once for instance" << id();
}
m_provider = provider;
}
BaseInstanceProvider* BaseInstance::provider() const
{
return m_provider;
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -30,14 +30,13 @@
#include "MessageLevel.h" #include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h" #include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class QDir; class QDir;
class Task; class Task;
class LaunchTask; class LaunchTask;
class BaseInstance; class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people // pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr; typedef std::shared_ptr<BaseInstance> InstancePtr;
@@ -68,7 +67,13 @@ public:
/// virtual destructor to make sure the destruction is COMPLETE /// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {}; virtual ~BaseInstance() {};
virtual void saveNow() = 0; virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
virtual void init() = 0;
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
/// responsible of cleaning up the husk
void nuke();
/*** /***
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason, * the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
@@ -87,18 +92,15 @@ public:
int64_t totalTimePlayed() const; int64_t totalTimePlayed() const;
void resetTimePlayed(); void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance /// get the type of this instance
QString instanceType() const; QString instanceType() const;
/// Path to the instance's root directory. /// Path to the instance's root directory.
QString instanceRoot() const; QString instanceRoot() const;
/// Path to the instance's game root directory.
virtual QString gameRoot() const
{
return instanceRoot();
}
QString name() const; QString name() const;
void setName(QString val); void setName(QString val);
@@ -111,6 +113,10 @@ public:
QString notes() const; QString notes() const;
void setNotes(QString val); void setNotes(QString val);
QString group() const;
void setGroupInitial(QString val);
void setGroupPost(QString val);
QString getPreLaunchCommand(); QString getPreLaunchCommand();
QString getPostExitCommand(); QString getPostExitCommand();
QString getWrapperCommand(); QString getWrapperCommand();
@@ -123,8 +129,25 @@ public:
virtual QStringList extraArguments() const; virtual QStringList extraArguments() const;
virtual QString intendedVersionId() const = 0;
virtual bool setIntendedVersionId(QString version) = 0;
/*!
* The instance's current version.
* This value represents the instance's current version. If this value is
* different from the intendedVersion, the instance should be updated.
* \warning Don't change this value unless you know what you're doing.
*/
virtual QString currentVersionId() const = 0;
/*!
* Whether or not 'the game' should be downloaded when the instance is launched.
*/
virtual bool shouldUpdate() const = 0;
virtual void setShouldUpdate(bool val) = 0;
/// Traits. Normally inside the version, depends on instance implementation. /// Traits. Normally inside the version, depends on instance implementation.
virtual QSet <QString> traits() const = 0; virtual QSet <QString> traits() = 0;
/** /**
* Gets the time that the instance was last launched. * Gets the time that the instance was last launched.
@@ -134,6 +157,14 @@ public:
/// Sets the last launched time to 'val' milliseconds since epoch /// Sets the last launched time to 'val' milliseconds since epoch
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
InstancePtr getSharedPtr();
/*!
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
/*! /*!
* \brief Gets this instance's settings object. * \brief Gets this instance's settings object.
* This settings object stores instance-specific settings. * This settings object stores instance-specific settings.
@@ -142,13 +173,19 @@ public:
virtual SettingsObjectPtr settings() const; virtual SettingsObjectPtr settings() const;
/// returns a valid update task /// returns a valid update task
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0; virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
/// returns a valid launcher (task container) /// returns a valid launcher (task container)
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0; virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
/// returns the current launch task (if any) /// returns the current launch task (if any)
shared_qobject_ptr<LaunchTask> getLaunchTask(); std::shared_ptr<LaunchTask> getLaunchTask();
/*!
* Returns a task that should be done right before launch
* This task should do any extra preparations needed
*/
virtual std::shared_ptr<Task> createJarModdingTask() = 0;
/*! /*!
* Create envrironment variables for running the instance * Create envrironment variables for running the instance
@@ -214,11 +251,10 @@ public:
} }
} }
virtual bool canLaunch() const; bool canLaunch() const;
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0; virtual bool canExport() const = 0;
bool reloadSettings(); virtual bool reload();
/** /**
* 'print' a verbose desription of the instance into a QStringList * 'print' a verbose desription of the instance into a QStringList
@@ -238,8 +274,12 @@ signals:
* \brief Signal emitted when properties relevant to the instance view change * \brief Signal emitted when properties relevant to the instance view change
*/ */
void propertiesChanged(BaseInstance *inst); void propertiesChanged(BaseInstance *inst);
/*!
* \brief Signal emitted when groups are affected in any way
*/
void groupChanged();
void launchTaskChanged(shared_qobject_ptr<LaunchTask>); void launchTaskChanged(std::shared_ptr<LaunchTask>);
void runningStatusChanged(bool running); void runningStatusChanged(bool running);
@@ -250,11 +290,13 @@ protected slots:
protected: /* data */ protected: /* data */
QString m_rootDir; QString m_rootDir;
QString m_group;
SettingsObjectPtr m_settings; SettingsObjectPtr m_settings;
// InstanceFlags m_flags; // InstanceFlags m_flags;
bool m_isRunning = false; bool m_isRunning = false;
shared_qobject_ptr<LaunchTask> m_launchProcess; std::shared_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted; QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */ private: /* data */
Status m_status = Status::Present; Status m_status = Status::Present;
@@ -263,6 +305,6 @@ private: /* data */
bool m_hasBrokenVersion = false; bool m_hasBrokenVersion = false;
}; };
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>) Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) //Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) //Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View File

@@ -0,0 +1,57 @@
#pragma once
#include <QObject>
#include <QString>
#include "BaseInstance.h"
#include "settings/SettingsObject.h"
#include "multimc_logic_export.h"
using InstanceId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
{
Q_OBJECT
public:
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
{
// nil
}
public:
virtual QList<InstanceId> discoverInstances() = 0;
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
virtual void loadGroupList() = 0;
virtual void saveGroupList() = 0;
virtual QString getStagedInstancePath()
{
return QString();
}
virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
{
return false;
}
virtual bool destroyStagingPath(const QString & path)
{
return true;
}
signals:
// Emit this when the list of provided instances changed
void instancesChanged();
// Emit when the set of groups your provider supplies changes.
void groupsChanged(QSet<QString> groups);
protected:
SettingsObjectPtr m_globalSettings;
};

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
return BaseVersionPtr(); return BaseVersionPtr();
} }
BaseVersionPtr BaseVersionList::getRecommended() const BaseVersionPtr BaseVersionList::getLatestStable() const
{ {
if (count() <= 0) if (count() <= 0)
return BaseVersionPtr(); return BaseVersionPtr();
@@ -38,6 +38,11 @@ BaseVersionPtr BaseVersionList::getRecommended() const
return at(0); return at(0);
} }
BaseVersionPtr BaseVersionList::getRecommended() const
{
return getLatestStable();
}
QVariant BaseVersionList::data(const QModelIndex &index, int role) const QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid()) if (!index.isValid())
@@ -88,7 +93,7 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
QHash<int, QByteArray> roles = QAbstractListModel::roleNames(); QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(VersionRole, "version"); roles.insert(VersionRole, "version");
roles.insert(VersionIdRole, "versionId"); roles.insert(VersionIdRole, "versionId");
roles.insert(ParentVersionRole, "parentGameVersion"); roles.insert(ParentGameVersionRole, "parentGameVersion");
roles.insert(RecommendedRole, "recommended"); roles.insert(RecommendedRole, "recommended");
roles.insert(LatestRole, "latest"); roles.insert(LatestRole, "latest");
roles.insert(TypeRole, "type"); roles.insert(TypeRole, "type");

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "QObjectPtr.h"
/*! /*!
* \brief Class that each instance type's version list derives from. * \brief Class that each instance type's version list derives from.
@@ -45,7 +44,7 @@ public:
VersionPointerRole = Qt::UserRole, VersionPointerRole = Qt::UserRole,
VersionRole, VersionRole,
VersionIdRole, VersionIdRole,
ParentVersionRole, ParentGameVersionRole,
RecommendedRole, RecommendedRole,
LatestRole, LatestRole,
TypeRole, TypeRole,
@@ -64,7 +63,7 @@ public:
* The task returned by this function should reset the model when it's done. * The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list. * \return A pointer to a task that reloads the version list.
*/ */
virtual shared_qobject_ptr<Task> getLoadTask() = 0; virtual Task *getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be //! Checks whether or not the list is loaded. If this returns false, the list should be
//loaded. //loaded.
@@ -77,22 +76,27 @@ public:
virtual int count() const = 0; virtual int count() const = 0;
//////// List Model Functions //////// //////// List Model Functions ////////
QVariant data(const QModelIndex &index, int role) const override; virtual QVariant data(const QModelIndex &index, int role) const;
int rowCount(const QModelIndex &parent) const override; virtual int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const override; virtual int columnCount(const QModelIndex &parent) const;
QHash<int, QByteArray> roleNames() const override; virtual QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list? //! which roles are provided by this version list?
virtual RoleList providesRoles() const; virtual RoleList providesRoles() const;
/*! /*!
* \brief Finds a version by its descriptor. * \brief Finds a version by its descriptor.
* \param descriptor The descriptor of the version to find. * \param The descriptor of the version to find.
* \return A const pointer to the version with the given descriptor. NULL if * \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist. * one doesn't exist.
*/ */
virtual BaseVersionPtr findVersion(const QString &descriptor); virtual BaseVersionPtr findVersion(const QString &descriptor);
/*!
* \brief Gets the latest stable version from this list
*/
virtual BaseVersionPtr getLatestStable() const;
/*! /*!
* \brief Gets the recommended version from this list * \brief Gets the recommended version from this list
* If the list doesn't support recommended versions, this works exactly as getLatestStable * If the list doesn't support recommended versions, this works exactly as getLatestStable

View File

@@ -8,14 +8,21 @@ set(CORE_SOURCES
BaseInstaller.cpp BaseInstaller.cpp
BaseVersionList.h BaseVersionList.h
BaseVersionList.cpp BaseVersionList.cpp
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
InstanceList.h InstanceList.h
InstanceList.cpp InstanceList.cpp
InstanceTask.h
InstanceTask.cpp
LoggedProcess.h LoggedProcess.h
LoggedProcess.cpp LoggedProcess.cpp
MessageLevel.cpp MessageLevel.cpp
MessageLevel.h MessageLevel.h
BaseInstanceProvider.h
FolderInstanceProvider.h
FolderInstanceProvider.cpp
BaseVersion.h BaseVersion.h
BaseInstance.h BaseInstance.h
BaseInstance.cpp BaseInstance.cpp
@@ -25,14 +32,6 @@ set(CORE_SOURCES
MMCStrings.h MMCStrings.h
MMCStrings.cpp MMCStrings.cpp
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
# Use tracking separate from memory management # Use tracking separate from memory management
Usable.h Usable.h
@@ -43,10 +42,6 @@ set(CORE_SOURCES
Env.h Env.h
Env.cpp Env.cpp
# String filters
Filter.h
Filter.cpp
# JSON parsing helpers # JSON parsing helpers
Json.h Json.h
Json.cpp Json.cpp
@@ -182,11 +177,9 @@ set(NEWS_SOURCES
# Icon interface # Icon interface
set(ICONS_SOURCES set(ICONS_SOURCES
# Icons System and related code # News System
icons/IIconList.h icons/IIconList.h
icons/IIconList.cpp icons/IIconList.cpp
icons/IconUtils.h
icons/IconUtils.cpp
) )
# Minecraft services status checker # Minecraft services status checker
@@ -213,16 +206,22 @@ set(MINECRAFT_SOURCES
minecraft/auth/flows/RefreshTask.cpp minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp minecraft/auth/flows/ValidateTask.cpp
minecraft/gameoptions/GameOptions.h minecraft/onesix/OneSixUpdate.h
minecraft/gameoptions/GameOptions.cpp minecraft/onesix/OneSixUpdate.cpp
minecraft/update/AssetUpdateTask.h minecraft/onesix/OneSixInstance.h
minecraft/update/AssetUpdateTask.cpp minecraft/onesix/OneSixInstance.cpp
minecraft/update/FMLLibrariesTask.cpp minecraft/onesix/OneSixProfileStrategy.cpp
minecraft/update/FMLLibrariesTask.h minecraft/onesix/OneSixProfileStrategy.h
minecraft/update/FoldersTask.cpp minecraft/onesix/OneSixVersionFormat.cpp
minecraft/update/FoldersTask.h minecraft/onesix/OneSixVersionFormat.h
minecraft/update/LibrariesTask.cpp minecraft/onesix/update/AssetUpdateTask.h
minecraft/update/LibrariesTask.h minecraft/onesix/update/AssetUpdateTask.cpp
minecraft/onesix/update/FMLLibrariesTask.cpp
minecraft/onesix/update/FMLLibrariesTask.h
minecraft/onesix/update/FoldersTask.cpp
minecraft/onesix/update/FoldersTask.h
minecraft/onesix/update/LibrariesTask.cpp
minecraft/onesix/update/LibrariesTask.h
minecraft/launch/ClaimAccount.cpp minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h minecraft/launch/ClaimAccount.h
minecraft/launch/CreateServerResourcePacksFolder.cpp minecraft/launch/CreateServerResourcePacksFolder.cpp
@@ -237,66 +236,86 @@ set(MINECRAFT_SOURCES
minecraft/launch/LauncherPartLaunch.h minecraft/launch/LauncherPartLaunch.h
minecraft/launch/PrintInstanceInfo.cpp minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
minecraft/launch/ReconstructAssets.h
minecraft/legacy/LegacyModList.h minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp minecraft/legacy/LegacyModList.cpp
minecraft/legacy/LegacyUpdate.h
minecraft/legacy/LegacyUpdate.cpp
minecraft/legacy/LegacyInstance.h minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyUpgradeTask.h minecraft/legacy/LwjglVersionList.h
minecraft/legacy/LegacyUpgradeTask.cpp minecraft/legacy/LwjglVersionList.cpp
minecraft/GradleSpecifier.h minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftProfile.cpp
minecraft/MinecraftInstance.h minecraft/MinecraftProfile.h
minecraft/LaunchProfile.cpp
minecraft/LaunchProfile.h
minecraft/Component.cpp
minecraft/Component.h
minecraft/ComponentList.cpp
minecraft/ComponentList.h
minecraft/ComponentUpdateTask.cpp
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h minecraft/MojangVersionFormat.h
minecraft/JarMod.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
minecraft/MinecraftVersion.cpp
minecraft/MinecraftVersion.h
minecraft/MinecraftVersionList.cpp
minecraft/MinecraftVersionList.h
minecraft/Rule.cpp minecraft/Rule.cpp
minecraft/Rule.h minecraft/Rule.h
minecraft/OneSixVersionFormat.cpp
minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp minecraft/OpSys.cpp
minecraft/OpSys.h minecraft/OpSys.h
minecraft/ParseUtils.cpp minecraft/ParseUtils.cpp
minecraft/ParseUtils.h minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp minecraft/ProfileUtils.cpp
minecraft/ProfileUtils.h minecraft/ProfileUtils.h
minecraft/ProfileStrategy.h
minecraft/Library.cpp minecraft/Library.cpp
minecraft/Library.h minecraft/Library.h
minecraft/MojangDownloadInfo.h minecraft/MojangDownloadInfo.h
minecraft/VersionBuildError.h
minecraft/VersionFile.cpp minecraft/VersionFile.cpp
minecraft/VersionFile.h minecraft/VersionFile.h
minecraft/ProfilePatch.h
minecraft/VersionFilterData.h minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp minecraft/VersionFilterData.cpp
minecraft/Mod.h minecraft/Mod.h
minecraft/Mod.cpp minecraft/Mod.cpp
minecraft/SimpleModList.h minecraft/ModList.h
minecraft/SimpleModList.cpp minecraft/ModList.cpp
minecraft/World.h minecraft/World.h
minecraft/World.cpp minecraft/World.cpp
minecraft/WorldList.h minecraft/WorldList.h
minecraft/WorldList.cpp minecraft/WorldList.cpp
# FTB
minecraft/ftb/OneSixFTBInstance.h
minecraft/ftb/OneSixFTBInstance.cpp
minecraft/ftb/LegacyFTBInstance.h
minecraft/ftb/LegacyFTBInstance.cpp
minecraft/ftb/FTBProfileStrategy.h
minecraft/ftb/FTBProfileStrategy.cpp
minecraft/ftb/FTBInstanceProvider.cpp
minecraft/ftb/FTBInstanceProvider.h
minecraft/ftb/FTBPlugin.h
minecraft/ftb/FTBPlugin.cpp
# Assets # Assets
minecraft/AssetsUtils.h minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp minecraft/AssetsUtils.cpp
# Forge and all things forge related # Forge and all things forge related
minecraft/forge/ForgeVersion.h
minecraft/forge/ForgeVersion.cpp
minecraft/forge/ForgeVersionList.h
minecraft/forge/ForgeVersionList.cpp
minecraft/forge/ForgeXzDownload.h minecraft/forge/ForgeXzDownload.h
minecraft/forge/ForgeXzDownload.cpp minecraft/forge/ForgeXzDownload.cpp
minecraft/forge/LegacyForge.h
minecraft/forge/LegacyForge.cpp
minecraft/forge/ForgeInstaller.h
minecraft/forge/ForgeInstaller.cpp
# Skin upload utilities # Liteloader and related things
minecraft/liteloader/LiteLoaderInstaller.h
minecraft/liteloader/LiteLoaderInstaller.cpp
minecraft/liteloader/LiteLoaderVersionList.h
minecraft/liteloader/LiteLoaderVersionList.cpp
minecraft/SkinUpload.cpp minecraft/SkinUpload.cpp
minecraft/SkinUpload.h minecraft/SkinUpload.h
) )
@@ -318,8 +337,8 @@ add_unit_test(Library
) )
# FIXME: shares data with FileSystem test # FIXME: shares data with FileSystem test
add_unit_test(SimpleModList add_unit_test(ModList
SOURCES minecraft/SimpleModList_test.cpp SOURCES minecraft/ModList_test.cpp
DATA testdata DATA testdata
LIBS MultiMC_logic LIBS MultiMC_logic
) )
@@ -342,6 +361,8 @@ set(TASKS_SOURCES
# Tasks # Tasks
tasks/Task.h tasks/Task.h
tasks/Task.cpp tasks/Task.cpp
tasks/ThreadTask.h
tasks/ThreadTask.cpp
tasks/SequentialTask.h tasks/SequentialTask.h
tasks/SequentialTask.cpp tasks/SequentialTask.cpp
) )
@@ -393,8 +414,6 @@ add_unit_test(JavaVersion
set(TRANSLATIONS_SOURCES set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h translations/TranslationsModel.h
translations/TranslationsModel.cpp translations/TranslationsModel.cpp
translations/POTranslator.h
translations/POTranslator.cpp
) )
set(TOOLS_SOURCES set(TOOLS_SOURCES
@@ -411,44 +430,32 @@ set(TOOLS_SOURCES
tools/MCEditTool.h tools/MCEditTool.h
) )
set(META_SOURCES set(WONKO_SOURCES
# Metadata sources # Wonko
meta/JsonFormat.cpp wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
meta/JsonFormat.h wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
meta/BaseEntity.cpp wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
meta/BaseEntity.h wonko/tasks/BaseWonkoEntityLocalLoadTask.h
meta/VersionList.cpp wonko/format/WonkoFormatV1.cpp
meta/VersionList.h wonko/format/WonkoFormatV1.h
meta/Version.cpp wonko/format/WonkoFormat.cpp
meta/Version.h wonko/format/WonkoFormat.h
meta/Index.cpp wonko/BaseWonkoEntity.cpp
meta/Index.h wonko/BaseWonkoEntity.h
wonko/WonkoVersionList.cpp
wonko/WonkoVersionList.h
wonko/WonkoVersion.cpp
wonko/WonkoVersion.h
wonko/WonkoIndex.cpp
wonko/WonkoIndex.h
wonko/WonkoUtil.cpp
wonko/WonkoUtil.h
wonko/WonkoReference.cpp
wonko/WonkoReference.h
) )
set(FTB_SOURCES add_unit_test(WonkoIndex
modplatform/ftb/FtbPackFetchTask.h SOURCES wonko/WonkoIndex_test.cpp
modplatform/ftb/FtbPackFetchTask.cpp
modplatform/ftb/FtbPackInstallTask.h
modplatform/ftb/FtbPackInstallTask.cpp
modplatform/ftb/FtbPrivatePackManager.h
modplatform/ftb/FtbPrivatePackManager.cpp
modplatform/ftb/PackHelpers.h
)
set(FLAME_SOURCES
# Flame
modplatform/flame/PackManifest.h
modplatform/flame/PackManifest.cpp
modplatform/flame/FileResolvingTask.h
modplatform/flame/FileResolvingTask.cpp
modplatform/flame/UrlResolvingTask.h
modplatform/flame/UrlResolvingTask.cpp
)
add_unit_test(Index
SOURCES meta/Index_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
@@ -473,29 +480,19 @@ set(LOGIC_SOURCES
${JAVA_SOURCES} ${JAVA_SOURCES}
${TRANSLATIONS_SOURCES} ${TRANSLATIONS_SOURCES}
${TOOLS_SOURCES} ${TOOLS_SOURCES}
${META_SOURCES} ${WONKO_SOURCES}
${ICONS_SOURCES} ${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
) )
message(STATUS "FOO! ${LOGIC_SOURCES}")
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_logic) generate_export_header(MultiMC_logic)
# Link # Link
target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES}) target_link_libraries(MultiMC_logic xz-embedded unpack200 systeminfo MultiMC_quazip ${NBT_NAME} ${ZLIB_LIBRARIES})
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
# Mark and export headers # Mark and export headers
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
# Install it
install(
TARGETS MultiMC_logic
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -44,7 +44,7 @@ QStringList splitArgs(QString args)
} }
else if (!inquotes.isNull()) else if (!inquotes.isNull())
{ {
if (cchar == '\\') if (cchar == 0x5C)
escape = true; escape = true;
else if (cchar == inquotes) else if (cchar == inquotes)
inquotes = 0; inquotes = 0;
@@ -54,7 +54,7 @@ QStringList splitArgs(QString args)
} }
else else
{ {
if (cchar == ' ') if (cchar == 0x20)
{ {
if (!current.isEmpty()) if (!current.isEmpty())
{ {
@@ -62,7 +62,7 @@ QStringList splitArgs(QString args)
current.clear(); current.clear();
} }
} }
else if (cchar == '"' || cchar == '\'') else if (cchar == 0x22 || cchar == 0x27)
inquotes = cchar; inquotes = cchar;
else else
current += cchar; current += cchar;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *

View File

@@ -3,24 +3,23 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include <QDir> #include <QDir>
#include <QCoreApplication>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QDebug> #include <QDebug>
#include "tasks/Task.h" #include "tasks/Task.h"
#include "meta/Index.h" #include "wonko/WonkoIndex.h"
#include "FileSystem.h"
#include <QDebug> #include <QDebug>
struct Env::Private class Env::Private
{ {
public:
QNetworkAccessManager m_qnam; QNetworkAccessManager m_qnam;
shared_qobject_ptr<HttpMetaCache> m_metacache; shared_qobject_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IIconList> m_iconlist; std::shared_ptr<IIconList> m_iconlist;
shared_qobject_ptr<Meta::Index> m_metadataIndex; QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
QString m_jarsPath; shared_qobject_ptr<WonkoIndex> m_wonkoIndex;
QSet<QString> m_features; QString m_wonkoRootUrl;
}; };
static Env * instance; static Env * instance;
@@ -74,13 +73,39 @@ void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
d->m_iconlist = iconlist; d->m_iconlist = iconlist;
} }
shared_qobject_ptr<Meta::Index> Env::metadataIndex() BaseVersionPtr Env::getVersion(QString component, QString version)
{ {
if (!d->m_metadataIndex) auto list = getVersionList(component);
if(!list)
{ {
d->m_metadataIndex.reset(new Meta::Index()); return nullptr;
} }
return d->m_metadataIndex; return list->findVersion(version);
}
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
{
auto iter = d->m_versionLists.find(component);
if(iter != d->m_versionLists.end())
{
return *iter;
}
//return std::make_shared<NullVersionList>();
return nullptr;
}
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
{
d->m_versionLists[name] = vlist;
}
shared_qobject_ptr<WonkoIndex> Env::wonkoIndex()
{
if (!d->m_wonkoIndex)
{
d->m_wonkoIndex.reset(new WonkoIndex());
}
return d->m_wonkoIndex;
} }
@@ -96,12 +121,11 @@ void Env::initHttpMetaCache()
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath()); m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
m_metacache->Load(); m_metacache->Load();
} }
@@ -167,43 +191,14 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
qDebug() << proxyDesc; qDebug() << proxyDesc;
} }
QString Env::getJarsPath() QString Env::wonkoRootUrl() const
{ {
if(d->m_jarsPath.isEmpty()) return d->m_wonkoRootUrl;
{
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
}
return d->m_jarsPath;
} }
void Env::setJarsPath(const QString& path) void Env::setWonkoRootUrl(const QString& url)
{ {
d->m_jarsPath = path; d->m_wonkoRootUrl = url;
} }
void Env::enableFeature(const QString& featureName, bool state) #include "Env.moc"
{
if(state)
{
d->m_features.insert(featureName);
}
else
{
d->m_features.remove(featureName);
}
}
bool Env::isFeatureEnabled(const QString& featureName) const
{
return d->m_features.contains(featureName);
}
void Env::getEnabledFeatures(QSet<QString>& features) const
{
features = d->m_features;
}
void Env::setEnabledFeatures(const QSet<QString>& features) const
{
d->m_features = features;
}

View File

@@ -13,23 +13,18 @@ class QNetworkAccessManager;
class HttpMetaCache; class HttpMetaCache;
class BaseVersionList; class BaseVersionList;
class BaseVersion; class BaseVersion;
class WonkoIndex;
namespace Meta
{
class Index;
}
#if defined(ENV) #if defined(ENV)
#undef ENV #undef ENV
#endif #endif
#define ENV (Env::getInstance()) #define ENV (Env::getInstance())
class MULTIMC_LOGIC_EXPORT Env class MULTIMC_LOGIC_EXPORT Env
{ {
friend class MultiMC; friend class MultiMC;
private: private:
struct Private; class Private;
Env(); Env();
~Env(); ~Env();
static void dispose(); static void dispose();
@@ -48,17 +43,20 @@ public:
/// Updates the application proxy settings from the settings object. /// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
/// get a version list by name
std::shared_ptr<BaseVersionList> getVersionList(QString component);
/// get a version by list name and version name
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
void registerIconList(std::shared_ptr<IIconList> iconlist); void registerIconList(std::shared_ptr<IIconList> iconlist);
shared_qobject_ptr<Meta::Index> metadataIndex(); shared_qobject_ptr<WonkoIndex> wonkoIndex();
QString getJarsPath(); QString wonkoRootUrl() const;
void setJarsPath(const QString & path); void setWonkoRootUrl(const QString &url);
bool isFeatureEnabled(const QString & featureName) const;
void enableFeature(const QString & featureName, bool state = true);
void getEnabledFeatures(QSet<QString> & features) const;
void setEnabledFeatures(const QSet<QString> & features) const;
protected: protected:
Private * d; Private * d;

View File

@@ -1,43 +0,0 @@
#pragma once
template <typename T>
inline void clamp(T& current, T min, T max)
{
if (current < min)
{
current = min;
}
else if(current > max)
{
current = max;
}
}
// List of numbers from min to max. Next is exponent times bigger than previous.
class ExponentialSeries
{
public:
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
{
m_current = m_min = min;
m_max = max;
m_exponent = exponent;
}
void reset()
{
m_current = m_min;
}
unsigned operator()()
{
unsigned retval = m_current;
m_current *= m_exponent;
clamp(m_current, m_min, m_max);
return retval;
}
unsigned m_current;
unsigned m_min;
unsigned m_max;
unsigned m_exponent;
};

View File

@@ -3,27 +3,11 @@
#include "FileSystem.h" #include "FileSystem.h"
#include <QDir> #include <QDir>
#include <QFile>
#include <QSaveFile> #include <QSaveFile>
#include <QFileInfo> #include <QFileInfo>
#include <QDebug> #include <QDebug>
#include <QUrl> #include <QUrl>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTextStream>
#if defined Q_OS_WIN32
#include <windows.h>
#include <string>
#include <sys/utime.h>
#include <winnls.h>
#include <shobjidl.h>
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
#else
#include <utime.h>
#endif
namespace FS { namespace FS {
@@ -78,13 +62,21 @@ QByteArray read(const QString &filename)
bool updateTimestamp(const QString& filename) bool updateTimestamp(const QString& filename)
{ {
#ifdef Q_OS_WIN32 QFile file(filename);
std::wstring filename_utf_16 = filename.toStdWString(); if (!file.exists())
return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); {
#else return false;
QByteArray filenameBA = QFile::encodeName(filename); }
return (utime(filenameBA.data(), nullptr) == 0); if (!file.open(QIODevice::ReadWrite))
#endif {
return false;
}
const quint64 size = file.size();
file.seek(size);
file.write( QByteArray(1, '0') );
file.resize(size);
return true;
} }
bool ensureFilePathExists(QString filenamepath) bool ensureFilePathExists(QString filenamepath)
@@ -171,6 +163,11 @@ bool copy::operator()(const QString &offset)
return true; return true;
} }
#if defined Q_OS_WIN32
#include <windows.h>
#include <string>
#endif
bool deletePath(QString path) bool deletePath(QString path)
{ {
bool OK = true; bool OK = true;
@@ -228,7 +225,7 @@ bool deletePath(QString path)
} }
QString PathCombine(const QString & path1, const QString & path2) QString PathCombine(QString path1, QString path2)
{ {
if(!path1.size()) if(!path1.size())
return path2; return path2;
@@ -237,16 +234,11 @@ QString PathCombine(const QString & path1, const QString & path2)
return QDir::cleanPath(path1 + QDir::separator() + path2); return QDir::cleanPath(path1 + QDir::separator() + path2);
} }
QString PathCombine(const QString & path1, const QString & path2, const QString & path3) QString PathCombine(QString path1, QString path2, QString path3)
{ {
return PathCombine(PathCombine(path1, path2), path3); return PathCombine(PathCombine(path1, path2), path3);
} }
QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
{
return PathCombine(PathCombine(path1, path2, path3), path4);
}
QString AbsolutePath(QString path) QString AbsolutePath(QString path)
{ {
return QFileInfo(path).absolutePath(); return QFileInfo(path).absolutePath();
@@ -294,7 +286,7 @@ QString NormalizePath(QString path)
} }
} }
QString badFilenameChars = "\"\\/?<>:*|!+\r\n"; QString badFilenameChars = "\"\\/?<>:*|!";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{ {
@@ -340,9 +332,21 @@ bool checkProblemticPathJava(QDir folder)
return pathfoldername.contains("!", Qt::CaseInsensitive); return pathfoldername.contains("!", Qt::CaseInsensitive);
} }
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
// Win32 crap // Win32 crap
#if defined Q_OS_WIN #if defined Q_OS_WIN
#include <windows.h>
#include <winnls.h>
#include <shobjidl.h>
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
bool called_coinit = false; bool called_coinit = false;
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
@@ -356,7 +360,7 @@ HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
if (!SUCCEEDED(hres)) if (!SUCCEEDED(hres))
{ {
qWarning("Failed to initialize COM. Error 0x%08lX", hres); qWarning("Failed to initialize COM. Error 0x%08X", hres);
return hres; return hres;
} }
} }

View File

@@ -83,9 +83,8 @@ private:
*/ */
MULTIMC_LOGIC_EXPORT bool deletePath(QString path); MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2); MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3); MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path); MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);

View File

@@ -1,31 +0,0 @@
#include "Filter.h"
Filter::~Filter(){}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
ContainsFilter::~ContainsFilter(){}
bool ContainsFilter::accepts(const QString& value)
{
return value.contains(pattern);
}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
ExactFilter::~ExactFilter(){}
bool ExactFilter::accepts(const QString& value)
{
return value == pattern;
}
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
:invert(invert)
{
pattern.setPattern(regexp);
pattern.optimize();
}
RegexpFilter::~RegexpFilter(){}
bool RegexpFilter::accepts(const QString& value)
{
auto match = pattern.match(value);
bool matched = match.hasMatch();
return invert ? (!matched) : (matched);
}

View File

@@ -1,44 +0,0 @@
#pragma once
#include <QString>
#include <QRegularExpression>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT Filter
{
public:
virtual ~Filter();
virtual bool accepts(const QString & value) = 0;
};
class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
{
public:
ContainsFilter(const QString &pattern);
virtual ~ContainsFilter();
bool accepts(const QString & value) override;
private:
QString pattern;
};
class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
{
public:
ExactFilter(const QString &pattern);
virtual ~ExactFilter();
bool accepts(const QString & value) override;
private:
QString pattern;
};
class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter
{
public:
RegexpFilter(const QString &regexp, bool invert);
virtual ~RegexpFilter();
bool accepts(const QString & value) override;
private:
QRegularExpression pattern;
bool invert = false;
};

View File

@@ -0,0 +1,356 @@
#include "FolderInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include "minecraft/onesix/OneSixInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
#include <QDir>
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
const static int GROUP_FILE_FORMAT_VERSION = 1;
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
: m_watcher(watcher), m_instDir(instDir)
{
m_watcher->removePath(m_instDir);
}
~WatchLock()
{
m_watcher->addPath(m_instDir);
}
QFileSystemWatcher * m_watcher;
QString m_instDir;
};
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
: BaseInstanceProvider(settings)
{
m_instDir = instDir;
if (!QDir::current().exists(m_instDir))
{
QDir::current().mkpath(m_instDir);
}
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
m_watcher->addPath(m_instDir);
}
QList< InstanceId > FolderInstanceProvider::discoverInstances()
{
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
// if it is a symlink, ignore it if it goes to the instance folder
if(dirInfo.isSymLink())
{
QFileInfo targetInfo(dirInfo.symLinkTarget());
QFileInfo instDirInfo(m_instDir);
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
{
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
}
auto id = dirInfo.fileName();
out.append(id);
qDebug() << "Found instance ID" << id;
}
return out;
}
InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
{
if(!m_groupsLoaded)
{
loadGroupList();
}
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
inst->init();
inst->setProvider(this);
auto iter = groupMap.find(id);
if (iter != groupMap.end())
{
inst->setGroupInitial((*iter));
}
connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged);
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
#include "InstanceImportTask.h"
Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup);
}
#include "InstanceCreationTask.h"
Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup);
}
#include "InstanceCopyTask.h"
Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
{
return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves);
}
void FolderInstanceProvider::saveGroupList()
{
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
{
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
if (!reverseGroupMap.count(group))
{
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
}
else
{
QSet<QString> &set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
}
catch(FS::FileSystemException & e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
void FolderInstanceProvider::loadGroupList()
{
QSet<QString> groupSet;
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QByteArray jsonData;
try
{
jsonData = FS::read(groupFileName);
}
catch (FS::FileSystemException & e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
groupMap.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
iter2++)
{
groupMap[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
emit groupsChanged(groupSet);
}
void FolderInstanceProvider::groupChanged()
{
// save the groups. save all of them.
auto instance = (BaseInstance *) QObject::sender();
auto id = instance->id();
groupMap[id] = instance->group();
emit groupsChanged({instance->group()});
saveGroupList();
}
void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
{
Q_UNUSED(path);
emit instancesChanged();
}
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
{
QString newInstDir = value.toString();
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
{
saveGroupList();
}
m_instDir = newInstDir;
m_groupsLoaded = false;
emit instancesChanged();
}
}
QString FolderInstanceProvider::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString();
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if(!rootPath.mkpath(relPath))
{
return QString();
}
return path;
}
bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
const QString& groupName)
{
if(!path.contains(keyPath))
{
qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath;
return false;
}
QDir dir;
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
if(!dir.rename(path, FS::PathCombine(m_instDir, instID)))
{
destroyStagingPath(keyPath);
return false;
}
groupMap[instID] = groupName;
emit groupsChanged({groupName});
emit instancesChanged();
}
saveGroupList();
return true;
}
bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
{
return FS::deletePath(keyPath);
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include "BaseInstanceProvider.h"
#include <QMap>
class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
{
Q_OBJECT
public:
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
public:
/// used by InstanceList to @return a list of plausible IDs to probe for
QList<InstanceId> discoverInstances() override;
/// used by InstanceList to (re)load an instance with the given @id.
InstancePtr loadInstance(const InstanceId& id) override;
// create instance in this provider
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
// copy instance to this provider
Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves);
// import zipped instance into this provider
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
*/
QString getStagedInstancePath() override;
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override;
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString & keyPath) override;
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
private slots:
void instanceDirContentsChanged(const QString &path);
void groupChanged();
private: /* methods */
void loadGroupList() override;
void saveGroupList() override;
private: /* data */
QString m_instDir;
QFileSystemWatcher * m_watcher;
QMap<QString, QString> groupMap;
bool m_groupsLoaded = false;
};

View File

@@ -1,13 +1,19 @@
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves) InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
{ {
m_globalSettings = settings;
m_target = target;
m_origInstance = origInstance; m_origInstance = origInstance;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
if(!copySaves) if(!copySaves)
{ {
@@ -21,7 +27,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
void InstanceCopyTask::executeTask() void InstanceCopyTask::executeTask()
{ {
setStatus(tr("Copying instance %1").arg(m_origInstance->name())); setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_stagingPath = m_target->getStagedInstancePath();
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).blacklist(m_matcher.get()); folderCopy.followSymlinks(false).blacklist(m_matcher.get());
@@ -36,6 +42,7 @@ void InstanceCopyTask::copyFinished()
auto successful = m_copyFuture.result(); auto successful = m_copyFuture.result();
if(!successful) if(!successful)
{ {
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance folder copy failed.")); emitFailed(tr("Instance folder copy failed."));
return; return;
} }
@@ -43,14 +50,19 @@ void InstanceCopyTask::copyFinished()
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->registerSetting("InstanceType", "Legacy");
// FIXME: and this too? errors???
m_origInstance->copy(instanceSettings, m_stagingPath);
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(m_instName); inst->setName(m_instName);
inst->setIconKey(m_instIcon); inst->setIconKey(m_instIcon);
m_target->commitStagedInstance(m_stagingPath, m_stagingPath, m_instName, m_instGroup);
emitSucceeded(); emitSucceeded();
} }
void InstanceCopyTask::copyAborted() void InstanceCopyTask::copyAborted()
{ {
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance folder copy has been aborted.")); emitFailed(tr("Instance folder copy has been aborted."));
return; return;
} }

View File

@@ -9,13 +9,16 @@
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "InstanceTask.h"
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves); explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
const QString &instIcon, const QString &instGroup, bool copySaves);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
@@ -24,8 +27,16 @@ protected:
void copyAborted(); void copyAborted();
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings;
BaseInstanceProvider * m_target = nullptr;
InstancePtr m_origInstance; InstancePtr m_origInstance;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
QFuture<bool> m_copyFuture; QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher; QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher; std::unique_ptr<IPathMatcher> m_matcher;
}; };

View File

@@ -1,31 +1,46 @@
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
//FIXME: remove this //FIXME: remove this
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftVersion.h"
#include "minecraft/ComponentList.h" #include "minecraft/onesix/OneSixInstance.h"
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
const QString& instName, const QString& instIcon, const QString& instGroup)
{ {
m_globalSettings = settings;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
m_version = version; m_version = version;
} }
void InstanceCreationTask::executeTask() void InstanceCreationTask::executeTask()
{ {
setStatus(tr("Creating instance from version %1").arg(m_version->name())); setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
if(!minecraftVersion)
{ {
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); emitFailed(tr("The supplied version is not a Minecraft version."));
instanceSettings->suspendSave(); return ;
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
auto components = inst.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
inst.setName(m_instName);
inst.setIconKey(m_instIcon);
instanceSettings->resumeSave();
} }
QString stagingPath = m_target->getStagedInstancePath();
QDir rootDir(stagingPath);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
instanceSettings->set("InstanceType", "OneSix");
InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath));
inst->setIntendedVersionId(m_version->descriptor());
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
inst->init();
m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
emitSucceeded(); emitSucceeded();
} }

View File

@@ -6,18 +6,26 @@
#include <QUrl> #include <QUrl>
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "InstanceTask.h"
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCreationTask(BaseVersionPtr version); explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings;
BaseInstanceProvider * m_target;
BaseVersionPtr m_version; BaseVersionPtr m_version;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
}; };

View File

@@ -1,24 +1,24 @@
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Env.h" #include "Env.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "icons/IIconList.h" #include "icons/IIconList.h"
#include "icons/IconUtils.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
// FIXME: this does not belong here, it's Minecraft/Flame specific InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
#include "minecraft/MinecraftInstance.h" const QString &instName, const QString &instIcon, const QString &instGroup)
#include "minecraft/ComponentList.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h"
#include "Json.h"
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
{ {
m_globalSettings = settings;
m_sourceUrl = sourceUrl; m_sourceUrl = sourceUrl;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
} }
void InstanceImportTask::executeTask() void InstanceImportTask::executeTask()
@@ -28,7 +28,7 @@ void InstanceImportTask::executeTask()
if (m_sourceUrl.isLocalFile()) if (m_sourceUrl.isLocalFile())
{ {
m_archivePath = m_sourceUrl.toLocalFile(); m_archivePath = m_sourceUrl.toLocalFile();
processZipPack(); extractAndTweak();
} }
else else
{ {
@@ -51,14 +51,12 @@ void InstanceImportTask::executeTask()
void InstanceImportTask::downloadSucceeded() void InstanceImportTask::downloadSucceeded()
{ {
processZipPack(); extractAndTweak();
m_filesNetJob.reset();
} }
void InstanceImportTask::downloadFailed(QString reason) void InstanceImportTask::downloadFailed(QString reason)
{ {
emitFailed(reason); emitFailed(reason);
m_filesNetJob.reset();
} }
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
@@ -66,47 +64,34 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
setProgress(current / 2, total); setProgress(current / 2, total);
} }
void InstanceImportTask::processZipPack() static QFileInfo findRecursive(const QString &dir, const QString &name)
{
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
{
if (info.isFile() && info.fileName() == name)
{
return info;
}
else if (info.isDir())
{
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
if (res.isFile() && res.exists())
{
return res;
}
}
}
return QFileInfo();
}
void InstanceImportTask::extractAndTweak()
{ {
setStatus(tr("Extracting modpack")); setStatus(tr("Extracting modpack"));
m_stagingPath = m_target->getStagedInstancePath();
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath; qDebug() << "Attempting to create instance from" << m_archivePath;
// open the zip and find relevant files in it m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath());
m_packZip.reset(new QuaZip(m_archivePath));
if (!m_packZip->open(QuaZip::mdUnzip))
{
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
QStringList blacklist = {"instance.cfg", "manifest.json"};
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
QString root;
if(!mmcFound.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcFound;
root = mmcFound;
m_modpackType = ModpackType::MultiMC;
}
else if(!flameFound.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameFound;
root = flameFound;
m_modpackType = ModpackType::Flame;
}
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
}
// make sure we extract just the pack
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture); m_extractFutureWatcher.setFuture(m_extractFuture);
@@ -114,271 +99,27 @@ void InstanceImportTask::processZipPack()
void InstanceImportTask::extractFinished() void InstanceImportTask::extractFinished()
{ {
m_packZip.reset();
if (m_extractFuture.result().isEmpty()) if (m_extractFuture.result().isEmpty())
{ {
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Failed to extract modpack")); emitFailed(tr("Failed to extract modpack"));
return; return;
} }
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
qDebug() << "Fixing permissions for extracted pack files..."; if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
QDirIterator it(extractDir, QDirIterator::Subdirectories);
while (it.hasNext())
{ {
auto filepath = it.next(); m_target->destroyStagingPath(m_stagingPath);
QFileInfo file(filepath); emitFailed(tr("Archive does not contain instance.cfg"));
auto permissions = QFile::permissions(filepath);
auto origPermissions = permissions;
if(file.isDir())
{
// Folder +rwx for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
}
else
{
// File +rw for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
}
if(origPermissions != permissions)
{
if(!QFile::setPermissions(filepath, permissions))
{
logWarning(tr("Could not fix permissions for %1").arg(filepath));
}
else
{
qDebug() << "Fixed" << filepath;
}
}
}
switch(m_modpackType)
{
case ModpackType::Flame:
processFlame();
return;
case ModpackType::MultiMC:
processMultiMC();
return;
case ModpackType::Unknown:
emitFailed(tr("Archive does not contain a recognized modpack type."));
return; return;
} }
}
void InstanceImportTask::extractAborted()
{
emitFailed(tr("Instance import has been aborted."));
return;
}
void InstanceImportTask::processFlame()
{
const static QMap<QString,QString> forgemap = {
{"1.2.5", "3.4.9.171"},
{"1.4.2", "6.0.1.355"},
{"1.4.7", "6.6.2.534"},
{"1.5.2", "7.8.1.737"}
};
Flame::Manifest pack;
try
{
QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
Flame::loadManifest(pack, configPath);
QFile::remove(configPath);
}
catch (const JSONValidationError &e)
{
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
if(!pack.overrides.isEmpty())
{
QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
if (QFile::exists(overridePath))
{
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
if (!QFile::rename(overridePath, mcPath))
{
emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
return;
}
}
else
{
logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
}
}
QString forgeVersion;
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
if(id.startsWith("forge-"))
{
id.remove("forge-");
forgeVersion = id;
continue;
}
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto mcVersion = pack.minecraft.version;
// Hack to correct some 'special sauce'...
if(mcVersion.endsWith('.'))
{
mcVersion.remove(QRegExp("[.]+$"));
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
auto components = instance.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
{
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
if(forgeVersion == "recommended")
{
if(forgemap.contains(mcVersion))
{
forgeVersion = forgemap[mcVersion];
}
else
{
logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion));
}
}
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
if(pack.name.contains("Direwolf20"))
{
instance.setIconKey("steve");
}
else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast"))
{
instance.setIconKey("ftb_logo");
}
else
{
// default to something other than the MultiMC default to distinguish these
instance.setIconKey("flame");
}
}
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
QFileInfo jarmodsInfo(jarmodsPath);
if(jarmodsInfo.isDir())
{
// install all the jar mods
qDebug() << "Found jarmods:";
QDir jarmodsDir(jarmodsPath);
QStringList jarMods;
for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
{
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
auto profile = instance.getComponentList();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
}
instance.setName(m_instName);
m_modIdResolver.reset(new Flame::FileResolvingTask(pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{
auto results = m_modIdResolver->getResults();
m_filesNetJob.reset(new NetJob(tr("Mod download")));
for(auto result: results.files)
{
QString filename = result.fileName;
if(!result.required)
{
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath , relpath);
switch(result.type)
{
case Flame::File::Type::Folder:
{
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod:
{
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
break;
}
case Flame::File::Type::Modpack:
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
{
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
{
m_modIdResolver.reset();
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
{
setStatus(status);
});
m_modIdResolver->start();
}
void InstanceImportTask::processMultiMC()
{
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!! // FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->registerSetting("InstanceType", "Legacy");
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); QString actualDir = instanceCfgFile.absolutePath();
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
// reset time played on import... because packs. // reset time played on import... because packs.
instance.resetTimePlayed(); instance.resetTimePlayed();
@@ -394,9 +135,8 @@ void InstanceImportTask::processMultiMC()
else else
{ {
m_instIcon = instance.iconKey(); m_instIcon = instance.iconKey();
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); if (QFile::exists(importIconPath))
if (!importIconPath.isNull() && QFile::exists(importIconPath))
{ {
// import icon // import icon
auto iconList = ENV.icons(); auto iconList = ENV.icons();
@@ -407,5 +147,18 @@ void InstanceImportTask::processMultiMC()
iconList->installIcons({importIconPath}); iconList->installIcons({importIconPath});
} }
} }
if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup))
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Unable to commit instance"));
return;
}
emitSucceeded(); emitSucceeded();
} }
void InstanceImportTask::extractAborted()
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance import has been aborted."));
return;
}

View File

@@ -1,34 +1,28 @@
#pragma once #pragma once
#include "InstanceTask.h" #include "tasks/Task.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include <QUrl> #include <QUrl>
#include <QFuture> #include <QFuture>
#include <QFutureWatcher> #include <QFutureWatcher>
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "QObjectPtr.h"
class QuaZip; class BaseInstanceProvider;
namespace Flame
{
class FileResolvingTask;
}
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl sourceUrl); explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private: private:
void processZipPack(); void extractAndTweak();
void processMultiMC();
void processFlame();
private slots: private slots:
void downloadSucceeded(); void downloadSucceeded();
@@ -38,17 +32,16 @@ private slots:
void extractAborted(); void extractAborted();
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings;
NetJobPtr m_filesNetJob; NetJobPtr m_filesNetJob;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl; QUrl m_sourceUrl;
BaseInstanceProvider * m_target;
QString m_archivePath; QString m_archivePath;
bool m_downloadRequired = false; bool m_downloadRequired = false;
std::unique_ptr<QuaZip> m_packZip; QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
QFuture<QStringList> m_extractFuture; QFuture<QStringList> m_extractFuture;
QFutureWatcher<QStringList> m_extractFutureWatcher; QFutureWatcher<QStringList> m_extractFutureWatcher;
enum class ModpackType{
Unknown,
MultiMC,
Flame
} m_modpackType = ModpackType::Unknown;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,49 +14,23 @@
*/ */
#include <QDir> #include <QDir>
#include <QDirIterator>
#include <QSet> #include <QSet>
#include <QFile> #include <QFile>
#include <QThread> #include <QThread>
#include <QTextStream> #include <QTextStream>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QTimer>
#include <QDebug> #include <QDebug>
#include <QFileSystemWatcher>
#include <QUuid>
#include <QJsonArray>
#include <QJsonDocument>
#include "InstanceList.h" #include "InstanceList.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "InstanceTask.h"
#include "settings/INISettingsObject.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
#include "minecraft/MinecraftInstance.h"
#include "FileSystem.h"
#include "ExponentialSeries.h"
#include "WatchLock.h"
const static int GROUP_FILE_FORMAT_VERSION = 1; #include "FolderInstanceProvider.h"
InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_globalSettings(settings) : QAbstractListModel(parent), m_instDir(instDir)
{ {
m_globalSettings = globalSettings;
resumeWatch(); resumeWatch();
// Create aand normalize path
if (!QDir::current().exists(instDir))
{
QDir::current().mkpath(instDir);
}
connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated);
// NOTE: canonicalPath requires the path to exist. Do not move this above the creation block!
m_instDir = QDir(instDir).canonicalPath();
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InstanceList::instanceDirContentsChanged);
m_watcher->addPath(m_instDir);
} }
InstanceList::~InstanceList() InstanceList::~InstanceList()
@@ -95,7 +69,6 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
{ {
return pdata->id(); return pdata->id();
} }
case Qt::EditRole:
case Qt::DisplayRole: case Qt::DisplayRole:
{ {
return pdata->name(); return pdata->name();
@@ -111,7 +84,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
// HACK: see GroupView.h in gui! // HACK: see GroupView.h in gui!
case GroupRole: case GroupRole:
{ {
return getInstanceGroup(pdata->id()); return pdata->group();
} }
default: default:
break; break;
@@ -119,85 +92,16 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid())
{
return false;
}
if(role != Qt::EditRole)
{
return false;
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
auto newName = value.toString();
if(pdata->name() == newName)
{
return true;
}
pdata->setName(newName);
return true;
}
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags f; Qt::ItemFlags f;
if (index.isValid()) if (index.isValid())
{ {
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
} }
return f; return f;
} }
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
{
auto inst = getInstanceById(id);
if(!inst)
{
return GroupId();
}
auto iter = m_groupMap.find(inst->id());
if(iter != m_groupMap.end())
{
return *iter;
}
return GroupId();
}
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
{
auto inst = getInstanceById(id);
if(!inst)
{
qDebug() << "Attempt to set a null instance's group";
return;
}
bool changed = false;
auto iter = m_groupMap.find(inst->id());
if(iter != m_groupMap.end())
{
if(*iter != name)
{
*iter = name;
changed = true;
}
}
else
{
changed = true;
m_groupMap[id] = name;
}
if(changed)
{
m_groups.insert(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), {GroupRole});
saveGroupList();
}
}
QStringList InstanceList::getGroups() QStringList InstanceList::getGroups()
{ {
return m_groups.toList(); return m_groups.toList();
@@ -205,52 +109,14 @@ QStringList InstanceList::getGroups()
void InstanceList::deleteGroup(const QString& name) void InstanceList::deleteGroup(const QString& name)
{ {
bool removed = false;
qDebug() << "Delete group" << name;
for(auto & instance: m_instances) for(auto & instance: m_instances)
{ {
const auto & instID = instance->id(); auto instGroupName = instance->group();
auto instGroupName = getInstanceGroup(instID);
if(instGroupName == name) if(instGroupName == name)
{ {
m_groupMap.remove(instID); instance->setGroupPost(QString());
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if(idx > 0)
{
emit dataChanged(index(idx), index(idx), {GroupRole});
} }
} }
}
if(removed)
{
saveGroupList();
}
}
void InstanceList::deleteInstance(const InstanceId& id)
{
auto inst = getInstanceById(id);
if(!inst)
{
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
return;
}
if(m_groupMap.remove(id))
{
saveGroupList();
}
qDebug() << "Will delete instance" << id;
if(!FS::deletePath(inst->instanceRoot()))
{
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
return;
}
qDebug() << "Instance" << id << "has been deleted by MultiMC.";
} }
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list) static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
@@ -270,60 +136,50 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
return out; return out;
} }
QList< InstanceId > InstanceList::discoverInstances() InstanceList::InstListError InstanceList::loadList(bool complete)
{
qDebug() << "Discovering instances in" << m_instDir;
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
// if it is a symlink, ignore it if it goes to the instance folder
if(dirInfo.isSymLink())
{
QFileInfo targetInfo(dirInfo.symLinkTarget());
QFileInfo instDirInfo(m_instDir);
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
{
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
}
auto id = dirInfo.fileName();
out.append(id);
qDebug() << "Found instance ID" << id;
}
instanceSet = out.toSet();
m_instancesProbed = true;
return out;
}
InstanceList::InstListError InstanceList::loadList()
{ {
auto existingIds = getIdMapping(m_instances); auto existingIds = getIdMapping(m_instances);
QList<InstancePtr> newList; QList<InstancePtr> newList;
for(auto & id: discoverInstances()) auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
{
for(auto & id: ids)
{ {
if(existingIds.contains(id)) if(existingIds.contains(id))
{ {
auto instPair = existingIds[id]; auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id); existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id; qDebug() << "Should keep and soft-reload" << id;
} }
else else
{ {
InstancePtr instPtr = loadInstance(id); InstancePtr instPtr = provider->loadInstance(id);
if(instPtr) if(instPtr)
{ {
newList.append(instPtr); newList.append(instPtr);
} }
} }
} }
};
if(complete)
{
for(auto & item: m_providers)
{
processIds(item.get(), item->discoverInstances());
}
}
else
{
for (auto & item: m_updatedProviders)
{
processIds(item, item->discoverInstances());
}
}
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it. // TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
if(!existingIds.isEmpty()) if(!existingIds.isEmpty())
@@ -350,6 +206,10 @@ InstanceList::InstListError InstanceList::loadList()
for(auto & removedItem: deadList) for(auto & removedItem: deadList)
{ {
auto instPtr = removedItem.first; auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
}
instPtr->invalidate(); instPtr->invalidate();
currentItem = removedItem.second; currentItem = removedItem.second;
if(back_bookmark == -1) if(back_bookmark == -1)
@@ -377,18 +237,10 @@ InstanceList::InstListError InstanceList::loadList()
{ {
add(newList); add(newList);
} }
m_dirty = false; m_updatedProviders.clear();
return NoError; return NoError;
} }
void InstanceList::saveNow()
{
for(auto & item: m_instances)
{
item->saveNow();
}
}
void InstanceList::add(const QList<InstancePtr> &t) void InstanceList::add(const QList<InstancePtr> &t)
{ {
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
@@ -408,7 +260,7 @@ void InstanceList::resumeWatch()
return; return;
} }
m_watchLevel++; m_watchLevel++;
if(m_watchLevel > 0 && m_dirty) if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
{ {
loadList(); loadList();
} }
@@ -421,13 +273,31 @@ void InstanceList::suspendWatch()
void InstanceList::providerUpdated() void InstanceList::providerUpdated()
{ {
m_dirty = true; auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
if(!provider)
{
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
return;
}
m_updatedProviders.insert(provider);
if(m_watchLevel == 1) if(m_watchLevel == 1)
{ {
loadList(); loadList();
} }
} }
void InstanceList::groupsPublished(QSet<QString> newGroups)
{
m_groups.unite(newGroups);
}
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
{
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
m_providers.append(provider);
}
InstancePtr InstanceList::getInstanceById(QString instId) const InstancePtr InstanceList::getInstanceById(QString instId) const
{ {
if(instId.isEmpty()) if(instId.isEmpty())
@@ -469,365 +339,4 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
} }
} }
InstancePtr InstanceList::loadInstance(const InstanceId& id)
{
if(!m_groupsLoaded)
{
loadGroupList();
}
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
void InstanceList::saveGroupList()
{
qDebug() << "Will save group list now.";
if(!m_instancesProbed)
{
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
return;
}
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++)
{
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
if(!instanceSet.contains(id))
{
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
continue;
}
if (!reverseGroupMap.count(group))
{
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
}
else
{
QSet<QString> &set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
qDebug() << "Group list saved.";
}
catch (const FS::FileSystemException &e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
void InstanceList::loadGroupList()
{
qDebug() << "Will load group list now.";
QSet<QString> groupSet;
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QByteArray jsonData;
try
{
jsonData = FS::read(groupFileName);
}
catch (const FS::FileSystemException &e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
m_groupMap.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
{
m_groupMap[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
m_groups.unite(groupSet);
qDebug() << "Group list loaded.";
}
void InstanceList::instanceDirContentsChanged(const QString& path)
{
Q_UNUSED(path);
emit instancesChanged();
}
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
{
saveGroupList();
}
m_instDir = newInstDir;
m_groupsLoaded = false;
emit instancesChanged();
}
}
class InstanceStaging : public Task
{
Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
public:
InstanceStaging (
InstanceList * parent,
Task * child,
const QString & stagingPath,
const QString& instanceName,
const QString& groupName )
: backoff(minBackoff, maxBackoff)
{
m_parent = parent;
m_child.reset(child);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
m_instanceName = instanceName;
m_groupName = groupName;
m_stagingPath = stagingPath;
m_backoffTimer.setSingleShot(true);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
}
virtual ~InstanceStaging() {};
// FIXME/TODO: add ability to abort during instance commit retries
bool abort() override
{
if(m_child && m_child->canAbort())
{
return m_child->abort();
}
return false;
}
bool canAbort() const override
{
if(m_child && m_child->canAbort())
{
return true;
}
return false;
}
protected:
virtual void executeTask() override
{
m_child->start();
}
QStringList warnings() const override
{
return m_child->warnings();
}
private slots:
void childSucceded()
{
unsigned sleepTime = backoff();
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
{
emitSucceeded();
return;
}
// we actually failed, retry?
if(sleepTime == maxBackoff)
{
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
return;
}
qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
m_backoffTimer.start(sleepTime * 500);
}
void childFailed(const QString & reason)
{
m_parent->destroyStagingPath(m_stagingPath);
emitFailed(reason);
}
private:
/*
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
* Basically, it starts messing things up while MultiMC is extracting/creating instances
* and causes that horrible failure that is NTFS to lock files in place because they are open.
*/
ExponentialSeries backoff;
QString m_stagingPath;
InstanceList * m_parent;
unique_qobject_ptr<Task> m_child;
QString m_instanceName;
QString m_groupName;
QTimer m_backoffTimer;
};
Task * InstanceList::wrapInstanceTask(InstanceTask * task)
{
auto stagingPath = getStagedInstancePath();
task->setStagingPath(stagingPath);
task->setParentSettings(m_globalSettings);
return new InstanceStaging(this, task, stagingPath, task->name(), task->group());
}
QString InstanceList::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString();
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if(!rootPath.mkpath(relPath))
{
return QString();
}
return path;
}
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
{
QDir dir;
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID);
if(!dir.rename(path, destination))
{
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
m_groupMap[instID] = groupName;
instanceSet.insert(instID);
m_groups.insert(groupName);
emit instancesChanged();
emit instanceSelectRequest(instID);
}
saveGroupList();
return true;
}
bool InstanceList::destroyStagingPath(const QString& keyPath)
{
return FS::deletePath(keyPath);
}
#include "InstanceList.moc" #include "InstanceList.moc"

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,49 +21,29 @@
#include <QList> #include <QList>
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
class QFileSystemWatcher; class QFileSystemWatcher;
class InstanceTask; class BaseInstance;
using InstanceId = QString; class QDir;
using GroupId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
enum class GroupsState
{
NotLoaded,
Steady,
Dirty
};
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0); explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
virtual ~InstanceList(); virtual ~InstanceList();
public: public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
enum AdditionalRoles enum AdditionalRoles
{ {
@@ -92,75 +72,37 @@ public:
return m_instances.count(); return m_instances.count();
} }
InstListError loadList(); InstListError loadList(bool complete = false);
void saveNow();
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
void addInstanceProvider(BaseInstanceProvider * provider);
InstancePtr getInstanceById(QString id) const; InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const; QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups(); QStringList getGroups();
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId & name); void deleteGroup(const QString & name);
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
Task * wrapInstanceTask(InstanceTask * task);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
*/
QString getStagedInstancePath();
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString & keyPath);
signals: signals:
void dataIsInvalid(); void dataIsInvalid();
void instancesChanged();
void instanceSelectRequest(QString instanceId);
void groupsChanged(QSet<QString> groups);
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
private slots: private slots:
void propertiesChanged(BaseInstance *inst); void propertiesChanged(BaseInstance *inst);
void groupsPublished(QSet<QString>);
void providerUpdated(); void providerUpdated();
void instanceDirContentsChanged(const QString &path);
private: private:
int getInstIndex(BaseInstance *inst) const; int getInstIndex(BaseInstance *inst) const;
void suspendWatch(); void suspendWatch();
void resumeWatch(); void resumeWatch();
void add(const QList<InstancePtr> &list); void add(const QList<InstancePtr> &list);
void loadGroupList();
void saveGroupList();
QList<InstanceId> discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
private: protected:
int m_watchLevel = 0; int m_watchLevel = 0;
bool m_dirty = false; QSet<BaseInstanceProvider *> m_updatedProviders;
QString m_instDir;
QList<InstancePtr> m_instances; QList<InstancePtr> m_instances;
QSet<QString> m_groups; QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings; SettingsObjectPtr m_globalSettings;
QString m_instDir; QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
QFileSystemWatcher * m_watcher;
QMap<InstanceId, GroupId> m_groupMap;
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
}; };

View File

@@ -1,9 +0,0 @@
#include "InstanceTask.h"
InstanceTask::InstanceTask()
{
}
InstanceTask::~InstanceTask()
{
}

View File

@@ -1,53 +0,0 @@
#pragma once
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "settings/SettingsObject.h"
class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
{
Q_OBJECT
public:
explicit InstanceTask();
virtual ~InstanceTask();
void setParentSettings(SettingsObjectPtr settings)
{
m_globalSettings = settings;
}
void setStagingPath(const QString &stagingPath)
{
m_stagingPath = stagingPath;
}
void setName(const QString &name)
{
m_instName = name;
}
QString name() const
{
return m_instName;
}
void setIcon(const QString &icon)
{
m_instIcon = icon;
}
void setGroup(const QString &group)
{
m_instGroup = group;
}
QString group() const
{
return m_instGroup;
}
protected: /* data */
SettingsObjectPtr m_globalSettings;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
};

View File

@@ -123,7 +123,7 @@ T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &w
{ {
return requireIsType<T>(value, what); return requireIsType<T>(value, what);
} }
catch (const JsonException &) catch (JsonException &)
{ {
return default_; return default_;
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,29 +1,172 @@
/* Copyright 2013-2019 MultiMC Contributors /*
* Copyright (C) 2010 Roberto Pompermaier
* Licensed under the Apache License, Version 2.0 (the "License"); Copyright (C) 2005-2014 Sergey A. Tachenov
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at Parts of this file were part of QuaZIP.
*
* http://www.apache.org/licenses/LICENSE-2.0 QuaZIP is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* Unless required by applicable law or agreed to in writing, software the Free Software Foundation, either version 2.1 of the License, or
* distributed under the License is distributed on an "AS IS" BASIS, (at your option) any later version.
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and QuaZIP is distributed in the hope that it will be useful,
* limitations under the License. but WITHOUT ANY WARRANTY; without even the implied warranty of
*/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>.
See COPYING file for the full LGPL text.
Original ZIP package is copyrighted by Gilles Vollant and contributors,
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
*/
#include <quazip.h> #include <quazip.h>
#include <quazipdir.h>
#include <quazipfile.h>
#include <JlCompress.h> #include <JlCompress.h>
#include <quazipdir.h>
#include "MMCZip.h" #include "MMCZip.h"
#include "FileSystem.h" #include "FileSystem.h"
#include <QDebug> #include <QDebug>
// ours bool copyData(QIODevice &inFile, QIODevice &outFile)
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter) {
while (!inFile.atEnd())
{
char buf[4096];
qint64 readLen = inFile.read(buf, 4096);
if (readLen <= 0)
return false;
if (outFile.write(buf, readLen) != readLen)
return false;
}
return true;
}
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
{
return JlCompress::extractDir(fileCompressed, dir);
}
bool compressFile(QuaZip *zip, QString fileName, QString fileDest)
{
if (!zip)
{
return false;
}
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
zip->getMode() != QuaZip::mdAdd)
{
return false;
}
QFile inFile;
inFile.setFileName(fileName);
if (!inFile.open(QIODevice::ReadOnly))
{
return false;
}
QuaZipFile outFile(zip);
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
{
return false;
}
if (!copyData(inFile, outFile) || outFile.getZipError() != UNZ_OK)
{
return false;
}
outFile.close();
if (outFile.getZipError() != UNZ_OK)
{
return false;
}
inFile.close();
return true;
}
bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QString>& added, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
{
if (!zip) return false;
if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd)
{
return false;
}
QDir directory(dir);
if (!directory.exists())
{
return false;
}
QDir origDirectory(origDir);
if (dir != origDir)
{
QString internalDirName = origDirectory.relativeFilePath(dir);
if(!blacklist || !blacklist->covers(internalDirName))
{
QuaZipFile dirZipFile(zip);
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
{
return false;
}
dirZipFile.close();
}
}
QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
for (auto file: files)
{
if(!file.isDir())
{
continue;
}
if(!compressSubDir(zip,file.absoluteFilePath(),origDir, added, prefix, blacklist))
{
return false;
}
}
files = directory.entryInfoList(QDir::Files);
for (auto file: files)
{
if(!file.isFile())
{
continue;
}
if(file.absoluteFilePath()==zip->getZipName())
{
continue;
}
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
if(blacklist && blacklist->covers(filename))
{
continue;
}
if(prefix.size())
{
filename = FS::PathCombine(prefix, filename);
}
added.insert(filename);
if (!compressFile(zip,file.absoluteFilePath(),filename))
{
return false;
}
}
return true;
}
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
std::function<bool(QString)> filter)
{ {
QuaZip modZip(from.filePath()); QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip); modZip.open(QuaZip::mdUnzip);
@@ -33,7 +176,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
{ {
QString filename = modZip.getCurrentFileName(); QString filename = modZip.getCurrentFileName();
if (filter && !filter(filename)) if (!filter(filename))
{ {
qDebug() << "Skipping file " << filename << " from " qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered"; << from.fileName() << " - filtered";
@@ -61,7 +204,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
fileInsideMod.close(); fileInsideMod.close();
return false; return false;
} }
if (!JlCompress::copyData(fileInsideMod, zipOutFile)) if (!copyData(fileInsideMod, zipOutFile))
{ {
zipOutFile.close(); zipOutFile.close();
fileInsideMod.close(); fileInsideMod.close();
@@ -74,7 +217,6 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
return true; return true;
} }
// ours
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods) bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
{ {
QuaZip zipOut(targetJarPath); QuaZip zipOut(targetJarPath);
@@ -99,7 +241,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
continue; continue;
if (mod.type() == Mod::MOD_ZIPFILE) if (mod.type() == Mod::MOD_ZIPFILE)
{ {
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
@@ -109,9 +251,9 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
} }
else if (mod.type() == Mod::MOD_SINGLEFILE) else if (mod.type() == Mod::MOD_SINGLEFILE)
{ {
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename(); auto filename = mod.filename();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) if (!compressFile(&zipOut, filename.absoluteFilePath(),
filename.fileName()))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
@@ -122,13 +264,12 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
} }
else if (mod.type() == Mod::MOD_FOLDER) else if (mod.type() == Mod::MOD_FOLDER)
{ {
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename(); auto filename = mod.filename();
QString what_to_zip = filename.absoluteFilePath(); QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip); QDir dir(what_to_zip);
dir.cdUp(); dir.cdUp();
QString parent_dir = dir.absolutePath(); QString parent_dir = dir.absolutePath();
if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
@@ -138,17 +279,9 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
qDebug() << "Adding folder " << filename.fileName() << " from " qDebug() << "Adding folder " << filename.fileName() << " from "
<< filename.absoluteFilePath(); << filename.absoluteFilePath();
} }
else
{
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
return false;
}
} }
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
@@ -167,8 +300,46 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
return true; return true;
} }
// ours bool MMCZip::noFilter(QString)
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root) {
return true;
}
bool MMCZip::metaInfFilter(QString key)
{
if(key.contains("META-INF"))
{
return false;
}
return true;
}
bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
{
QuaZip zip(zipFile);
QDir().mkpath(QFileInfo(zipFile).absolutePath());
if(!zip.open(QuaZip::mdCreate))
{
QFile::remove(zipFile);
return false;
}
QSet<QString> added;
if (!compressSubDir(&zip, dir, dir, added, prefix, blacklist))
{
QFile::remove(zipFile);
return false;
}
zip.close();
if(zip.getZipError()!=0)
{
QFile::remove(zipFile);
return false;
}
return true;
}
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
{ {
QuaZipDir rootDir(zip, root); QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files)) for(auto fileName: rootDir.entryList(QDir::Files))
@@ -178,7 +349,7 @@ QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const
} }
for(auto fileName: rootDir.entryList(QDir::Dirs)) for(auto fileName: rootDir.entryList(QDir::Dirs))
{ {
QString result = findFolderOfFileInZip(zip, what, root + fileName); QString result = findFileInZip(zip, what, root + fileName);
if(!result.isEmpty()) if(!result.isEmpty())
{ {
return result; return result;
@@ -187,7 +358,6 @@ QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const
return QString(); return QString();
} }
// ours
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{ {
QuaZipDir rootDir(zip, root); QuaZipDir rootDir(zip, root);
@@ -206,16 +376,95 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
return !result.isEmpty(); return !result.isEmpty();
} }
bool removeFile(QStringList listFile)
{
bool ret = true;
for (int i = 0; i < listFile.count(); i++)
{
ret &= QFile::remove(listFile.at(i));
}
return ret;
}
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
{
if(!zip)
return false;
if (zip->getMode() != QuaZip::mdUnzip)
return false;
if (!fileName.isEmpty())
zip->setCurrentFile(fileName);
QuaZipFile inFile(zip);
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
return false;
// Controllo esistenza cartella file risultato
QDir curDir;
if (fileDest.endsWith('/'))
{
if (!curDir.mkpath(fileDest))
{
return false;
}
}
else
{
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
{
return false;
}
}
QuaZipFileInfo64 info;
if (!zip->getCurrentFileInfo(&info))
return false;
QFile::Permissions srcPerm = info.getPermissions();
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
{
if (srcPerm != 0)
{
QFile(fileDest).setPermissions(srcPerm);
}
return true;
}
QFile outFile;
outFile.setFileName(fileDest);
if (!outFile.open(QIODevice::WriteOnly))
return false;
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
{
outFile.close();
removeFile(QStringList(fileDest));
return false;
}
outFile.close();
inFile.close();
if (inFile.getZipError() != UNZ_OK)
{
removeFile(QStringList(fileDest));
return false;
}
if (srcPerm != 0)
{
outFile.setPermissions(srcPerm);
}
return true;
}
// ours
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{ {
QDir directory(target); QDir directory(target);
QStringList extracted; QStringList extracted;
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
if (!zip->goToFirstFile()) if (!zip->goToFirstFile())
{ {
qWarning() << "Failed to seek to first file in zip";
return QStringList(); return QStringList();
} }
do do
@@ -231,25 +480,12 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
{ {
absFilePath += "/"; absFilePath += "/";
} }
if (!JlCompress::extractFile(zip, "", absFilePath)) if (!extractFile(zip, "", absFilePath))
{ {
qWarning() << "Failed to extract file" << name << "to" << absFilePath; removeFile(extracted);
JlCompress::removeFile(extracted);
return QStringList(); return QStringList();
} }
extracted.append(absFilePath); extracted.append(absFilePath);
qDebug() << "Extracted file" << name;
} while (zip->goToNextFile()); } while (zip->goToNextFile());
return extracted; return extracted;
} }
// ours
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
{
return {};
}
return MMCZip::extractSubDir(&zip, "", dir);
}

View File

@@ -1,50 +1,70 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
#include <QFileInfo> #include <QFileInfo>
#include <QSet> #include <QSet>
#include "minecraft/Mod.h" #include "minecraft/Mod.h"
#include "SeparatorPrefixTree.h"
#include <functional> #include <functional>
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include <JlCompress.h> class QuaZip;
namespace MMCZip namespace MMCZip
{ {
/**
* Compress a subdirectory.
* \param parentZip Opened zip containing the parent directory.
* \param dir The full path to the directory to pack.
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
* \param recursive Whether to pack sub-directories as well or only files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/**
* Compress a whole directory.
* \param fileCompressed The name of the archive.
* \param dir The directory to compress.
* \param recursive Whether to pack the subdirectories as well, or just regular files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/// filter function for @mergeZipFiles - passthrough
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
/// filter function for @mergeZipFiles - ignores METAINF
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
/** /**
* Merge two zip files, using a filter function * Merge two zip files, using a filter function
*/ */
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
const JlCompress::FilterFunction filter = nullptr);
/** /**
* take a source jar, add mods to it, resulting in target jar * take a source jar, add mods to it, resulting in target jar
*/ */
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods); bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if
* left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
/** /**
* Find a single file in archive by file name (not path) * Find a single file in archive by file name (not path)
* *
* \return the path prefix where the file is * \return the path prefix where the file is
*/ */
QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
/** /**
* Find a multiple files of the same name in archive by file name * Find a multiple files of the same name in archive by file name
@@ -54,18 +74,15 @@ namespace MMCZip
*/ */
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a single file to a destination
*
* \return true if it succeeds
*/
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
/** /**
* Extract a subdirectory from an archive * Extract a subdirectory from an archive
*/ */
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
} }

View File

@@ -1,10 +1,8 @@
#pragma once #pragma once
#include "BaseInstance.h" #include "BaseInstance.h"
#include "launch/LaunchTask.h"
class NullInstance: public BaseInstance class NullInstance: public BaseInstance
{ {
Q_OBJECT
public: public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir) :BaseInstance(globalSettings, settings, rootDir)
@@ -12,46 +10,73 @@ public:
setVersionBroken(true); setVersionBroken(true);
} }
virtual ~NullInstance() {}; virtual ~NullInstance() {};
void saveNow() override virtual bool setIntendedVersionId(QString) override
{ {
return false;
} }
QString getStatusbarDescription() override virtual QString currentVersionId() const override
{
return "Null";
};
virtual QString intendedVersionId() const override
{
return "Null";
};
virtual void init() override
{
};
virtual QString getStatusbarDescription() override
{ {
return tr("Unknown instance type"); return tr("Unknown instance type");
}; };
QSet< QString > traits() const override virtual bool shouldUpdate() const override
{
return false;
};
virtual QSet< QString > traits() override
{ {
return {}; return {};
}; };
QString instanceConfigFolder() const override virtual QString instanceConfigFolder() const override
{ {
return instanceRoot(); return instanceRoot();
}; };
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
{ {
return nullptr; return nullptr;
} }
shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override virtual shared_qobject_ptr< Task > createUpdateTask() override
{ {
return nullptr; return nullptr;
} }
QProcessEnvironment createEnvironment() override virtual std::shared_ptr<Task> createJarModdingTask() override
{
return nullptr;
}
virtual void setShouldUpdate(bool) override
{
};
virtual std::shared_ptr< BaseVersionList > versionList() const override
{
return nullptr;
};
virtual QProcessEnvironment createEnvironment() override
{ {
return QProcessEnvironment(); return QProcessEnvironment();
} }
QMap<QString, QString> getVariables() const override virtual QMap<QString, QString> getVariables() const override
{ {
return QMap<QString, QString>(); return QMap<QString, QString>();
} }
IPathMatcher::Ptr getLogFileMatcher() override virtual IPathMatcher::Ptr getLogFileMatcher() override
{ {
return nullptr; return nullptr;
} }
QString getLogFileRoot() override virtual QString getLogFileRoot() override
{ {
return instanceRoot(); return instanceRoot();
} }
QString typeName() const override virtual QString typeName() const override
{ {
return "Null"; return "Null";
} }
@@ -59,14 +84,6 @@ public:
{ {
return false; return false;
} }
bool canEdit() const override
{
return false;
}
bool canLaunch() const override
{
return false;
}
QStringList verboseDescription(AuthSessionPtr session) override QStringList verboseDescription(AuthSessionPtr session) override
{ {
QStringList out; QStringList out;

View File

@@ -1,49 +0,0 @@
#pragma once
#include "multimc_logic_export.h"
enum class ProblemSeverity
{
None,
Warning,
Error
};
struct PatchProblem
{
ProblemSeverity m_severity;
QString m_description;
};
class MULTIMC_LOGIC_EXPORT ProblemProvider
{
public:
virtual ~ProblemProvider() {};
virtual const QList<PatchProblem> getProblems() const = 0;
virtual ProblemSeverity getProblemSeverity() const = 0;
};
class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
{
public:
const QList<PatchProblem> getProblems() const override
{
return m_problems;
}
ProblemSeverity getProblemSeverity() const override
{
return m_problemSeverity;
}
virtual void addProblem(ProblemSeverity severity, const QString &description)
{
if(severity > m_problemSeverity)
{
m_problemSeverity = severity;
}
m_problems.append({severity, description});
}
private:
QList<PatchProblem> m_problems;
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
};

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include <functional>
#include <memory> #include <memory>
#include <QObject> #include <QObject>
#include <functional>
namespace details namespace details
{ {

View File

@@ -1,6 +1,4 @@
#pragma once #pragma once
#include <QWriteLocker>
#include <QReadLocker>
template <typename K, typename V> template <typename K, typename V>
class RWStorage class RWStorage
{ {
@@ -44,7 +42,7 @@ public:
} }
void setStale(K key) void setStale(K key)
{ {
QWriteLocker l(&lock); QReadLocker l(&lock);
if(cache.contains(key)) if(cache.contains(key))
{ {
stale_entries.insert(key); stale_entries.insert(key);
@@ -54,7 +52,6 @@ public:
{ {
QWriteLocker l(&lock); QWriteLocker l(&lock);
cache.clear(); cache.clear();
stale_entries.clear();
} }
private: private:
QReadWriteLock lock; QReadWriteLock lock;

View File

@@ -75,7 +75,6 @@ void Version::parse()
{ {
m_sections.clear(); m_sections.clear();
// FIXME: this is bad. versions can contain a lot more separators...
QStringList parts = m_string.split('.'); QStringList parts = m_string.split('.');
for (const auto part : parts) for (const auto part : parts)
@@ -83,3 +82,59 @@ void Version::parse()
m_sections.append(Section(part)); m_sections.append(Section(part));
} }
} }
bool versionIsInInterval(const QString &version, const QString &interval)
{
return versionIsInInterval(Version(version), interval);
}
bool versionIsInInterval(const Version &version, const QString &interval)
{
if (interval.isEmpty() || version.toString() == interval)
{
return true;
}
// Interval notation is used
QRegularExpression exp(
"(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
QRegularExpressionMatch match = exp.match(interval);
if (match.hasMatch())
{
const QChar start = match.captured("start").at(0);
const QChar end = match.captured("end").at(0);
const QString bottom = match.captured("bottom");
const QString top = match.captured("top");
// check if in range (bottom)
if (!bottom.isEmpty())
{
const auto bottomVersion = Version(bottom);
if ((start == '[') && !(version >= bottomVersion))
{
return false;
}
else if ((start == '(') && !(version > bottomVersion))
{
return false;
}
}
// check if in range (top)
if (!top.isEmpty())
{
const auto topVersion = Version(top);
if ((end == ']') && !(version <= topVersion))
{
return false;
}
else if ((end == ')') && !(version < topVersion))
{
return false;
}
}
return true;
}
return false;
}

View File

@@ -7,9 +7,8 @@
class QUrl; class QUrl;
class MULTIMC_LOGIC_EXPORT Version struct MULTIMC_LOGIC_EXPORT Version
{ {
public:
Version(const QString &str); Version(const QString &str);
Version() {} Version() {}
@@ -105,3 +104,7 @@ private:
void parse(); void parse();
}; };
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -60,6 +60,44 @@ private slots:
} }
void test_versionIsInInterval_data()
{
QTest::addColumn<QString>("version");
QTest::addColumn<QString>("interval");
QTest::addColumn<bool>("result");
QTest::newRow("empty, true") << "1.2.3" << "" << true;
QTest::newRow("one version, true") << "1.2.3" << "1.2.3" << true;
QTest::newRow("one version, false") << "1.2.3" << "1.2.2" << false;
QTest::newRow("one version inclusive <-> infinity, true") << "1.2.3" << "[1.2.3,)" << true;
QTest::newRow("one version exclusive <-> infinity, true") << "1.2.3" << "(1.2.2,)" << true;
QTest::newRow("one version inclusive <-> infitity, false") << "1.2.3" << "[1.2.4,)" << false;
QTest::newRow("one version exclusive <-> infinity, false") << "1.2.3" << "(1.2.3,)" << false;
QTest::newRow("infinity <-> one version inclusive, true") << "1.2.3" << "(,1.2.3]" << true;
QTest::newRow("infinity <-> one version exclusive, true") << "1.2.3" << "(,1.2.4)" << true;
QTest::newRow("infinity <-> one version inclusive, false") << "1.2.3" << "(,1.2.2]" << false;
QTest::newRow("infinity <-> one version exclusive, false") << "1.2.3" << "(,1.2.3)" << false;
QTest::newRow("inclusive <-> inclusive, true") << "1.2.3" << "[1.2.2,1.2.3]" << true;
QTest::newRow("inclusive <-> exclusive, true") << "1.2.3" << "[1.2.3,1.2.4)" << true;
QTest::newRow("exclusive <-> inclusive, true") << "1.2.3" << "(1.2.2,1.2.3]" << true;
QTest::newRow("exclusive <-> exclusive, true") << "1.2.3" << "(1.2.2,1.2.4)" << true;
QTest::newRow("inclusive <-> inclusive, false") << "1.2.3" << "[1.0.0,1.2.2]" << false;
QTest::newRow("inclusive <-> exclusive, false") << "1.2.3" << "[1.0.0,1.2.3)" << false;
QTest::newRow("exclusive <-> inclusive, false") << "1.2.3" << "(1.2.3,2.0.0]" << false;
QTest::newRow("exclusive <-> exclusive, false") << "1.2.3" << "(1.0.0,1.2.3)" << false;
}
void test_versionIsInInterval()
{
QFETCH(QString, version);
QFETCH(QString, interval);
QFETCH(bool, result);
QCOMPARE(versionIsInInterval(version, interval), result);
}
void test_versionCompare_data() void test_versionCompare_data()
{ {
setupVersions(); setupVersions();

View File

@@ -1,20 +0,0 @@
#pragma once
#include <QString>
#include <QFileSystemWatcher>
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& directory)
: m_watcher(watcher), m_directory(directory)
{
m_watcher->removePath(m_directory);
}
~WatchLock()
{
m_watcher->addPath(m_directory);
}
QFileSystemWatcher * m_watcher;
QString m_directory;
};

View File

@@ -22,5 +22,4 @@ public:
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0; virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0; virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0; virtual void installIcons(const QStringList &iconFiles) = 0;
virtual void installIcon(const QString &file, const QString &name) = 0;
}; };

View File

@@ -1,62 +0,0 @@
#include "IconUtils.h"
#include "FileSystem.h"
#include <QDirIterator>
#include <array>
namespace {
std::array<const char *, 6> validIconExtensions = {{
"svg",
"png",
"ico",
"gif",
"jpg",
"jpeg"
}};
}
namespace IconUtils{
QString findBestIconIn(const QString &folder, const QString & iconKey) {
int best_found = validIconExtensions.size();
QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
it.next();
auto fileInfo = it.fileInfo();
if(fileInfo.completeBaseName() != iconKey)
continue;
auto extension = fileInfo.suffix();
for(int i = 0; i < best_found; i++) {
if(extension == validIconExtensions[i]) {
best_found = i;
qDebug() << i << " : " << fileInfo.fileName();
best_filename = fileInfo.fileName();
}
}
}
return FS::PathCombine(folder, best_filename);
}
QString getIconFilter() {
QString out;
QTextStream stream(&out);
stream << '(';
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
if(i > 0) {
stream << " ";
}
stream << "*." << validIconExtensions[i];
}
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
stream << ')';
return out;
}
}

View File

@@ -1,14 +0,0 @@
#pragma once
#include <QString>
#include "multimc_logic_export.h"
namespace IconUtils {
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey);
// Get icon file type filter for file browser dialogs
MULTIMC_LOGIC_EXPORT QString getIconFilter();
}

View File

@@ -1,5 +1,4 @@
#include "JavaChecker.h" #include "JavaChecker.h"
#include "JavaUtils.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <Commandline.h> #include <Commandline.h>
#include <QFile> #include <QFile>
@@ -8,15 +7,13 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include "Env.h"
JavaChecker::JavaChecker(QObject *parent) : QObject(parent) JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
{ {
} }
void JavaChecker::performCheck() void JavaChecker::performCheck()
{ {
QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
QStringList args; QStringList args;
@@ -43,7 +40,6 @@ void JavaChecker::performCheck()
process->setArguments(args); process->setArguments(args);
process->setProgram(m_path); process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels); process->setProcessChannelMode(QProcess::SeparateChannels);
process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");; qDebug() << "Running java checker: " + m_path + args.join(" ");;
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
@@ -75,8 +71,8 @@ void JavaChecker::stderrReady()
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
{ {
killTimer.stop(); killTimer.stop();
QProcessPtr _process = process; QProcessPtr _process;
process.reset(); _process.swap(process);
JavaCheckResult result; JavaCheckResult result;
{ {

View File

@@ -3,8 +3,6 @@
#include <QTimer> #include <QTimer>
#include <memory> #include <memory>
#include "QObjectPtr.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "JavaVersion.h" #include "JavaVersion.h"
@@ -29,8 +27,8 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
} validity = Validity::Errored; } validity = Validity::Errored;
}; };
typedef shared_qobject_ptr<QProcess> QProcessPtr; typedef std::shared_ptr<QProcess> QProcessPtr;
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr; typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{ {
Q_OBJECT Q_OBJECT

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,19 +22,20 @@ void JavaCheckerJob::partFinished(JavaCheckResult result)
num_finished++; num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
<< javacheckers.size(); << javacheckers.size();
setProgress(num_finished, javacheckers.size()); emit progress(num_finished, javacheckers.size());
javaresults.replace(result.id, result); javaresults.replace(result.id, result);
if (num_finished == javacheckers.size()) if (num_finished == javacheckers.size())
{ {
emitSucceeded(); emit finished(javaresults);
} }
} }
void JavaCheckerJob::executeTask() void JavaCheckerJob::executeTask()
{ {
qDebug() << m_job_name.toLocal8Bit() << " started."; qDebug() << m_job_name.toLocal8Bit() << " started.";
m_running = true;
for (auto iter : javacheckers) for (auto iter : javacheckers)
{ {
javaresults.append(JavaCheckResult()); javaresults.append(JavaCheckResult());

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,32 +20,52 @@
#include "tasks/Task.h" #include "tasks/Task.h"
class JavaCheckerJob; class JavaCheckerJob;
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr; typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task class JavaCheckerJob : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
virtual ~JavaCheckerJob() {};
bool addJavaCheckerAction(JavaCheckerPtr base) bool addJavaCheckerAction(JavaCheckerPtr base)
{ {
javacheckers.append(base); javacheckers.append(base);
total_progress++;
// if this is already running, the action needs to be started right away! // if this is already running, the action needs to be started right away!
if (isRunning()) if (isRunning())
{ {
setProgress(num_finished, javacheckers.size()); setProgress(current_progress, total_progress);
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
base->performCheck(); base->performCheck();
} }
return true; return true;
} }
QList<JavaCheckResult> getResults()
JavaCheckerPtr operator[](int index)
{ {
return javaresults; return javacheckers[index];
} }
;
JavaCheckerPtr first()
{
if (javacheckers.size())
return javacheckers[0];
return JavaCheckerPtr();
}
int size() const
{
return javacheckers.size();
}
virtual bool isRunning() const override
{
return m_running;
}
signals:
void started();
void finished(QList<JavaCheckResult>);
private slots: private slots:
void partFinished(JavaCheckResult result); void partFinished(JavaCheckResult result);
@@ -57,5 +77,8 @@ private:
QString m_job_name; QString m_job_name;
QList<JavaCheckerPtr> javacheckers; QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults; QList<JavaCheckResult> javaresults;
qint64 current_progress = 0;
qint64 total_progress = 0;
int num_finished = 0; int num_finished = 0;
bool m_running = false;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -29,29 +29,9 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
{ {
} }
shared_qobject_ptr<Task> JavaInstallList::getLoadTask() Task *JavaInstallList::getLoadTask()
{ {
load(); return new JavaListLoadTask(this);
return getCurrentTask();
}
shared_qobject_ptr<Task> JavaInstallList::getCurrentTask()
{
if(m_status == Status::InProgress)
{
return m_loadTask;
}
return nullptr;
}
void JavaInstallList::load()
{
if(m_status != Status::InProgress)
{
m_status = Status::InProgress;
m_loadTask = new JavaListLoadTask(this);
m_loadTask->start();
}
} }
const BaseVersionPtr JavaInstallList::at(int i) const const BaseVersionPtr JavaInstallList::at(int i) const
@@ -61,7 +41,7 @@ const BaseVersionPtr JavaInstallList::at(int i) const
bool JavaInstallList::isLoaded() bool JavaInstallList::isLoaded()
{ {
return m_status == JavaInstallList::Status::Done; return m_loaded;
} }
int JavaInstallList::count() const int JavaInstallList::count() const
@@ -107,6 +87,7 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{ {
beginResetModel(); beginResetModel();
m_vlist = versions; m_vlist = versions;
m_loaded = true;
sortVersions(); sortVersions();
if(m_vlist.size()) if(m_vlist.size())
{ {
@@ -114,8 +95,6 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
best->recommended = true; best->recommended = true;
} }
endResetModel(); endResetModel();
m_status = Status::Done;
m_loadTask.reset();
} }
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
@@ -149,8 +128,8 @@ void JavaListLoadTask::executeTask()
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = ju.FindJavaPaths();
m_job = new JavaCheckerJob("Java detection"); m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
@@ -170,10 +149,9 @@ void JavaListLoadTask::executeTask()
m_job->start(); m_job->start();
} }
void JavaListLoadTask::javaCheckerFinished() void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{ {
QList<JavaInstallPtr> candidates; QList<JavaInstallPtr> candidates;
auto results = m_job->getResults();
qDebug() << "Found the following valid Java installations:"; qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results) for(JavaCheckResult result : results)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@
#include "JavaCheckerJob.h" #include "JavaCheckerJob.h"
#include "JavaInstall.h" #include "JavaInstall.h"
#include "QObjectPtr.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class JavaListLoadTask; class JavaListLoadTask;
@@ -33,35 +31,25 @@ class JavaListLoadTask;
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
{ {
Q_OBJECT Q_OBJECT
enum class Status
{
NotDone,
InProgress,
Done
};
public: public:
explicit JavaInstallList(QObject *parent = 0); explicit JavaInstallList(QObject *parent = 0);
shared_qobject_ptr<Task> getLoadTask() override; virtual Task *getLoadTask() override;
bool isLoaded() override; virtual bool isLoaded() override;
const BaseVersionPtr at(int i) const override; virtual const BaseVersionPtr at(int i) const override;
int count() const override; virtual int count() const override;
void sortVersions() override; virtual void sortVersions() override;
QVariant data(const QModelIndex &index, int role) const override; virtual QVariant data(const QModelIndex &index, int role) const override;
RoleList providesRoles() const override; virtual RoleList providesRoles() const override;
public slots: public slots:
void updateListData(QList<BaseVersionPtr> versions) override; virtual void updateListData(QList<BaseVersionPtr> versions) override;
protected: protected:
void load();
shared_qobject_ptr<Task> getCurrentTask();
protected:
Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
QList<BaseVersionPtr> m_vlist; QList<BaseVersionPtr> m_vlist;
bool m_loaded = false;
}; };
class JavaListLoadTask : public Task class JavaListLoadTask : public Task
@@ -70,14 +58,14 @@ class JavaListLoadTask : public Task
public: public:
explicit JavaListLoadTask(JavaInstallList *vlist); explicit JavaListLoadTask(JavaInstallList *vlist);
virtual ~JavaListLoadTask(); ~JavaListLoadTask();
void executeTask() override; virtual void executeTask();
public slots: public slots:
void javaCheckerFinished(); void javaCheckerFinished(QList<JavaCheckResult> results);
protected: protected:
shared_qobject_ptr<JavaCheckerJob> m_job; std::shared_ptr<JavaCheckerJob> m_job;
JavaInstallList *m_list; JavaInstallList *m_list;
JavaInstall *m_currentRecommended; JavaInstall *m_currentRecommended;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,107 +22,14 @@
#include <QDebug> #include <QDebug>
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "FileSystem.h" #include "FileSystem.h"
#define IBUS "@im=ibus"
JavaUtils::JavaUtils() JavaUtils::JavaUtils()
{ {
} }
#ifdef Q_OS_LINUX
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
{
QDir mmcBin(QCoreApplication::applicationDirPath());
auto items = LD_LIBRARY_PATH.split(':');
QStringList final;
for(auto & item: items)
{
QDir test(item);
if(test == mmcBin)
{
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
continue;
}
final.append(item);
}
return final.join(':');
}
#endif
QProcessEnvironment CleanEnviroment()
{
// prepare the process environment
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
QProcessEnvironment env;
QStringList ignored =
{
"JAVA_ARGS",
"CLASSPATH",
"CONFIGPATH",
"JAVA_HOME",
"JRE_HOME",
"_JAVA_OPTIONS",
"JAVA_OPTIONS",
"JAVA_TOOL_OPTIONS"
};
for(auto key: rawenv.keys())
{
auto value = rawenv.value(key);
// filter out dangerous java crap
if(ignored.contains(key))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// filter MultiMC-related things
if(key.startsWith("QT_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
#ifdef Q_OS_LINUX
// Do not pass LD_* variables to java. They were intended for MultiMC
if(key.startsWith("LD_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (key == "XMODIFIERS" && value.contains(IBUS))
{
QString save = value;
value.replace(IBUS, "");
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
}
if(key == "GAME_PRELOAD")
{
env.insert("LD_PRELOAD", value);
continue;
}
if(key == "GAME_LIBRARY_PATH")
{
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
continue;
}
#endif
// qDebug() << "Env: " << key << value;
env.insert(key, value);
}
#ifdef Q_OS_LINUX
// HACK: Workaround for QTBUG42500
if(!env.contains("LD_LIBRARY_PATH"))
{
env.insert("LD_LIBRARY_PATH", "");
}
#endif
return env;
}
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
{ {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
@@ -187,7 +94,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
// Iterate until RegEnumKeyEx fails // Iterate until RegEnumKeyEx fails
if (numSubKeys > 0) if (numSubKeys > 0)
{ {
for (DWORD i = 0; i < numSubKeys; i++) for (int i = 0; i < numSubKeys; i++)
{ {
subKeyNameSize = 255; subKeyNameSize = 255;
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
#include <QStringList> #include <QStringList>
#include "JavaCheckerJob.h"
#include "JavaChecker.h" #include "JavaChecker.h"
#include "JavaInstallList.h" #include "JavaInstallList.h"
@@ -26,8 +27,6 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
QProcessEnvironment CleanEnviroment();
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{ {
Q_OBJECT Q_OBJECT

View File

@@ -60,18 +60,9 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
{ {
if(m_parseable && rhs.m_parseable) if(m_parseable && rhs.m_parseable)
{ {
auto major = m_major; if(m_major < rhs.m_major)
auto rmajor = rhs.m_major;
// HACK: discourage using java 9
if(major > 8)
major = -major;
if(rmajor > 8)
rmajor = -rmajor;
if(major < rmajor)
return true; return true;
if(major > rmajor) if(m_major > rhs.m_major)
return false; return false;
if(m_minor < rhs.m_minor) if(m_minor < rhs.m_minor)
return true; return true;

View File

@@ -3,14 +3,6 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include <QString> #include <QString>
// NOTE: apparently the GNU C library pollutes the global namespace with these... undef them.
#ifdef major
#undef major
#endif
#ifdef minor
#undef minor
#endif
class MULTIMC_LOGIC_EXPORT JavaVersion class MULTIMC_LOGIC_EXPORT JavaVersion
{ {
friend class JavaVersionTest; friend class JavaVersionTest;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -60,7 +60,7 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check! // if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0) if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
{ {
m_JavaChecker = new JavaChecker(); m_JavaChecker = std::make_shared<JavaChecker>();
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC); emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath; m_JavaChecker->m_path = realJavaPath;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -31,8 +31,8 @@ public: /* methods */
}; };
virtual ~LaunchStep() {}; virtual ~LaunchStep() {};
private: /* methods */ protected: /* methods */
void bind(LaunchTask *parent); virtual void bind(LaunchTask *parent);
signals: signals:
void logLines(QStringList lines, MessageLevel::Enum level); void logLines(QStringList lines, MessageLevel::Enum level);

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -33,9 +33,9 @@ void LaunchTask::init()
m_instance->setRunning(true); m_instance->setRunning(true);
} }
shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst) std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
{ {
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst)); std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init(); proc->init();
return proc; return proc;
} }
@@ -44,12 +44,12 @@ LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{ {
} }
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step) void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
{ {
m_steps.append(step); m_steps.append(step);
} }
void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step) void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
{ {
m_steps.prepend(step); m_steps.prepend(step);
} }
@@ -83,7 +83,7 @@ void LaunchTask::onStepFinished()
} }
auto step = m_steps[currentStep]; auto step = m_steps[currentStep];
if(step->wasSuccessful()) if(step->successful())
{ {
// end? // end?
if(currentStep == m_steps.size() - 1) if(currentStep == m_steps.size() - 1)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -45,11 +45,11 @@ public:
}; };
public: /* methods */ public: /* methods */
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst); static std::shared_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask() {}; virtual ~LaunchTask() {};
void appendStep(shared_qobject_ptr<LaunchStep> step); void appendStep(std::shared_ptr<LaunchStep> step);
void prependStep(shared_qobject_ptr<LaunchStep> step); void prependStep(std::shared_ptr<LaunchStep> step);
void setCensorFilter(QMap<QString, QString> filter); void setCensorFilter(QMap<QString, QString> filter);
InstancePtr instance() InstancePtr instance()
@@ -117,7 +117,7 @@ private: /*methods */
protected: /* data */ protected: /* data */
InstancePtr m_instance; InstancePtr m_instance;
shared_qobject_ptr<LogModel> m_logModel; shared_qobject_ptr<LogModel> m_logModel;
QList <shared_qobject_ptr<LaunchStep>> m_steps; QList <std::shared_ptr<LaunchStep>> m_steps;
QMap<QString, QString> m_censorFilter; QMap<QString, QString> m_censorFilter;
int currentStep = -1; int currentStep = -1;
State state = NotStarted; State state = NotStarted;

View File

@@ -69,11 +69,6 @@ void LogModel::suspend(bool suspend)
m_suspended = suspend; m_suspended = suspend;
} }
bool LogModel::suspended()
{
return m_suspended;
}
void LogModel::clear() void LogModel::clear()
{ {
beginResetModel(); beginResetModel();
@@ -152,16 +147,3 @@ void LogModel::setOverflowMessage(const QString& overflowMessage)
{ {
m_overflowMessage = overflowMessage; m_overflowMessage = overflowMessage;
} }
void LogModel::setLineWrap(bool state)
{
if(m_lineWrap != state)
{
m_lineWrap = state;
}
}
bool LogModel::wrapLines() const
{
return m_lineWrap;
}

View File

@@ -17,9 +17,7 @@ public:
void append(MessageLevel::Enum, QString line); void append(MessageLevel::Enum, QString line);
void clear(); void clear();
void suspend(bool suspend); void suspend(bool suspend);
bool suspended();
QString toPlainText(); QString toPlainText();
@@ -28,9 +26,6 @@ public:
void setStopOnOverflow(bool stop); void setStopOnOverflow(bool stop);
void setOverflowMessage(const QString & overflowMessage); void setOverflowMessage(const QString & overflowMessage);
void setLineWrap(bool state);
bool wrapLines() const;
enum Roles enum Roles
{ {
LevelRole = Qt::UserRole LevelRole = Qt::UserRole
@@ -53,7 +48,6 @@ private: /* data */
bool m_stopOnOverflow = false; bool m_stopOnOverflow = false;
QString m_overflowMessage = "OVERFLOW"; QString m_overflowMessage = "OVERFLOW";
bool m_suspended = false; bool m_suspended = false;
bool m_lineWrap = true;
private: private:
Q_DISABLE_COPY(LogModel) Q_DISABLE_COPY(LogModel)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ class PostLaunchCommand: public LaunchStep
Q_OBJECT Q_OBJECT
public: public:
explicit PostLaunchCommand(LaunchTask *parent); explicit PostLaunchCommand(LaunchTask *parent);
virtual ~PostLaunchCommand() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();
virtual bool canAbort() const virtual bool canAbort() const

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ class PreLaunchCommand: public LaunchStep
Q_OBJECT Q_OBJECT
public: public:
explicit PreLaunchCommand(LaunchTask *parent); explicit PreLaunchCommand(LaunchTask *parent);
virtual ~PreLaunchCommand() {};
virtual void executeTask(); virtual void executeTask();
virtual bool abort(); virtual bool abort();
virtual bool canAbort() const virtual bool canAbort() const

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ void Update::executeTask()
emitFailed(tr("Task aborted.")); emitFailed(tr("Task aborted."));
return; return;
} }
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); m_updateTask.reset(m_parent->instance()->createUpdateTask());
if(m_updateTask) if(m_updateTask)
{ {
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
@@ -42,14 +42,14 @@ void Update::proceed()
void Update::updateFinished() void Update::updateFinished()
{ {
if(m_updateTask->wasSuccessful()) if(m_updateTask->successful())
{ {
m_updateTask.reset(); m_updateTask.reset();
emitSucceeded(); emitSucceeded();
} }
else else
{ {
QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason()); QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
m_updateTask.reset(); m_updateTask.reset();
emit logLine(reason, MessageLevel::Fatal); emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason); emitFailed(reason);

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,14 +19,13 @@
#include <QObjectPtr.h> #include <QObjectPtr.h>
#include <LoggedProcess.h> #include <LoggedProcess.h>
#include <java/JavaChecker.h> #include <java/JavaChecker.h>
#include <net/Mode.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away... // FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep class Update: public LaunchStep
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; explicit Update(LaunchTask *parent):LaunchStep(parent) {};
virtual ~Update() {}; virtual ~Update() {};
void executeTask() override; void executeTask() override;
@@ -41,5 +40,4 @@ private slots:
private: private:
shared_qobject_ptr<Task> m_updateTask; shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false; bool m_aborted = false;
Net::Mode m_mode = Net::Mode::Offline;
}; };

View File

@@ -1,162 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BaseEntity.h"
#include "Json.h"
#include "net/Download.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include "Env.h"
#include "Json.h"
class ParsingValidator : public Net::Validator
{
public: /* con/des */
ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity)
{
};
virtual ~ParsingValidator()
{
};
public: /* methods */
bool init(QNetworkRequest &) override
{
return true;
}
bool write(QByteArray & data) override
{
this->data.append(data);
return true;
}
bool abort() override
{
return true;
}
bool validate(QNetworkReply &) override
{
auto fname = m_entity->localFilename();
try
{
m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname));
return true;
}
catch (const Exception &e)
{
qWarning() << "Unable to parse response:" << e.cause();
return false;
}
}
private: /* data */
QByteArray data;
Meta::BaseEntity *m_entity;
};
Meta::BaseEntity::~BaseEntity()
{
}
QUrl Meta::BaseEntity::url() const
{
return QUrl("https://meta.multimc.org/v1/").resolved(localFilename());
}
bool Meta::BaseEntity::loadLocalFile()
{
const QString fname = QDir("meta").absoluteFilePath(localFilename());
if (!QFile::exists(fname))
{
return false;
}
// TODO: check if the file has the expected checksum
try
{
parse(Json::requireObject(Json::requireDocument(fname, fname), fname));
return true;
}
catch (const Exception &e)
{
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
// just make sure it's gone and we never consider it again.
QFile::remove(fname);
return false;
}
}
void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if(!isLoaded())
{
if(loadLocalFile())
{
m_loadStatus = LoadStatus::Local;
}
}
// if we need remote update, run the update task
if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate())
{
return;
}
NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()));
auto url = this->url();
auto entry = ENV.metacache()->resolveEntry("meta", localFilename());
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
/*
* The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage.
*/
dl->addValidator(new ParsingValidator(this));
job->addNetAction(dl);
m_updateStatus = UpdateStatus::InProgress;
m_updateTask.reset(job);
QObject::connect(job, &NetJob::succeeded, [&]()
{
m_loadStatus = LoadStatus::Remote;
m_updateStatus = UpdateStatus::Succeeded;
m_updateTask.reset();
});
QObject::connect(job, &NetJob::failed, [&]()
{
m_updateStatus = UpdateStatus::Failed;
m_updateTask.reset();
});
m_updateTask->start();
}
bool Meta::BaseEntity::isLoaded() const
{
return m_loadStatus > LoadStatus::NotLoaded;
}
bool Meta::BaseEntity::shouldStartRemoteUpdate() const
{
// TODO: version-locks and offline mode?
return m_updateStatus != UpdateStatus::InProgress;
}
shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask()
{
if(m_updateStatus == UpdateStatus::InProgress)
{
return m_updateTask;
}
return nullptr;
}

View File

@@ -1,68 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QJsonObject>
#include <QObject>
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
#include "net/Mode.h"
class Task;
namespace Meta
{
class MULTIMC_LOGIC_EXPORT BaseEntity
{
public: /* types */
using Ptr = std::shared_ptr<BaseEntity>;
enum class LoadStatus
{
NotLoaded,
Local,
Remote
};
enum class UpdateStatus
{
NotDone,
InProgress,
Failed,
Succeeded
};
public:
virtual ~BaseEntity();
virtual void parse(const QJsonObject &obj) = 0;
virtual QString localFilename() const = 0;
virtual QUrl url() const;
bool isLoaded() const;
bool shouldStartRemoteUpdate() const;
void load(Net::Mode loadType);
shared_qobject_ptr<Task> getCurrentTask();
protected: /* methods */
bool loadLocalFile();
private:
LoadStatus m_loadStatus = LoadStatus::NotLoaded;
UpdateStatus m_updateStatus = UpdateStatus::NotDone;
shared_qobject_ptr<Task> m_updateTask;
};
}

View File

@@ -1,148 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Index.h"
#include "VersionList.h"
#include "JsonFormat.h"
namespace Meta
{
Index::Index(QObject *parent)
: QAbstractListModel(parent)
{
}
Index::Index(const QVector<VersionListPtr> &lists, QObject *parent)
: QAbstractListModel(parent), m_lists(lists)
{
for (int i = 0; i < m_lists.size(); ++i)
{
m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i));
connectVersionList(i, m_lists.at(i));
}
}
QVariant Index::data(const QModelIndex &index, int role) const
{
if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size())
{
return QVariant();
}
VersionListPtr list = m_lists.at(index.row());
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case 0: return list->humanReadable();
default: break;
}
case UidRole: return list->uid();
case NameRole: return list->name();
case ListPtrRole: return QVariant::fromValue(list);
}
return QVariant();
}
int Index::rowCount(const QModelIndex &parent) const
{
return m_lists.size();
}
int Index::columnCount(const QModelIndex &parent) const
{
return 1;
}
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
{
return tr("Name");
}
else
{
return QVariant();
}
}
bool Index::hasUid(const QString &uid) const
{
return m_uids.contains(uid);
}
VersionListPtr Index::get(const QString &uid)
{
VersionListPtr out = m_uids.value(uid, nullptr);
if(!out)
{
out = std::make_shared<VersionList>(uid);
m_uids[uid] = out;
}
return out;
}
VersionPtr Index::get(const QString &uid, const QString &version)
{
auto list = get(uid);
return list->getVersion(version);
}
void Index::parse(const QJsonObject& obj)
{
parseIndex(obj, this);
}
void Index::merge(const std::shared_ptr<Index> &other)
{
const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
// initial load, no need to merge
if (m_lists.isEmpty())
{
beginResetModel();
m_lists = lists;
for (int i = 0; i < lists.size(); ++i)
{
m_uids.insert(lists.at(i)->uid(), lists.at(i));
connectVersionList(i, lists.at(i));
}
endResetModel();
}
else
{
for (const VersionListPtr &list : lists)
{
if (m_uids.contains(list->uid()))
{
m_uids[list->uid()]->mergeFromIndex(list);
}
else
{
beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size());
connectVersionList(m_lists.size(), list);
m_lists.append(list);
m_uids.insert(list->uid(), list);
endInsertRows();
}
}
}
}
void Index::connectVersionList(const int row, const VersionListPtr &list)
{
connect(list.get(), &VersionList::nameChanged, this, [this, row]()
{
emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole);
});
}
}

View File

@@ -1,71 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QAbstractListModel>
#include <memory>
#include "BaseEntity.h"
#include "multimc_logic_export.h"
class Task;
namespace Meta
{
using VersionListPtr = std::shared_ptr<class VersionList>;
using VersionPtr = std::shared_ptr<class Version>;
class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity
{
Q_OBJECT
public:
explicit Index(QObject *parent = nullptr);
explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr);
enum
{
UidRole = Qt::UserRole,
NameRole,
ListPtrRole
};
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QString localFilename() const override { return "index.json"; }
// queries
VersionListPtr get(const QString &uid);
VersionPtr get(const QString &uid, const QString &version);
bool hasUid(const QString &uid) const;
QVector<VersionListPtr> lists() const { return m_lists; }
public: // for usage by parsers only
void merge(const std::shared_ptr<Index> &other);
void parse(const QJsonObject &obj) override;
private:
QVector<VersionListPtr> m_lists;
QHash<QString, VersionListPtr> m_uids;
void connectVersionList(const int row, const VersionListPtr &list);
};
}

View File

@@ -1,44 +0,0 @@
#include <QTest>
#include "TestUtil.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "Env.h"
class IndexTest : public QObject
{
Q_OBJECT
private
slots:
void test_isProvidedByEnv()
{
QVERIFY(ENV.metadataIndex());
QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex());
}
void test_hasUid_and_getList()
{
Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
QVERIFY(windex.hasUid("list1"));
QVERIFY(!windex.hasUid("asdf"));
QVERIFY(windex.get("list2") != nullptr);
QCOMPARE(windex.get("list2")->uid(), QString("list2"));
QVERIFY(windex.get("adsf") != nullptr);
}
void test_merge()
{
Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
QCOMPARE(windex.lists().size(), 3);
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")})));
QCOMPARE(windex.lists().size(), 3);
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list4"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list5")})));
QCOMPARE(windex.lists().size(), 5);
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list6")})));
QCOMPARE(windex.lists().size(), 6);
}
};
QTEST_GUILESS_MAIN(IndexTest)
#include "Index_test.moc"

View File

@@ -1,218 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "JsonFormat.h"
// FIXME: remove this from here... somehow
#include "minecraft/OneSixVersionFormat.h"
#include "Json.h"
#include "Index.h"
#include "Version.h"
#include "VersionList.h"
using namespace Json;
namespace Meta
{
MetadataVersion currentFormatVersion()
{
return MetadataVersion::InitialRelease;
}
// Index
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
{
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
QVector<VersionListPtr> lists;
lists.reserve(objects.size());
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
{
VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid"));
list->setName(ensureString(obj, "name", QString()));
return list;
});
return std::make_shared<Index>(lists);
}
// Version
static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
{
VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
version->setType(ensureString(obj, "type", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
RequireSet requires, conflicts;
parseRequires(obj, &requires, "requires");
parseRequires(obj, &conflicts, "conflicts");
version->setRequires(requires, conflicts);
return version;
}
static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
{
VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
QString("%1/%2.json").arg(version->uid(), version->version()),
obj.contains("order")));
return version;
}
// Version list / package
static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj)
{
const QString uid = requireString(obj, "uid");
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
QVector<VersionPtr> versions;
versions.reserve(versionsRaw.size());
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj)
{
auto version = parseCommonVersion(uid, vObj);
version->setProvidesRecommendations();
return version;
});
VersionListPtr list = std::make_shared<VersionList>(uid);
list->setName(ensureString(obj, "name", QString()));
list->setVersions(versions);
return list;
}
MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required)
{
if (!obj.contains("formatVersion"))
{
if(required)
{
return MetadataVersion::Invalid;
}
return MetadataVersion::InitialRelease;
}
if (!obj.value("formatVersion").isDouble())
{
return MetadataVersion::Invalid;
}
switch(obj.value("formatVersion").toInt())
{
case 0:
case 1:
return MetadataVersion::InitialRelease;
default:
return MetadataVersion::Invalid;
}
}
void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version)
{
if(version == MetadataVersion::Invalid)
{
return;
}
obj.insert("formatVersion", int(version));
}
void parseIndex(const QJsonObject &obj, Index *ptr)
{
const MetadataVersion version = parseFormatVersion(obj);
switch (version)
{
case MetadataVersion::InitialRelease:
ptr->merge(parseIndexInternal(obj));
break;
case MetadataVersion::Invalid:
throw ParseException(QObject::tr("Unknown format version!"));
}
}
void parseVersionList(const QJsonObject &obj, VersionList *ptr)
{
const MetadataVersion version = parseFormatVersion(obj);
switch (version)
{
case MetadataVersion::InitialRelease:
ptr->merge(parseVersionListInternal(obj));
break;
case MetadataVersion::Invalid:
throw ParseException(QObject::tr("Unknown format version!"));
}
}
void parseVersion(const QJsonObject &obj, Version *ptr)
{
const MetadataVersion version = parseFormatVersion(obj);
switch (version)
{
case MetadataVersion::InitialRelease:
ptr->merge(parseVersionInternal(obj));
break;
case MetadataVersion::Invalid:
throw ParseException(QObject::tr("Unknown format version!"));
}
}
/*
[
{"uid":"foo", "equals":"version"}
]
*/
void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName)
{
if(obj.contains(keyName))
{
QSet<QString> requires;
auto reqArray = requireArray(obj, keyName);
auto iter = reqArray.begin();
while(iter != reqArray.end())
{
auto reqObject = requireObject(*iter);
auto uid = requireString(reqObject, "uid");
auto equals = ensureString(reqObject, "equals", QString());
auto suggests = ensureString(reqObject, "suggests", QString());
ptr->insert({uid, equals, suggests});
iter++;
}
}
}
void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName)
{
if(!ptr || ptr->empty())
{
return;
}
QJsonArray arrOut;
for(auto &iter: *ptr)
{
QJsonObject reqOut;
reqOut.insert("uid", iter.uid);
if(!iter.equalsVersion.isEmpty())
{
reqOut.insert("equals", iter.equalsVersion);
}
if(!iter.suggests.isEmpty())
{
reqOut.insert("suggests", iter.suggests);
}
arrOut.append(reqOut);
}
obj.insert(keyName, arrOut);
}
}

View File

@@ -1,83 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QJsonObject>
#include <memory>
#include "Exception.h"
#include "meta/BaseEntity.h"
#include <set>
namespace Meta
{
class Index;
class Version;
class VersionList;
enum class MetadataVersion
{
Invalid = -1,
InitialRelease = 1
};
class ParseException : public Exception
{
public:
using Exception::Exception;
};
struct Require
{
bool operator==(const Require & rhs) const
{
return uid == rhs.uid;
}
bool operator<(const Require & rhs) const
{
return uid < rhs.uid;
}
bool deepEquals(const Require & rhs) const
{
return uid == rhs.uid
&& equalsVersion == rhs.equalsVersion
&& suggests == rhs.suggests;
}
QString uid;
QString equalsVersion;
QString suggests;
};
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(key.uid, seed);
}
using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
void parseVersion(const QJsonObject &obj, Version *ptr);
void parseVersionList(const QJsonObject &obj, VersionList *ptr);
MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required = true);
void serializeFormatVersion(QJsonObject &obj, MetadataVersion version);
// FIXME: this has a different shape than the others...FIX IT!?
void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires");
void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires");
MetadataVersion currentFormatVersion();
}
Q_DECLARE_METATYPE(std::set<Meta::Require>)

View File

@@ -1,140 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Version.h"
#include <QDateTime>
#include "JsonFormat.h"
#include "minecraft/ComponentList.h"
Meta::Version::Version(const QString &uid, const QString &version)
: BaseVersion(), m_uid(uid), m_version(version)
{
}
Meta::Version::~Version()
{
}
QString Meta::Version::descriptor()
{
return m_version;
}
QString Meta::Version::name()
{
if(m_data)
return m_data->name;
return m_uid;
}
QString Meta::Version::typeString() const
{
return m_type;
}
QDateTime Meta::Version::time() const
{
return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC);
}
void Meta::Version::parse(const QJsonObject& obj)
{
parseVersion(obj, this);
}
void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
{
if(other->m_providesRecommendations)
{
if(m_recommended != other->m_recommended)
{
setRecommended(other->m_recommended);
}
}
if (m_type != other->m_type)
{
setType(other->m_type);
}
if (m_time != other->m_time)
{
setTime(other->m_time);
}
if (m_requires != other->m_requires)
{
m_requires = other->m_requires;
}
if (m_conflicts != other->m_conflicts)
{
m_conflicts = other->m_conflicts;
}
if(m_volatile != other->m_volatile)
{
setVolatile(other->m_volatile);
}
}
void Meta::Version::merge(const VersionPtr &other)
{
mergeFromList(other);
if(other->m_data)
{
setData(other->m_data);
}
}
QString Meta::Version::localFilename() const
{
return m_uid + '/' + m_version + ".json";
}
void Meta::Version::setType(const QString &type)
{
m_type = type;
emit typeChanged();
}
void Meta::Version::setTime(const qint64 time)
{
m_time = time;
emit timeChanged();
}
void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
{
m_requires = requires;
m_conflicts = conflicts;
emit requiresChanged();
}
void Meta::Version::setVolatile(bool volatile_)
{
m_volatile = volatile_;
}
void Meta::Version::setData(const VersionFilePtr &data)
{
m_data = data;
}
void Meta::Version::setProvidesRecommendations()
{
m_providesRecommendations = true;
}
void Meta::Version::setRecommended(bool recommended)
{
m_recommended = recommended;
}

View File

@@ -1,118 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "BaseVersion.h"
#include <QJsonObject>
#include <QStringList>
#include <QVector>
#include <memory>
#include "minecraft/VersionFile.h"
#include "BaseEntity.h"
#include "multimc_logic_export.h"
#include "JsonFormat.h"
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity
{
Q_OBJECT
public: /* con/des */
explicit Version(const QString &uid, const QString &version);
virtual ~Version();
QString descriptor() override;
QString name() override;
QString typeString() const override;
QString uid() const
{
return m_uid;
}
QString version() const
{
return m_version;
}
QString type() const
{
return m_type;
}
QDateTime time() const;
qint64 rawTime() const
{
return m_time;
}
const Meta::RequireSet &requires() const
{
return m_requires;
}
VersionFilePtr data() const
{
return m_data;
}
bool isRecommended() const
{
return m_recommended;
}
bool isLoaded() const
{
return m_data != nullptr;
}
void merge(const VersionPtr &other);
void mergeFromList(const VersionPtr &other);
void parse(const QJsonObject &obj) override;
QString localFilename() const override;
public: // for usage by format parsers only
void setType(const QString &type);
void setTime(const qint64 time);
void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
void setVolatile(bool volatile_);
void setRecommended(bool recommended);
void setProvidesRecommendations();
void setData(const VersionFilePtr &data);
signals:
void typeChanged();
void timeChanged();
void requiresChanged();
private:
bool m_providesRecommendations = false;
bool m_recommended = false;
QString m_name;
QString m_uid;
QString m_version;
QString m_type;
qint64 m_time = 0;
Meta::RequireSet m_requires;
Meta::RequireSet m_conflicts;
bool m_volatile = false;
VersionFilePtr m_data;
};
}
Q_DECLARE_METATYPE(Meta::VersionPtr)

View File

@@ -1,245 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VersionList.h"
#include <QDateTime>
#include "Version.h"
#include "JsonFormat.h"
#include "Version.h"
namespace Meta
{
VersionList::VersionList(const QString &uid, QObject *parent)
: BaseVersionList(parent), m_uid(uid)
{
setObjectName("Version list: " + uid);
}
shared_qobject_ptr<Task> VersionList::getLoadTask()
{
load(Net::Mode::Online);
return getCurrentTask();
}
bool VersionList::isLoaded()
{
return BaseEntity::isLoaded();
}
const BaseVersionPtr VersionList::at(int i) const
{
return m_versions.at(i);
}
int VersionList::count() const
{
return m_versions.size();
}
void VersionList::sortVersions()
{
beginResetModel();
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
{
return *a.get() < *b.get();
});
endResetModel();
}
QVariant VersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid())
{
return QVariant();
}
VersionPtr version = m_versions.at(index.row());
switch (role)
{
case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast<BaseVersion>(version));
case VersionRole:
case VersionIdRole:
return version->version();
case ParentVersionRole:
{
// FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'.
auto & reqs = version->requires();
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
{
return req.uid == "net.minecraft";
});
if (iter != reqs.end())
{
return (*iter).equalsVersion;
}
return QVariant();
}
case TypeRole: return version->type();
case UidRole: return version->uid();
case TimeRole: return version->time();
case RequiresRole: return QVariant::fromValue(version->requires());
case SortRole: return version->rawTime();
case VersionPtrRole: return QVariant::fromValue(version);
case RecommendedRole: return version->isRecommended();
// FIXME: this should be determined in whatever view/proxy is used...
// case LatestRole: return version == getLatestStable();
default: return QVariant();
}
}
BaseVersionList::RoleList VersionList::providesRoles() const
{
return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole,
TypeRole, UidRole, TimeRole, RequiresRole, SortRole,
RecommendedRole, LatestRole, VersionPtrRole};
}
QHash<int, QByteArray> VersionList::roleNames() const
{
QHash<int, QByteArray> roles = BaseVersionList::roleNames();
roles.insert(UidRole, "uid");
roles.insert(TimeRole, "time");
roles.insert(SortRole, "sort");
roles.insert(RequiresRole, "requires");
return roles;
}
QString VersionList::localFilename() const
{
return m_uid + "/index.json";
}
QString VersionList::humanReadable() const
{
return m_name.isEmpty() ? m_uid : m_name;
}
VersionPtr VersionList::getVersion(const QString &version)
{
VersionPtr out = m_lookup.value(version, nullptr);
if(!out)
{
out = std::make_shared<Version>(m_uid, version);
m_lookup[version] = out;
}
return out;
}
void VersionList::setName(const QString &name)
{
m_name = name;
emit nameChanged(name);
}
void VersionList::setVersions(const QVector<VersionPtr> &versions)
{
beginResetModel();
m_versions = versions;
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
{
return a->rawTime() > b->rawTime();
});
for (int i = 0; i < m_versions.size(); ++i)
{
m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i));
setupAddedVersion(i, m_versions.at(i));
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel();
}
void VersionList::parse(const QJsonObject& obj)
{
parseVersionList(obj, this);
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
{
if(!a)
return b;
if(!b)
return a;
if(a->type() == b->type())
{
// newer of same type wins
return (a->rawTime() > b->rawTime() ? a : b);
}
// 'release' type wins
return (a->type() == "release" ? a : b);
}
void VersionList::mergeFromIndex(const VersionListPtr &other)
{
if (m_name != other->m_name)
{
setName(other->m_name);
}
}
void VersionList::merge(const VersionListPtr &other)
{
if (m_name != other->m_name)
{
setName(other->m_name);
}
// TODO: do not reset the whole model. maybe?
beginResetModel();
m_versions.clear();
if(other->m_versions.isEmpty())
{
qWarning() << "Empty list loaded ...";
}
for (const VersionPtr &version : other->m_versions)
{
// we already have the version. merge the contents
if (m_lookup.contains(version->version()))
{
m_lookup.value(version->version())->mergeFromList(version);
}
else
{
m_lookup.insert(version->uid(), version);
}
// connect it.
setupAddedVersion(m_versions.size(), version);
m_versions.append(version);
m_recommended = getBetterVersion(m_recommended, version);
}
endResetModel();
}
void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
{
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
version->disconnect();
connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); });
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
}
BaseVersionPtr VersionList::getRecommended() const
{
return m_recommended;
}
}

View File

@@ -1,101 +0,0 @@
/* Copyright 2015-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "BaseEntity.h"
#include "BaseVersionList.h"
#include <QJsonObject>
#include <memory>
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
using VersionListPtr = std::shared_ptr<class VersionList>;
class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity
{
Q_OBJECT
Q_PROPERTY(QString uid READ uid CONSTANT)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
explicit VersionList(const QString &uid, QObject *parent = nullptr);
enum Roles
{
UidRole = Qt::UserRole + 100,
TimeRole,
RequiresRole,
VersionPtrRole
};
shared_qobject_ptr<Task> getLoadTask() override;
bool isLoaded() override;
const BaseVersionPtr at(int i) const override;
int count() const override;
void sortVersions() override;
BaseVersionPtr getRecommended() const override;
QVariant data(const QModelIndex &index, int role) const override;
RoleList providesRoles() const override;
QHash<int, QByteArray> roleNames() const override;
QString localFilename() const override;
QString uid() const
{
return m_uid;
}
QString name() const
{
return m_name;
}
QString humanReadable() const;
VersionPtr getVersion(const QString &version);
QVector<VersionPtr> versions() const
{
return m_versions;
}
public: // for usage only by parsers
void setName(const QString &name);
void setVersions(const QVector<VersionPtr> &versions);
void merge(const VersionListPtr &other);
void mergeFromIndex(const VersionListPtr &other);
void parse(const QJsonObject &obj) override;
signals:
void nameChanged(const QString &name);
protected slots:
void updateListData(QList<BaseVersionPtr>) override
{
}
private:
QVector<VersionPtr> m_versions;
QHash<QString, VersionPtr> m_lookup;
QString m_uid;
QString m_name;
VersionPtr m_recommended;
void setupAddedVersion(const int row, const VersionPtr &version);
};
}
Q_DECLARE_METATYPE(Meta::VersionListPtr)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,34 +27,6 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "net/Download.h" #include "net/Download.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "net/URLConstants.h"
namespace {
QSet<QString> collectPathsFromDir(QString dirPath)
{
QFileInfo dirInfo(dirPath);
if (!dirInfo.exists())
{
return {};
}
QSet<QString> out;
QDirIterator iter(dirPath, QDirIterator::Subdirectories);
while (iter.hasNext())
{
QString value = iter.next();
QFileInfo info(value);
if(info.isFile())
{
out.insert(value);
qDebug() << value;
}
}
return out;
}
}
namespace AssetsUtils namespace AssetsUtils
@@ -64,7 +36,7 @@ namespace AssetsUtils
* Returns true on success, with index populated * Returns true on success, with index populated
* index is undefined otherwise * index is undefined otherwise
*/ */
bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
{ {
/* /*
{ {
@@ -88,7 +60,7 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
qCritical() << "Failed to read assets index file" << path; qCritical() << "Failed to read assets index file" << path;
return false; return false;
} }
index.id = assetsId; index->id = assetsId;
// Read the file and close it. // Read the file and close it.
QByteArray jsonData = file.readAll(); QByteArray jsonData = file.readAll();
@@ -117,13 +89,7 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
QJsonValue isVirtual = root.value("virtual"); QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined()) if (!isVirtual.isUndefined())
{ {
index.isVirtual = isVirtual.toBool(false); index->isVirtual = isVirtual.toBool(false);
}
QJsonValue mapToResources = root.value("map_to_resources");
if (!mapToResources.isUndefined())
{
index.mapToResources = mapToResources.toBool(false);
} }
QJsonValue objects = root.value("objects"); QJsonValue objects = root.value("objects");
@@ -155,14 +121,13 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
} }
} }
index.objects.insert(iter.key(), object); index->objects.insert(iter.key(), object);
} }
return true; return true;
} }
// FIXME: ugly code duplication QDir reconstructAssets(QString assetsId)
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
{ {
QDir assetsDir = QDir("assets/"); QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
@@ -175,77 +140,24 @@ QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
if (!indexFile.exists()) if (!indexFile.exists())
{ {
qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
return virtualRoot; return virtualRoot;
} }
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index; AssetsIndex index;
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
{
qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!";
return virtualRoot;
}
QString targetPath; if (loadAssetsIndex && index.isVirtual)
if(index.isVirtual)
{ {
return virtualRoot; qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
}
else if(index.mapToResources)
{
return QDir(resourcesFolder);
}
return virtualRoot;
}
// FIXME: ugly code duplication
bool reconstructAssets(QString assetsId, QString resourcesFolder)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects"));
QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual"));
QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json");
QFile indexFile(indexPath);
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
if (!indexFile.exists())
{
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!";
return false;
}
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
{
qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!";
return false;
}
QString targetPath;
bool removeLeftovers = false;
if(index.isVirtual)
{
targetPath = virtualRoot.path();
removeLeftovers = true;
qDebug() << "Reconstructing virtual assets folder at" << targetPath;
}
else if(index.mapToResources)
{
targetPath = resourcesFolder;
qDebug() << "Reconstructing resources folder at" << targetPath;
}
if (!targetPath.isNull())
{
auto presentFiles = collectPathsFromDir(targetPath);
for (QString map : index.objects.keys()) for (QString map : index.objects.keys())
{ {
AssetObject asset_object = index.objects.value(map); AssetObject asset_object = index.objects.value(map);
QString target_path = FS::PathCombine(targetPath, map); QString target_path = FS::PathCombine(virtualRoot.path(), map);
QFile target(target_path); QFile target(target_path);
QString tlk = asset_object.hash.left(2); QString tlk = asset_object.hash.left(2);
@@ -254,32 +166,24 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
QFile original(original_path); QFile original(original_path);
if (!original.exists()) if (!original.exists())
continue; continue;
presentFiles.remove(target_path);
if (!target.exists()) if (!target.exists())
{ {
QFileInfo info(target_path); QFileInfo info(target_path);
QDir target_dir = info.dir(); QDir target_dir = info.dir();
// qDebug() << target_dir;
qDebug() << target_dir.path(); if (!target_dir.exists())
FS::ensureFolderPathExists(target_dir.path()); QDir("").mkpath(target_dir.path());
bool couldCopy = original.copy(target_path); bool couldCopy = original.copy(target_path);
qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); qDebug() << " Copying" << original_path << "to" << target_path
<< QString::number(couldCopy); // << original.errorString();
} }
} }
// TODO: Write last used time to virtualRoot/.lastused // TODO: Write last used time to virtualRoot/.lastused
if(removeLeftovers)
{
for(auto & file: presentFiles)
{
qDebug() << "Would remove" << file;
} }
}
} return virtualRoot;
return true;
} }
} }
@@ -308,7 +212,7 @@ QString AssetObject::getLocalPath()
QUrl AssetObject::getUrl() QUrl AssetObject::getUrl()
{ {
return URLConstants::RESOURCE_BASE + getRelPath(); return QUrl("http://resources.download.minecraft.net/" + getRelPath());
} }
QString AssetObject::getRelPath() QString AssetObject::getRelPath()

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2019 MultiMC Contributors /* Copyright 2013-2017 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -38,16 +38,11 @@ struct AssetsIndex
QString id; QString id;
QMap<QString, AssetObject> objects; QMap<QString, AssetObject> objects;
bool isVirtual = false; bool isVirtual = false;
bool mapToResources = false;
}; };
/// FIXME: this is absolutely horrendous. REDO!!!!
namespace AssetsUtils namespace AssetsUtils
{ {
bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder);
/// Reconstruct a virtual assets folder for the given assets ID and return the folder /// Reconstruct a virtual assets folder for the given assets ID and return the folder
bool reconstructAssets(QString assetsId, QString resourcesFolder); QDir reconstructAssets(QString assetsId);
} }

View File

@@ -1,439 +0,0 @@
#include <meta/VersionList.h>
#include <meta/Index.h>
#include <Env.h>
#include "Component.h"
#include "meta/Version.h"
#include "VersionFile.h"
#include "minecraft/ComponentList.h"
#include <FileSystem.h>
#include <QSaveFile>
#include "OneSixVersionFormat.h"
#include <assert.h>
Component::Component(ComponentList * parent, const QString& uid)
{
assert(parent);
m_parent = parent;
m_uid = uid;
}
Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
{
assert(parent);
m_parent = parent;
m_metaVersion = version;
m_uid = version->uid();
m_version = m_cachedVersion = version->version();
m_cachedName = version->name();
m_loaded = version->isLoaded();
}
Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
{
assert(parent);
m_parent = parent;
m_file = file;
m_uid = uid;
m_cachedVersion = m_file->version;
m_cachedName = m_file->name;
m_loaded = true;
}
std::shared_ptr<Meta::Version> Component::getMeta()
{
return m_metaVersion;
}
void Component::applyTo(LaunchProfile* profile)
{
// do not apply disabled components
if(!isEnabled())
{
return;
}
auto vfile = getVersionFile();
if(vfile)
{
vfile->applyTo(profile);
}
else
{
profile->applyProblemSeverity(getProblemSeverity());
}
}
std::shared_ptr<class VersionFile> Component::getVersionFile() const
{
if(m_metaVersion)
{
if(!m_metaVersion->isLoaded())
{
m_metaVersion->load(Net::Mode::Online);
}
return m_metaVersion->data();
}
else
{
return m_file;
}
}
std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
{
// FIXME: what if the metadata index isn't loaded yet?
if(ENV.metadataIndex()->hasUid(m_uid))
{
return ENV.metadataIndex()->get(m_uid);
}
return nullptr;
}
int Component::getOrder()
{
if(m_orderOverride)
return m_order;
auto vfile = getVersionFile();
if(vfile)
{
return vfile->order;
}
return 0;
}
void Component::setOrder(int order)
{
m_orderOverride = true;
m_order = order;
}
QString Component::getID()
{
return m_uid;
}
QString Component::getName()
{
if (!m_cachedName.isEmpty())
return m_cachedName;
return m_uid;
}
QString Component::getVersion()
{
return m_cachedVersion;
}
QString Component::getFilename()
{
return m_parent->patchFilePathForUid(m_uid);
}
QDateTime Component::getReleaseDateTime()
{
if(m_metaVersion)
{
return m_metaVersion->time();
}
auto vfile = getVersionFile();
if(vfile)
{
return vfile->releaseTime;
}
// FIXME: fake
return QDateTime::currentDateTime();
}
bool Component::isEnabled()
{
return !canBeDisabled() || !m_disabled;
}
bool Component::canBeDisabled()
{
return isRemovable() && !m_dependencyOnly;
}
bool Component::setEnabled(bool state)
{
bool intendedDisabled = !state;
if (!canBeDisabled())
{
intendedDisabled = false;
}
if(intendedDisabled != m_disabled)
{
m_disabled = intendedDisabled;
emit dataChanged();
return true;
}
return false;
}
bool Component::isCustom()
{
return m_file != nullptr;
}
bool Component::isCustomizable()
{
if(m_metaVersion)
{
if(getVersionFile())
{
return true;
}
}
return false;
}
bool Component::isRemovable()
{
return !m_important;
}
bool Component::isRevertible()
{
if (isCustom())
{
if(ENV.metadataIndex()->hasUid(m_uid))
{
return true;
}
}
return false;
}
bool Component::isMoveable()
{
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
return true;
}
bool Component::isVersionChangeable()
{
auto list = getVersionList();
if(list)
{
if(!list->isLoaded())
{
list->load(Net::Mode::Online);
}
return list->count() != 0;
}
return false;
}
void Component::setImportant(bool state)
{
if(m_important != state)
{
m_important = state;
emit dataChanged();
}
}
ProblemSeverity Component::getProblemSeverity() const
{
auto file = getVersionFile();
if(file)
{
return file->getProblemSeverity();
}
return ProblemSeverity::Error;
}
const QList<PatchProblem> Component::getProblems() const
{
auto file = getVersionFile();
if(file)
{
return file->getProblems();
}
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
}
void Component::setVersion(const QString& version)
{
if(version == m_version)
{
return;
}
m_version = version;
if(m_loaded)
{
// we are loaded and potentially have state to invalidate
if(m_file)
{
// we have a file... explicit version has been changed and there is nothing else to do.
}
else
{
// we don't have a file, therefore we are loaded with metadata
m_cachedVersion = version;
// see if the meta version is loaded
auto metaVersion = ENV.metadataIndex()->get(m_uid, version);
if(metaVersion->isLoaded())
{
// if yes, we can continue with that.
m_metaVersion = metaVersion;
}
else
{
// if not, we need loading
m_metaVersion.reset();
m_loaded = false;
}
updateCachedData();
}
}
else
{
// not loaded... assume it will be sorted out later by the update task
}
emit dataChanged();
}
bool Component::customize()
{
if(isCustom())
{
return false;
}
auto filename = getFilename();
if(!FS::ensureFilePathExists(filename))
{
return false;
}
// FIXME: get rid of this try-catch.
try
{
QSaveFile jsonFile(filename);
if(!jsonFile.open(QIODevice::WriteOnly))
{
return false;
}
auto vfile = getVersionFile();
if(!vfile)
{
return false;
}
auto document = OneSixVersionFormat::versionFileToJson(vfile);
jsonFile.write(document.toJson());
if(!jsonFile.commit())
{
return false;
}
m_file = vfile;
m_metaVersion.reset();
emit dataChanged();
}
catch (const Exception &error)
{
qWarning() << "Version could not be loaded:" << error.cause();
}
return true;
}
bool Component::revert()
{
if(!isCustom())
{
// already not custom
return true;
}
auto filename = getFilename();
bool result = true;
// just kill the file and reload
if(QFile::exists(filename))
{
result = QFile::remove(filename);
}
if(result)
{
// file gone...
m_file.reset();
// check local cache for metadata...
auto version = ENV.metadataIndex()->get(m_uid, m_version);
if(version->isLoaded())
{
m_metaVersion = version;
}
else
{
m_metaVersion.reset();
m_loaded = false;
}
emit dataChanged();
}
return result;
}
/**
* deep inspecting compare for requirement sets
* By default, only uids are compared for set operations.
* This compares all fields of the Require structs in the sets.
*/
static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
{
// NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
if(a.size() != b.size())
{
return false;
}
for(const auto & reqA :a)
{
const auto &iter2 = b.find(reqA);
if(iter2 == b.cend())
{
return false;
}
const auto & reqB = *iter2;
if(!reqA.deepEquals(reqB))
{
return false;
}
}
return true;
}
void Component::updateCachedData()
{
auto file = getVersionFile();
if(file)
{
bool changed = false;
if(m_cachedName != file->name)
{
m_cachedName = file->name;
changed = true;
}
if(m_cachedVersion != file->version)
{
m_cachedVersion = file->version;
changed = true;
}
if(m_cachedVolatile != file->m_volatile)
{
m_cachedVolatile = file->m_volatile;
changed = true;
}
if(!deepCompare(m_cachedRequires, file->requires))
{
m_cachedRequires = file->requires;
changed = true;
}
if(!deepCompare(m_cachedConflicts, file->conflicts))
{
m_cachedConflicts = file->conflicts;
changed = true;
}
if(changed)
{
emit dataChanged();
}
}
else
{
// in case we removed all the metadata
m_cachedRequires.clear();
m_cachedConflicts.clear();
emit dataChanged();
}
}

Some files were not shown because too many files have changed in this diff Show More