aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
authorRachel Powers <508861+Ryex@users.noreply.github.com>2023-02-12 02:44:39 -0700
committerRachel Powers <508861+Ryex@users.noreply.github.com>2023-03-20 14:56:32 -0700
commita1053a4c5ac8651be3b57814918e28179eb2a1f9 (patch)
tree598e2c8a5acb4bf03510d36225bff82934bf3b45 /launcher
parent9f441a9678f56c5fb5efbc415b3faff176609b9c (diff)
downloadPrismLauncher-a1053a4c5ac8651be3b57814918e28179eb2a1f9.tar.gz
PrismLauncher-a1053a4c5ac8651be3b57814918e28179eb2a1f9.tar.bz2
PrismLauncher-a1053a4c5ac8651be3b57814918e28179eb2a1f9.zip
feat: warnings when instance resources are linked
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
Diffstat (limited to 'launcher')
-rw-r--r--launcher/FileSystem.cpp4
-rw-r--r--launcher/FileSystem.h2
-rw-r--r--launcher/minecraft/World.cpp24
-rw-r--r--launcher/minecraft/World.h15
-rw-r--r--launcher/minecraft/WorldList.cpp30
-rw-r--r--launcher/minecraft/WorldList.h5
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp20
-rw-r--r--launcher/minecraft/mod/Resource.cpp20
-rw-r--r--launcher/minecraft/mod/Resource.h13
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp26
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h2
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp20
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp1
13 files changed, 179 insertions, 3 deletions
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index a9461bb0..af2ba299 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -1641,5 +1641,9 @@ bool canLink(const QString& src, const QString& dst)
return canLinkOnFS(src) && canLinkOnFS(dst);
}
+uintmax_t hardLinkCount(const QString& path)
+{
+ return fs::hard_link_count(StringUtils::toStdString(path));
+}
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index cafbd2a8..361993eb 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -528,4 +528,6 @@ bool canLinkOnFS(FilesystemType type);
*/
bool canLink(const QString& src, const QString& dst);
+uintmax_t hardLinkCount(const QString& path);
+
}
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index d310f8b9..54fb9434 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -56,6 +56,8 @@
#include <optional>
+#include "FileSystem.h"
+
using std::optional;
using std::nullopt;
@@ -567,3 +569,25 @@ bool World::operator==(const World &other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
}
+
+bool World::isSymLinkUnder(const QString& instPath) const
+{
+ if (isSymLink())
+ return true;
+
+ auto instDir = QDir(instPath);
+
+ auto relAbsPath = instDir.relativeFilePath(m_containerFile.absoluteFilePath());
+ auto relCanonPath = instDir.relativeFilePath(m_containerFile.canonicalFilePath());
+
+ return relAbsPath != relCanonPath;
+}
+
+bool World::isMoreThanOneHardLink() const
+{
+ if (m_containerFile.isDir())
+ {
+ return FS::hardLinkCount(QDir(m_containerFile.absoluteFilePath()).filePath("level.dat")) > 1;
+ }
+ return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1;
+}
diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h
index 8327253a..10328cce 100644
--- a/launcher/minecraft/World.h
+++ b/launcher/minecraft/World.h
@@ -95,6 +95,21 @@ public:
// WEAK compare operator - used for replacing worlds
bool operator==(const World &other) const;
+ [[nodiscard]] auto isSymLink() const -> bool{ return m_containerFile.isSymLink(); }
+
+ /**
+ * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance
+ *
+ * @param instPath path to an instance directory
+ * @return true
+ * @return false
+ */
+ [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const;
+
+ [[nodiscard]] bool isMoreThanOneHardLink() const;
+
+ QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); }
+
private:
void readFromZip(const QFileInfo &file);
void readFromFS(const QFileInfo &file);
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index ae29a972..1262fa1d 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -128,6 +128,10 @@ bool WorldList::isValid()
return m_dir.exists() && m_dir.isReadable();
}
+QString WorldList::instDirPath() const {
+ return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+}
+
bool WorldList::deleteWorld(int index)
{
if (index >= worlds.size() || index < 0)
@@ -173,7 +177,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const
{
- return parent.isValid()? 0 : 4;
+ return parent.isValid()? 0 : 5;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
@@ -207,6 +211,14 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
case SizeColumn:
return locale.formattedDataSize(world.bytes());
+ case InfoColumn:
+ if (world.isSymLinkUnder(instDirPath())) {
+ return tr("This world is symbolicly linked from elsewhere.");
+ }
+ if (world.isMoreThanOneHardLink()) {
+ return tr("\nThis world is hard linked elsewhere.");
+ }
+ return "";
default:
return QVariant();
}
@@ -222,7 +234,16 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
- {
+ {
+ if (column == InfoColumn) {
+ if (world.isSymLinkUnder(instDirPath())) {
+ return tr("Warning: This world is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(world.canonicalFilePath());
+ }
+ if (world.isMoreThanOneHardLink()) {
+ return tr("Warning: This world is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return world.folderName();
}
case ObjectRole:
@@ -274,6 +295,9 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
case SizeColumn:
//: World size on disk
return tr("Size");
+ case InfoColumn:
+ //: special warnings?
+ return tr("Info");
default:
return QVariant();
}
@@ -289,6 +313,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
return tr("Date and time the world was last played.");
case SizeColumn:
return tr("Size of the world on disk.");
+ case InfoColumn:
+ return tr("Information and warnings about the world.");
default:
return QVariant();
}
diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h
index 08294755..bd32dd4e 100644
--- a/launcher/minecraft/WorldList.h
+++ b/launcher/minecraft/WorldList.h
@@ -33,7 +33,8 @@ public:
NameColumn,
GameModeColumn,
LastPlayedColumn,
- SizeColumn
+ SizeColumn,
+ InfoColumn
};
enum Roles
@@ -112,6 +113,8 @@ public:
return m_dir;
}
+ QString instDirPath() const;
+
const QList<World> &allWorlds() const
{
return worlds;
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 3f31b93c..e2053f92 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -39,13 +39,17 @@
#include <FileSystem.h>
#include <QDebug>
#include <QFileSystemWatcher>
+#include <QIcon>
#include <QMimeData>
#include <QString>
+#include <QStyle>
#include <QThreadPool>
#include <QUrl>
#include <QUuid>
#include <algorithm>
+#include "Application.h"
+
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
@@ -97,8 +101,24 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
}
case Qt::ToolTipRole:
+ if (column == NAME_COLUMN) {
+ if (at(row)->isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());
+ }
+ if (at(row)->isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return m_resources[row]->internal_id();
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+ return {};
+ }
case Qt::CheckStateRole:
switch (column)
{
diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp
index 0d35d755..a0b8a4bb 100644
--- a/launcher/minecraft/mod/Resource.cpp
+++ b/launcher/minecraft/mod/Resource.cpp
@@ -1,6 +1,8 @@
#include "Resource.h"
+
#include <QRegularExpression>
+#include <QFileInfo>
#include "FileSystem.h"
@@ -152,3 +154,21 @@ bool Resource::destroy()
return FS::deletePath(m_file_info.filePath());
}
+
+bool Resource::isSymLinkUnder(const QString& instPath) const
+{
+ if (isSymLink())
+ return true;
+
+ auto instDir = QDir(instPath);
+
+ auto relAbsPath = instDir.relativeFilePath(m_file_info.absoluteFilePath());
+ auto relCanonPath = instDir.relativeFilePath(m_file_info.canonicalFilePath());
+
+ return relAbsPath != relCanonPath;
+}
+
+bool Resource::isMoreThanOneHardLink() const
+{
+ return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1;
+}
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
index 0c37f3a3..a5e9ae91 100644
--- a/launcher/minecraft/mod/Resource.h
+++ b/launcher/minecraft/mod/Resource.h
@@ -94,6 +94,19 @@ class Resource : public QObject {
// Delete all files of this resource.
bool destroy();
+ [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
+
+ /**
+ * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance
+ *
+ * @param instPath path to an instance directory
+ * @return true
+ * @return false
+ */
+ [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const;
+
+ [[nodiscard]] bool isMoreThanOneHardLink() const;
+
protected:
/* The file corresponding to this resource. */
QFileInfo m_file_info;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index f2a77c12..d1748c1c 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -2,10 +2,14 @@
#include <QCoreApplication>
#include <QDebug>
+#include <QFileInfo>
+#include <QIcon>
#include <QMimeData>
+#include <QStyle>
#include <QThreadPool>
#include <QUrl>
+#include "Application.h"
#include "FileSystem.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
@@ -417,7 +421,25 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return {};
}
case Qt::ToolTipRole:
+ if (column == NAME_COLUMN) {
+ if (at(row).isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row).fileinfo().canonicalFilePath());;
+ }
+ if (at(row).isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
+
return m_resources[row]->internal_id();
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+
+ return {};
+ }
case Qt::CheckStateRole:
switch (column) {
case ACTIVE_COLUMN:
@@ -531,3 +553,7 @@ void ResourceFolderModel::enableInteraction(bool enabled)
return (compare_result.first < 0);
return (compare_result.first > 0);
}
+
+QString ResourceFolderModel::instDirPath() const {
+ return QFileInfo(m_dir.filePath("../..")).absoluteFilePath();
+}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 3bd78870..f840b2de 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -125,6 +125,8 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
};
+ QString instDirPath() const;
+
public slots:
void enableInteraction(bool enabled);
void disableInteraction(bool disabled) { enableInteraction(!disabled); }
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index da4bd091..56584a34 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -36,6 +36,10 @@
#include "ResourcePackFolderModel.h"
+#include <QIcon>
+#include <QStyle>
+
+#include "Application.h"
#include "Version.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
@@ -78,12 +82,28 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
default:
return {};
}
+ case Qt::DecorationRole: {
+ if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+ return {};
+ }
case Qt::ToolTipRole: {
if (column == PackFormatColumn) {
//: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
}
+ if (column == NAME_COLUMN) {
+ if (at(row)->isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolicly linked from elsewhere. Editing it will also change the origonal") +
+ tr("\nCanonical Path: %1").arg(at(row)->fileinfo().canonicalFilePath());;
+ }
+ if (at(row)->isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the origonal");
+ }
+ }
return m_resources[row]->internal_id();
}
case Qt::CheckStateRole:
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index d4a395d9..b6ad159e 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -107,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl
auto head = ui->worldTreeView->header();
head->setSectionResizeMode(0, QHeaderView::Stretch);
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(4, QHeaderView::ResizeToContents);
connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
worldChanged(QModelIndex(), QModelIndex());