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
TabWidth: 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.
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?!".
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
5. Place all information below the ---- of lines.
- It makes the issue look pretty

2
.gitignore vendored
View File

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

View File

@@ -2,28 +2,29 @@
language: cpp
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:
include:
- os: linux
dist: precise
sudo: required
exclude:
# only use clang on OS X
- os: osx
compiler: gcc
env: TRAVIS_DIST=precise QT_VERSION=5.4.2
- os: linux
dist: precise
sudo: required
compiler: gcc
env: TRAVIS_DIST=precise QT_VERSION=5.6.2
- os: linux
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
# only use the qt available from homebrew
- os: osx
env: QT_VERSION=5.4.2
- os: osx
env: QT_VERSION=5.5.1
# - os: osx
# env: QT_VERSION=5.6
# Install dependencies
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.
## Build dependencies
* A C++ compiler capable of building C++11 code.
* 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.
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
* 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
* 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`)
### 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.
2. Choose a place to install Qt.
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.
- Other components are selected by default, you can untick them if you don't need them.
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.
4. Read the instructions that just popped up about a build location and choose one.
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.
- 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.
@@ -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.
## 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
- 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.
* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
* [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.
@@ -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
2. Choose a place to install Qt (C:\Qt is the default),
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,
- Other components are selected by default, you can untick them if you don't need them.
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,
- Otherwise you can skip this step.
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,
- 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.

View File

@@ -13,7 +13,7 @@ endif()
project(MultiMC)
enable_testing()
##################################### Set CMake options #####################################
######## Set CMake options ########
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -32,61 +32,22 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
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)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
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 ################################
# Find the required Qt parts
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Concurrent REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Test REQUIRED)
find_package(Qt5Xml REQUIRED)
find_package(Qt5Core)
find_package(Qt5Widgets)
find_package(Qt5Concurrent)
find_package(Qt5Network)
find_package(Qt5Test)
find_package(Qt5Xml)
# The Qt5 cmake files don't provide its install paths, so ask qmake.
include(QMakeQuery)
@@ -101,133 +62,6 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
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 ################################
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_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++)
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus)
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/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
############################### Built Artifacts ###############################
add_subdirectory(api/logic)
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(wonkoclient)

View File

@@ -1,6 +1,6 @@
#MultiMC
Copyright 2012-2019 MultiMC Contributors
Copyright 2012-2017 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
@@ -60,6 +60,26 @@
You should have received a copy of the GNU Lesser General Public License
along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
#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>
@@ -117,17 +137,6 @@
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
# Material Design Icons
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)
@@ -192,63 +201,5 @@
#ColumnResizer
Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
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.
Copyright 2011 Aurélien Gâteau <agateau@kde.org>
License: LGPL v2.1 or later (see COPYING)

View File

@@ -1,6 +1,4 @@
<p align="center">
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
</p>
![MultiMC](http://i.imgur.com/QJXbz.png)
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.
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
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 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
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
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).

View File

@@ -21,14 +21,8 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
generate_export_header(MultiMC_gui)
# 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
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.
*/
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");
* 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");
* 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");
* you may not use this file except in compliance with the License.
@@ -187,7 +187,8 @@ Qt::DropActions IconList::supportedDropActions() const
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)
return true;
@@ -260,7 +261,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
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;
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
{
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?
QIcon icon(path);
if (icon.isNull())
if (!icon.availableSizes().size())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end())
@@ -402,6 +392,20 @@ QIcon IconList::getIcon(const QString &key) const
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
{
auto iter = name_index.find(key == "default" ? "infinity" : key);
@@ -411,9 +415,4 @@ int IconList::getIconIndex(const QString &key) const
return -1;
}
QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
//#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");
* you may not use this file except in compliance with the License.
@@ -38,8 +38,8 @@ public:
virtual ~IconList() {};
QIcon getIcon(const QString &key) const;
QIcon getBigIcon(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 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -56,7 +56,6 @@ public:
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void installIcons(const QStringList &iconFiles) override;
void installIcon(const QString &file, const QString &name) override;
const MMCIcon * icon(const QString &key) const;
@@ -80,7 +79,7 @@ protected slots:
void fileChanged(const QString &path);
void SettingChanged(const Setting & setting, QVariant value);
private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
std::shared_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
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");
* 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].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");
* 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 replace(IconType new_type, QIcon icon, QString path = QString());
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");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,19 @@
#include <QFile>
#include "BaseInstaller.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/onesix/OneSixInstance.h"
BaseInstaller::BaseInstaller()
{
}
bool BaseInstaller::isApplied(MinecraftInstance *on)
bool BaseInstaller::isApplied(OneSixInstance *on)
{
return QFile::exists(filename(on->instanceRoot()));
}
bool BaseInstaller::add(MinecraftInstance *to)
bool BaseInstaller::add(OneSixInstance *to)
{
if (!patchesDir(to->instanceRoot()).exists())
{
@@ -46,7 +46,7 @@ bool BaseInstaller::add(MinecraftInstance *to)
return true;
}
bool BaseInstaller::remove(MinecraftInstance *from)
bool BaseInstaller::remove(OneSixInstance *from)
{
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");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
#include "multimc_logic_export.h"
class MinecraftInstance;
class OneSixInstance;
class QDir;
class QString;
class QObject;
@@ -32,12 +32,12 @@ class MULTIMC_LOGIC_EXPORT BaseInstaller
public:
BaseInstaller();
virtual ~BaseInstaller(){};
bool isApplied(MinecraftInstance *on);
bool isApplied(OneSixInstance *on);
virtual bool add(MinecraftInstance *to);
virtual bool remove(MinecraftInstance *from);
virtual bool add(OneSixInstance *to);
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:
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");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "minecraft/MinecraftVersionList.h"
#include "FileSystem.h"
#include "Commandline.h"
@@ -102,6 +103,13 @@ void BaseInstance::invalidate()
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)
{
Status status = currentStatus();
@@ -175,6 +183,11 @@ QString BaseInstance::instanceRoot() const
return m_rootDir;
}
InstancePtr BaseInstance::getSharedPtr()
{
return shared_from_this();
}
SettingsObjectPtr BaseInstance::settings() const
{
return m_settings;
@@ -185,7 +198,7 @@ bool BaseInstance::canLaunch() const
return (!hasVersionBroken() && !isRunning());
}
bool BaseInstance::reloadSettings()
bool BaseInstance::reload()
{
return m_settings->reload();
}
@@ -202,6 +215,31 @@ void BaseInstance::setLastLaunch(qint64 val)
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)
{
//FIXME: if no change, do not set. setting involves saving a file.
@@ -239,16 +277,31 @@ QString BaseInstance::name() 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
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
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");
* you may not use this file except in compliance with the License.
@@ -30,14 +30,13 @@
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "multimc_logic_export.h"
class QDir;
class Task;
class LaunchTask;
class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
@@ -68,7 +67,13 @@ public:
/// virtual destructor to make sure the destruction is COMPLETE
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,
@@ -87,18 +92,15 @@ public:
int64_t totalTimePlayed() const;
void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance
QString instanceType() const;
/// Path to the instance's root directory.
QString instanceRoot() const;
/// Path to the instance's game root directory.
virtual QString gameRoot() const
{
return instanceRoot();
}
QString name() const;
void setName(QString val);
@@ -111,6 +113,10 @@ public:
QString notes() const;
void setNotes(QString val);
QString group() const;
void setGroupInitial(QString val);
void setGroupPost(QString val);
QString getPreLaunchCommand();
QString getPostExitCommand();
QString getWrapperCommand();
@@ -123,8 +129,25 @@ public:
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.
virtual QSet <QString> traits() const = 0;
virtual QSet <QString> traits() = 0;
/**
* Gets the time that the instance was last launched.
@@ -134,6 +157,14 @@ public:
/// Sets the last launched time to 'val' milliseconds since epoch
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.
* This settings object stores instance-specific settings.
@@ -142,13 +173,19 @@ public:
virtual SettingsObjectPtr settings() const;
/// 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)
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)
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
@@ -214,11 +251,10 @@ public:
}
}
virtual bool canLaunch() const;
virtual bool canEdit() const = 0;
bool canLaunch() const;
virtual bool canExport() const = 0;
bool reloadSettings();
virtual bool reload();
/**
* '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
*/
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);
@@ -250,11 +290,13 @@ protected slots:
protected: /* data */
QString m_rootDir;
QString m_group;
SettingsObjectPtr m_settings;
// InstanceFlags m_flags;
bool m_isRunning = false;
shared_qobject_ptr<LaunchTask> m_launchProcess;
std::shared_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */
Status m_status = Status::Present;
@@ -263,6 +305,6 @@ private: /* data */
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_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");
* 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");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
return BaseVersionPtr();
}
BaseVersionPtr BaseVersionList::getRecommended() const
BaseVersionPtr BaseVersionList::getLatestStable() const
{
if (count() <= 0)
return BaseVersionPtr();
@@ -38,6 +38,11 @@ BaseVersionPtr BaseVersionList::getRecommended() const
return at(0);
}
BaseVersionPtr BaseVersionList::getRecommended() const
{
return getLatestStable();
}
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
@@ -88,7 +93,7 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(VersionRole, "version");
roles.insert(VersionIdRole, "versionId");
roles.insert(ParentVersionRole, "parentGameVersion");
roles.insert(ParentGameVersionRole, "parentGameVersion");
roles.insert(RecommendedRole, "recommended");
roles.insert(LatestRole, "latest");
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");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
#include "BaseVersion.h"
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
/*!
* \brief Class that each instance type's version list derives from.
@@ -45,7 +44,7 @@ public:
VersionPointerRole = Qt::UserRole,
VersionRole,
VersionIdRole,
ParentVersionRole,
ParentGameVersionRole,
RecommendedRole,
LatestRole,
TypeRole,
@@ -64,7 +63,7 @@ public:
* 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.
*/
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
//loaded.
@@ -77,22 +76,27 @@ public:
virtual int count() const = 0;
//////// List Model Functions ////////
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QHash<int, QByteArray> roleNames() const override;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list?
virtual RoleList providesRoles() const;
/*!
* \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
* one doesn't exist.
*/
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
* If the list doesn't support recommended versions, this works exactly as getLatestStable

View File

@@ -8,14 +8,21 @@ set(CORE_SOURCES
BaseInstaller.cpp
BaseVersionList.h
BaseVersionList.cpp
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
InstanceList.h
InstanceList.cpp
InstanceTask.h
InstanceTask.cpp
LoggedProcess.h
LoggedProcess.cpp
MessageLevel.cpp
MessageLevel.h
BaseInstanceProvider.h
FolderInstanceProvider.h
FolderInstanceProvider.cpp
BaseVersion.h
BaseInstance.h
BaseInstance.cpp
@@ -25,14 +32,6 @@ set(CORE_SOURCES
MMCStrings.h
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
Usable.h
@@ -43,10 +42,6 @@ set(CORE_SOURCES
Env.h
Env.cpp
# String filters
Filter.h
Filter.cpp
# JSON parsing helpers
Json.h
Json.cpp
@@ -182,11 +177,9 @@ set(NEWS_SOURCES
# Icon interface
set(ICONS_SOURCES
# Icons System and related code
# News System
icons/IIconList.h
icons/IIconList.cpp
icons/IconUtils.h
icons/IconUtils.cpp
)
# Minecraft services status checker
@@ -213,16 +206,22 @@ set(MINECRAFT_SOURCES
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp
minecraft/gameoptions/GameOptions.h
minecraft/gameoptions/GameOptions.cpp
minecraft/update/AssetUpdateTask.h
minecraft/update/AssetUpdateTask.cpp
minecraft/update/FMLLibrariesTask.cpp
minecraft/update/FMLLibrariesTask.h
minecraft/update/FoldersTask.cpp
minecraft/update/FoldersTask.h
minecraft/update/LibrariesTask.cpp
minecraft/update/LibrariesTask.h
minecraft/onesix/OneSixUpdate.h
minecraft/onesix/OneSixUpdate.cpp
minecraft/onesix/OneSixInstance.h
minecraft/onesix/OneSixInstance.cpp
minecraft/onesix/OneSixProfileStrategy.cpp
minecraft/onesix/OneSixProfileStrategy.h
minecraft/onesix/OneSixVersionFormat.cpp
minecraft/onesix/OneSixVersionFormat.h
minecraft/onesix/update/AssetUpdateTask.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.h
minecraft/launch/CreateServerResourcePacksFolder.cpp
@@ -237,66 +236,86 @@ set(MINECRAFT_SOURCES
minecraft/launch/LauncherPartLaunch.h
minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
minecraft/launch/ReconstructAssets.h
minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp
minecraft/legacy/LegacyUpdate.h
minecraft/legacy/LegacyUpdate.cpp
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyUpgradeTask.h
minecraft/legacy/LegacyUpgradeTask.cpp
minecraft/legacy/LwjglVersionList.h
minecraft/legacy/LwjglVersionList.cpp
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.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/MinecraftProfile.cpp
minecraft/MinecraftProfile.h
minecraft/MojangVersionFormat.cpp
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.h
minecraft/OneSixVersionFormat.cpp
minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp
minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp
minecraft/ProfileUtils.h
minecraft/ProfileStrategy.h
minecraft/Library.cpp
minecraft/Library.h
minecraft/MojangDownloadInfo.h
minecraft/VersionBuildError.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
minecraft/ProfilePatch.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h
minecraft/Mod.cpp
minecraft/SimpleModList.h
minecraft/SimpleModList.cpp
minecraft/ModList.h
minecraft/ModList.cpp
minecraft/World.h
minecraft/World.cpp
minecraft/WorldList.h
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
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
# 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.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.h
)
@@ -318,8 +337,8 @@ add_unit_test(Library
)
# FIXME: shares data with FileSystem test
add_unit_test(SimpleModList
SOURCES minecraft/SimpleModList_test.cpp
add_unit_test(ModList
SOURCES minecraft/ModList_test.cpp
DATA testdata
LIBS MultiMC_logic
)
@@ -342,6 +361,8 @@ set(TASKS_SOURCES
# Tasks
tasks/Task.h
tasks/Task.cpp
tasks/ThreadTask.h
tasks/ThreadTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
)
@@ -393,8 +414,6 @@ add_unit_test(JavaVersion
set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h
translations/TranslationsModel.cpp
translations/POTranslator.h
translations/POTranslator.cpp
)
set(TOOLS_SOURCES
@@ -411,44 +430,32 @@ set(TOOLS_SOURCES
tools/MCEditTool.h
)
set(META_SOURCES
# Metadata sources
meta/JsonFormat.cpp
meta/JsonFormat.h
meta/BaseEntity.cpp
meta/BaseEntity.h
meta/VersionList.cpp
meta/VersionList.h
meta/Version.cpp
meta/Version.h
meta/Index.cpp
meta/Index.h
set(WONKO_SOURCES
# Wonko
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
wonko/tasks/BaseWonkoEntityLocalLoadTask.h
wonko/format/WonkoFormatV1.cpp
wonko/format/WonkoFormatV1.h
wonko/format/WonkoFormat.cpp
wonko/format/WonkoFormat.h
wonko/BaseWonkoEntity.cpp
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
modplatform/ftb/FtbPackFetchTask.h
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
add_unit_test(WonkoIndex
SOURCES wonko/WonkoIndex_test.cpp
LIBS MultiMC_logic
)
@@ -473,29 +480,19 @@ set(LOGIC_SOURCES
${JAVA_SOURCES}
${TRANSLATIONS_SOURCES}
${TOOLS_SOURCES}
${META_SOURCES}
${WONKO_SOURCES}
${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
)
message(STATUS "FOO! ${LOGIC_SOURCES}")
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_logic)
# Link
target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
target_link_libraries(MultiMC_logic xz-embedded unpack200 systeminfo MultiMC_quazip ${NBT_NAME} ${ZLIB_LIBRARIES})
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
# Mark and export headers
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>
*
@@ -44,7 +44,7 @@ QStringList splitArgs(QString args)
}
else if (!inquotes.isNull())
{
if (cchar == '\\')
if (cchar == 0x5C)
escape = true;
else if (cchar == inquotes)
inquotes = 0;
@@ -54,7 +54,7 @@ QStringList splitArgs(QString args)
}
else
{
if (cchar == ' ')
if (cchar == 0x20)
{
if (!current.isEmpty())
{
@@ -62,7 +62,7 @@ QStringList splitArgs(QString args)
current.clear();
}
}
else if (cchar == '"' || cchar == '\'')
else if (cchar == 0x22 || cchar == 0x27)
inquotes = cchar;
else
current += cchar;

View File

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

View File

@@ -3,24 +3,23 @@
#include "BaseVersion.h"
#include "BaseVersionList.h"
#include <QDir>
#include <QCoreApplication>
#include <QNetworkProxy>
#include <QNetworkAccessManager>
#include <QDebug>
#include "tasks/Task.h"
#include "meta/Index.h"
#include "FileSystem.h"
#include "wonko/WonkoIndex.h"
#include <QDebug>
struct Env::Private
class Env::Private
{
public:
QNetworkAccessManager m_qnam;
shared_qobject_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IIconList> m_iconlist;
shared_qobject_ptr<Meta::Index> m_metadataIndex;
QString m_jarsPath;
QSet<QString> m_features;
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
shared_qobject_ptr<WonkoIndex> m_wonkoIndex;
QString m_wonkoRootUrl;
};
static Env * instance;
@@ -74,13 +73,39 @@ void Env::registerIconList(std::shared_ptr<IIconList> 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("liteloader", QDir("mods/liteloader").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("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").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();
}
@@ -167,43 +191,14 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
qDebug() << proxyDesc;
}
QString Env::getJarsPath()
QString Env::wonkoRootUrl() const
{
if(d->m_jarsPath.isEmpty())
{
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
}
return d->m_jarsPath;
return d->m_wonkoRootUrl;
}
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)
{
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;
}
#include "Env.moc"

View File

@@ -13,23 +13,18 @@ class QNetworkAccessManager;
class HttpMetaCache;
class BaseVersionList;
class BaseVersion;
namespace Meta
{
class Index;
}
class WonkoIndex;
#if defined(ENV)
#undef ENV
#endif
#define ENV (Env::getInstance())
class MULTIMC_LOGIC_EXPORT Env
{
friend class MultiMC;
private:
struct Private;
class Private;
Env();
~Env();
static void dispose();
@@ -48,17 +43,20 @@ public:
/// Updates the application proxy settings from the settings object.
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);
shared_qobject_ptr<Meta::Index> metadataIndex();
shared_qobject_ptr<WonkoIndex> wonkoIndex();
QString getJarsPath();
void setJarsPath(const QString & path);
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;
QString wonkoRootUrl() const;
void setWonkoRootUrl(const QString &url);
protected:
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 <QDir>
#include <QFile>
#include <QSaveFile>
#include <QFileInfo>
#include <QDebug>
#include <QUrl>
#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 {
@@ -78,13 +62,21 @@ QByteArray read(const QString &filename)
bool updateTimestamp(const QString& filename)
{
#ifdef Q_OS_WIN32
std::wstring filename_utf_16 = filename.toStdWString();
return (_wutime64(filename_utf_16.c_str(), nullptr) == 0);
#else
QByteArray filenameBA = QFile::encodeName(filename);
return (utime(filenameBA.data(), nullptr) == 0);
#endif
QFile file(filename);
if (!file.exists())
{
return false;
}
if (!file.open(QIODevice::ReadWrite))
{
return false;
}
const quint64 size = file.size();
file.seek(size);
file.write( QByteArray(1, '0') );
file.resize(size);
return true;
}
bool ensureFilePathExists(QString filenamepath)
@@ -171,6 +163,11 @@ bool copy::operator()(const QString &offset)
return true;
}
#if defined Q_OS_WIN32
#include <windows.h>
#include <string>
#endif
bool deletePath(QString path)
{
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())
return path2;
@@ -237,16 +234,11 @@ QString PathCombine(const QString & path1, const QString & 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);
}
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)
{
return QFileInfo(path).absolutePath();
@@ -294,7 +286,7 @@ QString NormalizePath(QString path)
}
}
QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
QString badFilenameChars = "\"\\/?<>:*|!";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
@@ -340,9 +332,21 @@ bool checkProblemticPathJava(QDir folder)
return pathfoldername.contains("!", Qt::CaseInsensitive);
}
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
// Win32 crap
#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;
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
@@ -356,7 +360,7 @@ HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
if (!SUCCEEDED(hres))
{
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
qWarning("Failed to initialize COM. Error 0x%08X", hres);
return hres;
}
}

View File

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

View File

@@ -9,13 +9,16 @@
#include "settings/SettingsObject.h"
#include "BaseVersion.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
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:
//! Entry point for tasks.
@@ -24,8 +27,16 @@ protected:
void copyAborted();
private: /* data */
SettingsObjectPtr m_globalSettings;
BaseInstanceProvider * m_target = nullptr;
InstancePtr m_origInstance;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher;
};

View File

@@ -1,31 +1,46 @@
#include "InstanceCreationTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
//FIXME: remove this
#include "minecraft/MinecraftInstance.h"
#include "minecraft/ComponentList.h"
#include "minecraft/MinecraftVersion.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;
}
void InstanceCreationTask::executeTask()
{
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"));
instanceSettings->suspendSave();
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();
emitFailed(tr("The supplied version is not a Minecraft version."));
return ;
}
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();
}

View File

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

View File

@@ -1,24 +1,24 @@
#include "InstanceImportTask.h"
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h"
#include "Env.h"
#include "MMCZip.h"
#include "NullInstance.h"
#include "settings/INISettingsObject.h"
#include "icons/IIconList.h"
#include "icons/IconUtils.h"
#include <QtConcurrentRun>
// FIXME: this does not belong here, it's Minecraft/Flame specific
#include "minecraft/MinecraftInstance.h"
#include "minecraft/ComponentList.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h"
#include "Json.h"
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
const QString &instName, const QString &instIcon, const QString &instGroup)
{
m_globalSettings = settings;
m_sourceUrl = sourceUrl;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
}
void InstanceImportTask::executeTask()
@@ -28,7 +28,7 @@ void InstanceImportTask::executeTask()
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
processZipPack();
extractAndTweak();
}
else
{
@@ -51,14 +51,12 @@ void InstanceImportTask::executeTask()
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
m_filesNetJob.reset();
extractAndTweak();
}
void InstanceImportTask::downloadFailed(QString reason)
{
emitFailed(reason);
m_filesNetJob.reset();
}
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
@@ -66,47 +64,34 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 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"));
m_stagingPath = m_target->getStagedInstancePath();
QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
// open the zip and find relevant files in it
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());
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture);
@@ -114,271 +99,27 @@ void InstanceImportTask::processZipPack()
void InstanceImportTask::extractFinished()
{
m_packZip.reset();
if (m_extractFuture.result().isEmpty())
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Failed to extract modpack"));
return;
}
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
QDirIterator it(extractDir, QDirIterator::Subdirectories);
while (it.hasNext())
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
{
auto filepath = it.next();
QFileInfo file(filepath);
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;
}
}
void InstanceImportTask::extractAborted()
{
emitFailed(tr("Instance import has been aborted."));
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Archive does not contain instance.cfg"));
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!!!
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
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.
instance.resetTimePlayed();
@@ -394,9 +135,8 @@ void InstanceImportTask::processMultiMC()
else
{
m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
if (!importIconPath.isNull() && QFile::exists(importIconPath))
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
if (QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
@@ -407,5 +147,18 @@ void InstanceImportTask::processMultiMC()
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();
}
void InstanceImportTask::extractAborted()
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance import has been aborted."));
return;
}

View File

@@ -1,34 +1,28 @@
#pragma once
#include "InstanceTask.h"
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include <QUrl>
#include <QFuture>
#include <QFutureWatcher>
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
class QuaZip;
namespace Flame
{
class FileResolvingTask;
}
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{
Q_OBJECT
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:
//! Entry point for tasks.
virtual void executeTask() override;
private:
void processZipPack();
void processMultiMC();
void processFlame();
void extractAndTweak();
private slots:
void downloadSucceeded();
@@ -38,17 +32,16 @@ private slots:
void extractAborted();
private: /* data */
SettingsObjectPtr m_globalSettings;
NetJobPtr m_filesNetJob;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl;
BaseInstanceProvider * m_target;
QString m_archivePath;
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;
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");
* you may not use this file except in compliance with the License.
@@ -14,49 +14,23 @@
*/
#include <QDir>
#include <QDirIterator>
#include <QSet>
#include <QFile>
#include <QThread>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QTimer>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QUuid>
#include <QJsonArray>
#include <QJsonDocument>
#include "InstanceList.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)
: QAbstractListModel(parent), m_globalSettings(settings)
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_instDir(instDir)
{
m_globalSettings = globalSettings;
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()
@@ -95,7 +69,6 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
{
return pdata->id();
}
case Qt::EditRole:
case Qt::DisplayRole:
{
return pdata->name();
@@ -111,7 +84,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
// HACK: see GroupView.h in gui!
case GroupRole:
{
return getInstanceGroup(pdata->id());
return pdata->group();
}
default:
break;
@@ -119,85 +92,16 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
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 f;
if (index.isValid())
{
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
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()
{
return m_groups.toList();
@@ -205,53 +109,15 @@ QStringList InstanceList::getGroups()
void InstanceList::deleteGroup(const QString& name)
{
bool removed = false;
qDebug() << "Delete group" << name;
for(auto & instance: m_instances)
{
const auto & instID = instance->id();
auto instGroupName = getInstanceGroup(instID);
auto instGroupName = instance->group();
if(instGroupName == name)
{
m_groupMap.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if(idx > 0)
{
emit dataChanged(index(idx), index(idx), {GroupRole});
instance->setGroupPost(QString());
}
}
}
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)
{
@@ -270,60 +136,50 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
return out;
}
QList< InstanceId > InstanceList::discoverInstances()
{
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()
InstanceList::InstListError InstanceList::loadList(bool complete)
{
auto existingIds = getIdMapping(m_instances);
QList<InstancePtr> newList;
for(auto & id: discoverInstances())
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
{
for(auto & id: ids)
{
if(existingIds.contains(id))
{
auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
}
else
{
InstancePtr instPtr = loadInstance(id);
InstancePtr instPtr = provider->loadInstance(id);
if(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.
if(!existingIds.isEmpty())
@@ -350,6 +206,10 @@ InstanceList::InstListError InstanceList::loadList()
for(auto & removedItem: deadList)
{
auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
}
instPtr->invalidate();
currentItem = removedItem.second;
if(back_bookmark == -1)
@@ -377,18 +237,10 @@ InstanceList::InstListError InstanceList::loadList()
{
add(newList);
}
m_dirty = false;
m_updatedProviders.clear();
return NoError;
}
void InstanceList::saveNow()
{
for(auto & item: m_instances)
{
item->saveNow();
}
}
void InstanceList::add(const QList<InstancePtr> &t)
{
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
@@ -408,7 +260,7 @@ void InstanceList::resumeWatch()
return;
}
m_watchLevel++;
if(m_watchLevel > 0 && m_dirty)
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
{
loadList();
}
@@ -421,13 +273,31 @@ void InstanceList::suspendWatch()
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)
{
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
{
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"

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");
* you may not use this file except in compliance with the License.
@@ -21,49 +21,29 @@
#include <QList>
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
class QFileSystemWatcher;
class InstanceTask;
using InstanceId = QString;
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 BaseInstance;
class QDir;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{
Q_OBJECT
public:
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
enum AdditionalRoles
{
@@ -92,75 +72,37 @@ public:
return m_instances.count();
}
InstListError loadList();
void saveNow();
InstListError loadList(bool complete = false);
/// 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;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId & 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);
void deleteGroup(const QString & name);
signals:
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:
void propertiesChanged(BaseInstance *inst);
void groupsPublished(QSet<QString>);
void providerUpdated();
void instanceDirContentsChanged(const QString &path);
private:
int getInstIndex(BaseInstance *inst) const;
void suspendWatch();
void resumeWatch();
void add(const QList<InstancePtr> &list);
void loadGroupList();
void saveGroupList();
QList<InstanceId> discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
private:
protected:
int m_watchLevel = 0;
bool m_dirty = false;
QSet<BaseInstanceProvider *> m_updatedProviders;
QString m_instDir;
QList<InstancePtr> m_instances;
QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings;
QString m_instDir;
QFileSystemWatcher * m_watcher;
QMap<InstanceId, GroupId> m_groupMap;
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
};

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);
}
catch (const JsonException &)
catch (JsonException &)
{
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");
* you may not use this file except in compliance with the License.

View File

@@ -1,29 +1,172 @@
/* 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.
/*
Copyright (C) 2010 Roberto Pompermaier
Copyright (C) 2005-2014 Sergey A. Tachenov
Parts of this file were part of QuaZIP.
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
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
QuaZIP 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 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 <quazipdir.h>
#include <quazipfile.h>
#include <JlCompress.h>
#include <quazipdir.h>
#include "MMCZip.h"
#include "FileSystem.h"
#include <QDebug>
// ours
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter)
bool copyData(QIODevice &inFile, QIODevice &outFile)
{
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());
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())
{
QString filename = modZip.getCurrentFileName();
if (filter && !filter(filename))
if (!filter(filename))
{
qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
@@ -61,7 +204,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
fileInsideMod.close();
return false;
}
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
if (!copyData(fileInsideMod, zipOutFile))
{
zipOutFile.close();
fileInsideMod.close();
@@ -74,7 +217,6 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
return true;
}
// ours
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
{
QuaZip zipOut(targetJarPath);
@@ -99,7 +241,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
continue;
if (mod.type() == Mod::MOD_ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
{
zipOut.close();
QFile::remove(targetJarPath);
@@ -109,9 +251,9 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
else if (mod.type() == Mod::MOD_SINGLEFILE)
{
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
if (!compressFile(&zipOut, filename.absoluteFilePath(),
filename.fileName()))
{
zipOut.close();
QFile::remove(targetJarPath);
@@ -122,13 +264,12 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
else if (mod.type() == Mod::MOD_FOLDER)
{
// FIXME: buggy - does not work with addedFiles
auto filename = mod.filename();
QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip);
dir.cdUp();
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();
QFile::remove(targetJarPath);
@@ -138,17 +279,9 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
qDebug() << "Adding folder " << filename.fileName() << " from "
<< 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();
QFile::remove(targetJarPath);
@@ -167,8 +300,46 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
return true;
}
// ours
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
bool MMCZip::noFilter(QString)
{
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);
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))
{
QString result = findFolderOfFileInZip(zip, what, root + fileName);
QString result = findFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
@@ -187,7 +358,6 @@ QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const
return QString();
}
// ours
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{
QuaZipDir rootDir(zip, root);
@@ -206,16 +376,95 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
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)
{
QDir directory(target);
QStringList extracted;
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
if (!zip->goToFirstFile())
{
qWarning() << "Failed to seek to first file in zip";
return QStringList();
}
do
@@ -231,25 +480,12 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
{
absFilePath += "/";
}
if (!JlCompress::extractFile(zip, "", absFilePath))
if (!extractFile(zip, "", absFilePath))
{
qWarning() << "Failed to extract file" << name << "to" << absFilePath;
JlCompress::removeFile(extracted);
removeFile(extracted);
return QStringList();
}
extracted.append(absFilePath);
qDebug() << "Extracted file" << name;
} while (zip->goToNextFile());
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
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/Mod.h"
#include "SeparatorPrefixTree.h"
#include <functional>
#include "multimc_logic_export.h"
#include <JlCompress.h>
class QuaZip;
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
*/
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
const JlCompress::FilterFunction filter = nullptr);
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
/**
* 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);
/**
* 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)
*
* \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
@@ -54,18 +74,15 @@ namespace MMCZip
*/
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
*/
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
#include "BaseInstance.h"
#include "launch/LaunchTask.h"
class NullInstance: public BaseInstance
{
Q_OBJECT
public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir)
@@ -12,46 +10,73 @@ public:
setVersionBroken(true);
}
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");
};
QSet< QString > traits() const override
virtual bool shouldUpdate() const override
{
return false;
};
virtual QSet< QString > traits() override
{
return {};
};
QString instanceConfigFolder() const override
virtual QString instanceConfigFolder() const override
{
return instanceRoot();
};
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
{
return nullptr;
}
shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
virtual shared_qobject_ptr< Task > createUpdateTask() override
{
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();
}
QMap<QString, QString> getVariables() const override
virtual QMap<QString, QString> getVariables() const override
{
return QMap<QString, QString>();
}
IPathMatcher::Ptr getLogFileMatcher() override
virtual IPathMatcher::Ptr getLogFileMatcher() override
{
return nullptr;
}
QString getLogFileRoot() override
virtual QString getLogFileRoot() override
{
return instanceRoot();
}
QString typeName() const override
virtual QString typeName() const override
{
return "Null";
}
@@ -59,14 +84,6 @@ public:
{
return false;
}
bool canEdit() const override
{
return false;
}
bool canLaunch() const override
{
return false;
}
QStringList verboseDescription(AuthSessionPtr session) override
{
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
#include <functional>
#include <memory>
#include <QObject>
#include <functional>
namespace details
{

View File

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

View File

@@ -75,7 +75,6 @@ void Version::parse()
{
m_sections.clear();
// FIXME: this is bad. versions can contain a lot more separators...
QStringList parts = m_string.split('.');
for (const auto part : parts)
@@ -83,3 +82,59 @@ void Version::parse()
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 MULTIMC_LOGIC_EXPORT Version
struct MULTIMC_LOGIC_EXPORT Version
{
public:
Version(const QString &str);
Version() {}
@@ -105,3 +104,7 @@ private:
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");
* 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()
{
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 bool iconFileExists(const QString &key) const = 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 "JavaUtils.h"
#include <FileSystem.h>
#include <Commandline.h>
#include <QFile>
@@ -8,15 +7,13 @@
#include <QCoreApplication>
#include <QDebug>
#include "Env.h"
JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
{
}
void JavaChecker::performCheck()
{
QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar");
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
QStringList args;
@@ -43,7 +40,6 @@ void JavaChecker::performCheck()
process->setArguments(args);
process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels);
process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");;
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)
{
killTimer.stop();
QProcessPtr _process = process;
process.reset();
QProcessPtr _process;
_process.swap(process);
JavaCheckResult result;
{

View File

@@ -3,8 +3,6 @@
#include <QTimer>
#include <memory>
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
#include "JavaVersion.h"
@@ -29,8 +27,8 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
} validity = Validity::Errored;
};
typedef shared_qobject_ptr<QProcess> QProcessPtr;
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
typedef std::shared_ptr<QProcess> QProcessPtr;
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{
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");
* you may not use this file except in compliance with the License.
@@ -22,19 +22,20 @@ void JavaCheckerJob::partFinished(JavaCheckResult result)
num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
<< javacheckers.size();
setProgress(num_finished, javacheckers.size());
emit progress(num_finished, javacheckers.size());
javaresults.replace(result.id, result);
if (num_finished == javacheckers.size())
{
emitSucceeded();
emit finished(javaresults);
}
}
void JavaCheckerJob::executeTask()
{
qDebug() << m_job_name.toLocal8Bit() << " started.";
m_running = true;
for (auto iter : javacheckers)
{
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");
* you may not use this file except in compliance with the License.
@@ -20,32 +20,52 @@
#include "tasks/Task.h"
class JavaCheckerJob;
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task
{
Q_OBJECT
public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
virtual ~JavaCheckerJob() {};
bool addJavaCheckerAction(JavaCheckerPtr base)
{
javacheckers.append(base);
total_progress++;
// if this is already running, the action needs to be started right away!
if (isRunning())
{
setProgress(num_finished, javacheckers.size());
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
setProgress(current_progress, total_progress);
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
base->performCheck();
}
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:
void partFinished(JavaCheckResult result);
@@ -57,5 +77,8 @@ private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
qint64 current_progress = 0;
qint64 total_progress = 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");
* 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 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();
}
return new JavaListLoadTask(this);
}
const BaseVersionPtr JavaInstallList::at(int i) const
@@ -61,7 +41,7 @@ const BaseVersionPtr JavaInstallList::at(int i) const
bool JavaInstallList::isLoaded()
{
return m_status == JavaInstallList::Status::Done;
return m_loaded;
}
int JavaInstallList::count() const
@@ -107,6 +87,7 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{
beginResetModel();
m_vlist = versions;
m_loaded = true;
sortVersions();
if(m_vlist.size())
{
@@ -114,8 +95,6 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
best->recommended = true;
}
endResetModel();
m_status = Status::Done;
m_loadTask.reset();
}
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
@@ -149,8 +128,8 @@ void JavaListLoadTask::executeTask()
JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths();
m_job = new JavaCheckerJob("Java detection");
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: ";
@@ -170,10 +149,9 @@ void JavaListLoadTask::executeTask()
m_job->start();
}
void JavaListLoadTask::javaCheckerFinished()
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{
QList<JavaInstallPtr> candidates;
auto results = m_job->getResults();
qDebug() << "Found the following valid Java installations:";
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");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,6 @@
#include "JavaCheckerJob.h"
#include "JavaInstall.h"
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
class JavaListLoadTask;
@@ -33,35 +31,25 @@ class JavaListLoadTask;
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
{
Q_OBJECT
enum class Status
{
NotDone,
InProgress,
Done
};
public:
explicit JavaInstallList(QObject *parent = 0);
shared_qobject_ptr<Task> getLoadTask() override;
bool isLoaded() override;
const BaseVersionPtr at(int i) const override;
int count() const override;
void sortVersions() override;
virtual Task *getLoadTask() override;
virtual bool isLoaded() override;
virtual const BaseVersionPtr at(int i) const override;
virtual int count() const override;
virtual void sortVersions() override;
QVariant data(const QModelIndex &index, int role) const override;
RoleList providesRoles() const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual RoleList providesRoles() const override;
public slots:
void updateListData(QList<BaseVersionPtr> versions) override;
virtual void updateListData(QList<BaseVersionPtr> versions) override;
protected:
void load();
shared_qobject_ptr<Task> getCurrentTask();
protected:
Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
QList<BaseVersionPtr> m_vlist;
bool m_loaded = false;
};
class JavaListLoadTask : public Task
@@ -70,14 +58,14 @@ class JavaListLoadTask : public Task
public:
explicit JavaListLoadTask(JavaInstallList *vlist);
virtual ~JavaListLoadTask();
~JavaListLoadTask();
void executeTask() override;
virtual void executeTask();
public slots:
void javaCheckerFinished();
void javaCheckerFinished(QList<JavaCheckResult> results);
protected:
shared_qobject_ptr<JavaCheckerJob> m_job;
std::shared_ptr<JavaCheckerJob> m_job;
JavaInstallList *m_list;
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");
* you may not use this file except in compliance with the License.
@@ -22,107 +22,14 @@
#include <QDebug>
#include "java/JavaUtils.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaInstallList.h"
#include "FileSystem.h"
#define IBUS "@im=ibus"
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 javaVersion(new JavaInstall());
@@ -187,7 +94,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
// Iterate until RegEnumKeyEx fails
if (numSubKeys > 0)
{
for (DWORD i = 0; i < numSubKeys; i++)
for (int i = 0; i < numSubKeys; i++)
{
subKeyNameSize = 255;
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");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
#include <QStringList>
#include "JavaCheckerJob.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
@@ -26,8 +27,6 @@
#include "multimc_logic_export.h"
QProcessEnvironment CleanEnviroment();
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{
Q_OBJECT

View File

@@ -60,18 +60,9 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
{
if(m_parseable && rhs.m_parseable)
{
auto major = 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)
if(m_major < rhs.m_major)
return true;
if(major > rmajor)
if(m_major > rhs.m_major)
return false;
if(m_minor < rhs.m_minor)
return true;

View File

@@ -3,14 +3,6 @@
#include "multimc_logic_export.h"
#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
{
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");
* 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 (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);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
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");
* 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");
* 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");
* you may not use this file except in compliance with the License.
@@ -31,8 +31,8 @@ public: /* methods */
};
virtual ~LaunchStep() {};
private: /* methods */
void bind(LaunchTask *parent);
protected: /* methods */
virtual void bind(LaunchTask *parent);
signals:
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>
*
@@ -33,9 +33,9 @@ void LaunchTask::init()
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();
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);
}
void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
{
m_steps.prepend(step);
}
@@ -83,7 +83,7 @@ void LaunchTask::onStepFinished()
}
auto step = m_steps[currentStep];
if(step->wasSuccessful())
if(step->successful())
{
// end?
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>
*
@@ -45,11 +45,11 @@ public:
};
public: /* methods */
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask() {};
void appendStep(shared_qobject_ptr<LaunchStep> step);
void prependStep(shared_qobject_ptr<LaunchStep> step);
void appendStep(std::shared_ptr<LaunchStep> step);
void prependStep(std::shared_ptr<LaunchStep> step);
void setCensorFilter(QMap<QString, QString> filter);
InstancePtr instance()
@@ -117,7 +117,7 @@ private: /*methods */
protected: /* data */
InstancePtr m_instance;
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;
int currentStep = -1;
State state = NotStarted;

View File

@@ -69,11 +69,6 @@ void LogModel::suspend(bool suspend)
m_suspended = suspend;
}
bool LogModel::suspended()
{
return m_suspended;
}
void LogModel::clear()
{
beginResetModel();
@@ -152,16 +147,3 @@ void LogModel::setOverflowMessage(const QString& 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 clear();
void suspend(bool suspend);
bool suspended();
QString toPlainText();
@@ -28,9 +26,6 @@ public:
void setStopOnOverflow(bool stop);
void setOverflowMessage(const QString & overflowMessage);
void setLineWrap(bool state);
bool wrapLines() const;
enum Roles
{
LevelRole = Qt::UserRole
@@ -53,7 +48,6 @@ private: /* data */
bool m_stopOnOverflow = false;
QString m_overflowMessage = "OVERFLOW";
bool m_suspended = false;
bool m_lineWrap = true;
private:
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");
* 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");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ class PostLaunchCommand: public LaunchStep
Q_OBJECT
public:
explicit PostLaunchCommand(LaunchTask *parent);
virtual ~PostLaunchCommand() {};
virtual void executeTask();
virtual bool abort();
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");
* 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");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ class PreLaunchCommand: public LaunchStep
Q_OBJECT
public:
explicit PreLaunchCommand(LaunchTask *parent);
virtual ~PreLaunchCommand() {};
virtual void executeTask();
virtual bool abort();
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");
* 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");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ void Update::executeTask()
emitFailed(tr("Task aborted."));
return;
}
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
m_updateTask.reset(m_parent->instance()->createUpdateTask());
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
@@ -42,14 +42,14 @@ void Update::proceed()
void Update::updateFinished()
{
if(m_updateTask->wasSuccessful())
if(m_updateTask->successful())
{
m_updateTask.reset();
emitSucceeded();
}
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();
emit logLine(reason, MessageLevel::Fatal);
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");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,13 @@
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
#include <net/Mode.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep
{
Q_OBJECT
public:
explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {};
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
virtual ~Update() {};
void executeTask() override;
@@ -41,5 +40,4 @@ private slots:
private:
shared_qobject_ptr<Task> m_updateTask;
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");
* you may not use this file except in compliance with the License.
@@ -27,34 +27,6 @@
#include "FileSystem.h"
#include "net/Download.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
@@ -64,7 +36,7 @@ namespace AssetsUtils
* Returns true on success, with index populated
* 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;
return false;
}
index.id = assetsId;
index->id = assetsId;
// Read the file and close it.
QByteArray jsonData = file.readAll();
@@ -117,13 +89,7 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined())
{
index.isVirtual = isVirtual.toBool(false);
}
QJsonValue mapToResources = root.value("map_to_resources");
if (!mapToResources.isUndefined())
{
index.mapToResources = mapToResources.toBool(false);
index->isVirtual = isVirtual.toBool(false);
}
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;
}
// FIXME: ugly code duplication
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
QDir reconstructAssets(QString assetsId)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
@@ -175,77 +140,24 @@ QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
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;
}
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 determine assets path!";
return virtualRoot;
}
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
QString targetPath;
if(index.isVirtual)
if (loadAssetsIndex && index.isVirtual)
{
return virtualRoot;
}
else if(index.mapToResources)
{
return QDir(resourcesFolder);
}
return virtualRoot;
}
qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
// 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())
{
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);
QString tlk = asset_object.hash.left(2);
@@ -254,32 +166,24 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
QFile original(original_path);
if (!original.exists())
continue;
presentFiles.remove(target_path);
if (!target.exists())
{
QFileInfo info(target_path);
QDir target_dir = info.dir();
qDebug() << target_dir.path();
FS::ensureFolderPathExists(target_dir.path());
// qDebug() << target_dir;
if (!target_dir.exists())
QDir("").mkpath(target_dir.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
if(removeLeftovers)
{
for(auto & file: presentFiles)
{
qDebug() << "Would remove" << file;
}
}
}
return true;
return virtualRoot;
}
}
@@ -308,7 +212,7 @@ QString AssetObject::getLocalPath()
QUrl AssetObject::getUrl()
{
return URLConstants::RESOURCE_BASE + getRelPath();
return QUrl("http://resources.download.minecraft.net/" + 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");
* you may not use this file except in compliance with the License.
@@ -38,16 +38,11 @@ struct AssetsIndex
QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
bool mapToResources = false;
};
/// FIXME: this is absolutely horrendous. REDO!!!!
namespace AssetsUtils
{
bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index);
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder);
bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
/// 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