mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a29bc5110 | ||
|
|
77dbd7ff31 | ||
|
|
7fab050adb | ||
|
|
b547045203 | ||
|
|
fcd2e1473f | ||
|
|
287dd6d805 | ||
|
|
717c2ca9cf | ||
|
|
abd1a11768 | ||
|
|
34636301ea | ||
|
|
f298ea0ae3 | ||
|
|
eee1b4b3f1 | ||
|
|
00120605fb | ||
|
|
32088b47f1 | ||
|
|
726b381ea7 | ||
|
|
071550f7fb | ||
|
|
f8a50edd0f | ||
|
|
08c7113ecb | ||
|
|
d436fe8cb3 | ||
|
|
d5183ebe6b | ||
|
|
c72326ee4b | ||
|
|
75e90f2f3f | ||
|
|
be012715f3 | ||
|
|
2b86a14c86 | ||
|
|
c96c01f40e | ||
|
|
d6edc5d6b7 | ||
|
|
4040dc12b7 | ||
|
|
d4fc7c3de1 | ||
|
|
27c4861907 | ||
|
|
8decb9df68 | ||
|
|
8365f26d83 | ||
|
|
0ca21d9a5e | ||
|
|
f60d0e5ec6 | ||
|
|
d22d77d921 | ||
|
|
7084021067 | ||
|
|
1dd2acf07f | ||
|
|
3fad475013 | ||
|
|
010a5d80d6 | ||
|
|
2bd755743c | ||
|
|
9eb1dd0ec0 | ||
|
|
e473caf14a | ||
|
|
1c87f1d8d4 | ||
|
|
bb6cde175a |
@@ -65,7 +65,6 @@ add_filter(NAME src/downloads GROUPS
|
||||
downloadlist
|
||||
downloadlistview
|
||||
downloadmanager
|
||||
downloadmanager2
|
||||
)
|
||||
|
||||
add_filter(NAME src/env GROUPS
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,212 +0,0 @@
|
||||
#ifndef MODORGANIZER_CURLDOWNLOADER_INCLUDED
|
||||
#define MODORGANIZER_CURLDOWNLOADER_INCLUDED
|
||||
|
||||
#include <ipluginrepository.h>
|
||||
|
||||
namespace dm::curl
|
||||
{
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using hr_clock = std::chrono::high_resolution_clock;
|
||||
|
||||
struct defer_t {};
|
||||
extern const defer_t defer;
|
||||
|
||||
class GlobalHandle
|
||||
{
|
||||
public:
|
||||
GlobalHandle();
|
||||
~GlobalHandle();
|
||||
|
||||
GlobalHandle(const GlobalHandle&) = delete;
|
||||
GlobalHandle& operator=(const GlobalHandle) = delete;
|
||||
};
|
||||
|
||||
|
||||
class EasyHandle
|
||||
{
|
||||
public:
|
||||
EasyHandle();
|
||||
EasyHandle(defer_t);
|
||||
~EasyHandle();
|
||||
|
||||
EasyHandle(const EasyHandle&) = delete;
|
||||
EasyHandle& operator=(const EasyHandle&) = delete;
|
||||
|
||||
bool create();
|
||||
CURL* get() const;
|
||||
|
||||
private:
|
||||
CURL* m_handle;
|
||||
};
|
||||
|
||||
|
||||
class MultiHandle
|
||||
{
|
||||
public:
|
||||
MultiHandle();
|
||||
MultiHandle(defer_t);
|
||||
~MultiHandle();
|
||||
|
||||
MultiHandle(const MultiHandle&) = delete;
|
||||
MultiHandle& operator=(const MultiHandle&) = delete;
|
||||
|
||||
bool create();
|
||||
CURLM* get() const;
|
||||
|
||||
private:
|
||||
CURLM* m_handle;
|
||||
};
|
||||
|
||||
|
||||
class FileHandle
|
||||
{
|
||||
public:
|
||||
FileHandle();
|
||||
~FileHandle();
|
||||
|
||||
FileHandle(const FileHandle&) = delete;
|
||||
FileHandle& operator=(const FileHandle&) = delete;
|
||||
|
||||
bool opened() const;
|
||||
std::size_t open(fs::path p, bool append);
|
||||
void close();
|
||||
|
||||
bool write(std::string_view sv);
|
||||
|
||||
private:
|
||||
HANDLE m_handle;
|
||||
fs::path m_path;
|
||||
|
||||
bool doOpen(bool append);
|
||||
};
|
||||
|
||||
|
||||
struct SListDeleter
|
||||
{
|
||||
void operator()(curl_slist* p)
|
||||
{
|
||||
if (p) {
|
||||
curl_slist_free_all(p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using SList = std::unique_ptr<curl_slist, SListDeleter>;
|
||||
|
||||
|
||||
class Download : public MOBase::IDownload
|
||||
{
|
||||
public:
|
||||
Download(std::string url, Info info);
|
||||
|
||||
CURL* setup(curl_off_t maxSpeed);
|
||||
|
||||
CURL* handle() const;
|
||||
States state() const override;
|
||||
Stats stats() const override;
|
||||
std::string stealBuffer();
|
||||
QByteArray buffer() const override;
|
||||
int httpCode() const override;
|
||||
std::string error() const;
|
||||
std::string debugName() const;
|
||||
|
||||
void start();
|
||||
void stop() override;
|
||||
bool finish(CURLcode code);
|
||||
|
||||
bool xfer(
|
||||
curl_off_t dltotal, curl_off_t dlnow,
|
||||
curl_off_t ultotal, curl_off_t ulnow);
|
||||
|
||||
bool header(std::string_view sv);
|
||||
bool write(std::string_view data);
|
||||
void debug(curl_infotype t, std::string_view data);
|
||||
|
||||
private:
|
||||
std::string m_url;
|
||||
Info m_info;
|
||||
EasyHandle m_handle;
|
||||
FileHandle m_out;
|
||||
SList m_headers;
|
||||
std::string m_buffer;
|
||||
States m_state;
|
||||
std::string m_error;
|
||||
|
||||
hr_clock::time_point m_lastCheck;
|
||||
std::size_t m_bytes;
|
||||
std::atomic<double> m_bytesPerSecond;
|
||||
std::atomic<double> m_progress;
|
||||
|
||||
std::size_t resumeFrom();
|
||||
bool rename();
|
||||
fs::path outputFile() const;
|
||||
|
||||
static int s_xfer(
|
||||
void* p,
|
||||
curl_off_t dltotal, curl_off_t dlnow,
|
||||
curl_off_t ultotal, curl_off_t ulnow);
|
||||
|
||||
static size_t s_header(char* data, size_t size, size_t n, void* p);
|
||||
static size_t s_write(char* data, size_t size, size_t n, void* p);
|
||||
static int s_debug(CURL* h, curl_infotype t, char* data, size_t n, void *p);
|
||||
};
|
||||
|
||||
|
||||
class Downloader : public MOBase::IDownloader
|
||||
{
|
||||
public:
|
||||
static const std::size_t NoLimit =
|
||||
std::numeric_limits<std::size_t>::max();
|
||||
|
||||
Downloader();
|
||||
~Downloader();
|
||||
|
||||
void cancel();
|
||||
void stop();
|
||||
void join();
|
||||
|
||||
void maxSpeed(std::size_t bytesPerSecond);
|
||||
|
||||
bool finished() const;
|
||||
|
||||
std::shared_ptr<MOBase::IDownload> add(
|
||||
const QUrl& url, const MOBase::IDownload::Info& info={}) override;
|
||||
|
||||
private:
|
||||
using DownloadList = std::list<std::shared_ptr<Download>>;
|
||||
|
||||
std::shared_ptr<GlobalHandle> m_global;
|
||||
MultiHandle m_handle;
|
||||
|
||||
std::vector<std::shared_ptr<Download>> m_temp;
|
||||
std::mutex m_tempMutex;
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_cancel, m_stop, m_finished;
|
||||
std::condition_variable m_cv;
|
||||
DownloadList m_list;
|
||||
std::map<CURL*, DownloadList::iterator> m_map;
|
||||
|
||||
std::atomic<std::size_t> m_maxSpeed;
|
||||
|
||||
|
||||
void run();
|
||||
void checkTemp();
|
||||
void perform();
|
||||
void poll();
|
||||
bool start(std::shared_ptr<Download> d);
|
||||
void setLimits();
|
||||
void checkCancel();
|
||||
|
||||
void checkQueue();
|
||||
bool cleanupActive();
|
||||
DownloadList::iterator removeFromActive(DownloadList::iterator itor);
|
||||
void stopOverMax(std::size_t max);
|
||||
bool addFromQueue(std::size_t max);
|
||||
curl_off_t maxSpeedPer() const;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // MODORGANIZER_CURLDOWNLOADER_INCLUDED
|
||||
@@ -226,7 +226,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
|
||||
if ((leftIndex < m_manager.numTotalDownloads())
|
||||
&& (rightIndex < m_manager.numTotalDownloads())) {
|
||||
if (left.column() == DownloadList::COL_NAME) {
|
||||
return m_manager.getFileName(left.row()).compare(m_manager.getFileName(right.row()), Qt::CaseInsensitive) < 0;
|
||||
return left.data(Qt::DisplayRole).toString().compare(right.data(Qt::DisplayRole).toString(), Qt::CaseInsensitive) < 0;
|
||||
} else if (left.column() == DownloadList::COL_MODNAME) {
|
||||
QString leftName, rightName;
|
||||
|
||||
@@ -275,7 +275,7 @@ bool DownloadList::lessThanPredicate(const QModelIndex &left, const QModelIndex
|
||||
if (leftState == rightState)
|
||||
return m_manager.getFileTime(left.row()) < m_manager.getFileTime(right.row());
|
||||
else
|
||||
return leftState > rightState;
|
||||
return leftState < rightState;
|
||||
} else if (left.column() == DownloadList::COL_SIZE) {
|
||||
return m_manager.getFileSize(left.row()) < m_manager.getFileSize(right.row());
|
||||
} else if (left.column() == DownloadList::COL_FILETIME) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,114 +0,0 @@
|
||||
#ifndef MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
|
||||
#define MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
|
||||
|
||||
#include <ipluginrepository.h>
|
||||
|
||||
class PluginContainer;
|
||||
namespace dm::curl { class Downloader; class Download; }
|
||||
|
||||
|
||||
namespace dm
|
||||
{
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class DownloadManager2;
|
||||
|
||||
class Download
|
||||
{
|
||||
public:
|
||||
enum States
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Queueing,
|
||||
Queued,
|
||||
|
||||
Finished,
|
||||
Errored,
|
||||
|
||||
Running,
|
||||
|
||||
Pausing,
|
||||
Paused,
|
||||
|
||||
Cancelling,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
Download(DownloadManager2& dm, MOBase::IPluginRepository& repo, QString what);
|
||||
|
||||
const QString& what() const;
|
||||
const QString& error() const;
|
||||
|
||||
States state() const;
|
||||
|
||||
bool start();
|
||||
void cancel();
|
||||
void pause();
|
||||
void queue();
|
||||
void tick();
|
||||
|
||||
QString debugName() const;
|
||||
|
||||
private:
|
||||
DownloadManager2& m_dm;
|
||||
MOBase::IPluginRepository& m_repo;
|
||||
std::unique_ptr<MOBase::IRepositoryDownload> m_download;
|
||||
QString m_what;
|
||||
States m_state;
|
||||
QString m_error;
|
||||
|
||||
void setState(States s);
|
||||
void next();
|
||||
};
|
||||
|
||||
|
||||
class DownloadManager2
|
||||
{
|
||||
public:
|
||||
static const std::size_t NoLimit =
|
||||
std::numeric_limits<std::size_t>::max();
|
||||
|
||||
DownloadManager2(PluginContainer& pc);
|
||||
~DownloadManager2();
|
||||
|
||||
void add(QString what);
|
||||
|
||||
void maxActive(std::size_t n);
|
||||
void maxSpeed(std::size_t bytesPerSecond);
|
||||
|
||||
bool hasActive() const;
|
||||
|
||||
curl::Downloader& downloader();
|
||||
|
||||
private:
|
||||
using DownloadList = std::list<std::shared_ptr<Download>>;
|
||||
|
||||
PluginContainer& m_pc;
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_stop;
|
||||
|
||||
std::unique_ptr<curl::Downloader> m_downloader;
|
||||
|
||||
std::vector<std::shared_ptr<Download>> m_temp;
|
||||
std::mutex m_tempMutex;
|
||||
std::condition_variable m_cv;
|
||||
|
||||
DownloadList m_queued, m_active, m_inactive;
|
||||
std::atomic<std::size_t> m_maxActive, m_maxSpeed;
|
||||
std::atomic<bool> m_hasActive;
|
||||
|
||||
|
||||
void run();
|
||||
void checkTemp();
|
||||
void checkQueue();
|
||||
void cleanupActive();
|
||||
void stopOverMax(std::size_t max);
|
||||
void addFromQueue(std::size_t max);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // MODORGANIZER_DOWNLOADEDMANAGER2_INCLUDED
|
||||
+23
-5
@@ -232,8 +232,8 @@ void forEachEntryImpl(
|
||||
|
||||
if (status < 0) {
|
||||
log::error(
|
||||
"NtOpenFile() failed for '{}', {}",
|
||||
toString(poa), formatSystemMessage(status));
|
||||
"failed to open directory '{}': {}",
|
||||
toString(poa), formatNtMessage(status));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -264,8 +264,9 @@ void forEachEntryImpl(
|
||||
break;
|
||||
} else if (status < 0) {
|
||||
log::error(
|
||||
"NtQueryDirectoryFile() failed for '{}', {}",
|
||||
toString(poa), formatSystemMessage(status));
|
||||
"failed to read directory '{}': {}",
|
||||
toString(poa), formatNtMessage(status));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -321,6 +322,23 @@ void forEachEntryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring makeNtPath(const std::wstring& path)
|
||||
{
|
||||
constexpr const wchar_t* nt_prefix = L"\\??\\";
|
||||
constexpr const wchar_t* nt_unc_prefix = L"\\??\\UNC\\";
|
||||
constexpr const wchar_t* share_prefix = L"\\\\";
|
||||
|
||||
if (path.starts_with(nt_prefix)) {
|
||||
// already an nt path
|
||||
return path;
|
||||
} else if (path.starts_with(share_prefix)) {
|
||||
// network shared need \??\UNC\ as a prefix
|
||||
return nt_unc_prefix + path.substr(2);
|
||||
} else {
|
||||
// prepend the \??\ prefix
|
||||
return nt_prefix + path;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryWalker::forEachEntry(
|
||||
const std::wstring& path, void* cx,
|
||||
@@ -335,7 +353,7 @@ void DirectoryWalker::forEachEntry(
|
||||
NtClose = (NtClose_type)::GetProcAddress(m.get(), "NtClose");
|
||||
}
|
||||
|
||||
const std::wstring ntpath = std::wstring(L"\\??\\") + path;
|
||||
const std::wstring ntpath = makeNtPath(path);
|
||||
|
||||
UNICODE_STRING ObjectName = {};
|
||||
ObjectName.Buffer = const_cast<wchar_t*>(ntpath.c_str());
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "instancemanager.h"
|
||||
#include "thread_utils.h"
|
||||
#include "shared/util.h"
|
||||
#include "downloadmanager2.h"
|
||||
#include <report.h>
|
||||
#include <log.h>
|
||||
|
||||
|
||||
+22
-20
@@ -351,6 +351,7 @@ MainWindow::MainWindow(Settings &settings
|
||||
}
|
||||
|
||||
settings.geometry().restoreState(ui->downloadView->header());
|
||||
settings.geometry().restoreState(ui->savegameList->header());
|
||||
|
||||
ui->splitter->setStretchFactor(0, 3);
|
||||
ui->splitter->setStretchFactor(1, 2);
|
||||
@@ -512,6 +513,8 @@ void MainWindow::setupModList()
|
||||
|
||||
connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { scheduleCheckForProblems(); });
|
||||
connect(&ui->modList->actions(), &ModListViewActions::originModified, this, &MainWindow::originModified);
|
||||
connect(&ui->modList->actions(), &ModListViewActions::modInfoDisplayed, this, &MainWindow::modInfoDisplayed);
|
||||
|
||||
connect(m_OrganizerCore.modList(), &ModList::modPrioritiesChanged, [&]() { m_ArchiveListWriter.write(); });
|
||||
}
|
||||
|
||||
@@ -2003,6 +2006,7 @@ void MainWindow::storeSettings()
|
||||
|
||||
s.geometry().saveState(ui->espList->header());
|
||||
s.geometry().saveState(ui->downloadView->header());
|
||||
s.geometry().saveState(ui->savegameList->header());
|
||||
|
||||
s.widgets().saveIndex(ui->executablesListBox);
|
||||
s.widgets().saveIndex(ui->tabWidget);
|
||||
@@ -2819,41 +2823,37 @@ void MainWindow::nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVa
|
||||
}
|
||||
QVariantList resultList = resultData.toList();
|
||||
|
||||
QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *watcher = new QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>>();
|
||||
QObject::connect(watcher, &QFutureWatcher<std::set<QSharedPointer<ModInfo>>>::finished, this, &MainWindow::finishUpdateInfo);
|
||||
QFuture<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> future = QtConcurrent::run([=]() -> std::pair<QString, std::set<QSharedPointer<ModInfo>>> {
|
||||
return std::make_pair(gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true));
|
||||
auto* watcher = new QFutureWatcher<NxmUpdateInfoData>();
|
||||
QObject::connect(watcher, &QFutureWatcher<NxmUpdateInfoData>::finished, [this, watcher]() {
|
||||
finishUpdateInfo(watcher->result());
|
||||
watcher->deleteLater();
|
||||
});
|
||||
auto future = QtConcurrent::run([=]() {
|
||||
return NxmUpdateInfoData{ gameNameReal, ModInfo::filteredMods(gameNameReal, resultList, userData.toBool(), true) };
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
ui->modList->invalidateFilter();
|
||||
}
|
||||
|
||||
void MainWindow::finishUpdateInfo()
|
||||
void MainWindow::finishUpdateInfo(const NxmUpdateInfoData& data)
|
||||
{
|
||||
QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *watcher = static_cast<QFutureWatcher<std::pair<QString, std::set<QSharedPointer<ModInfo>>>> *>(sender());
|
||||
|
||||
QString game = watcher->result().first;
|
||||
auto finalMods = watcher->result().second;
|
||||
|
||||
if (finalMods.empty()) {
|
||||
log::info("{}", tr("None of your %1 mods appear to have had recent file updates.").arg(game));
|
||||
if (data.finalMods.empty()) {
|
||||
log::info("{}", tr("None of your %1 mods appear to have had recent file updates.").arg(data.game));
|
||||
}
|
||||
|
||||
std::set<std::pair<QString, int>> organizedGames;
|
||||
for (auto mod : finalMods) {
|
||||
for (auto& mod : data.finalMods) {
|
||||
if (mod->canBeUpdated()) {
|
||||
organizedGames.insert(std::make_pair<QString, int>(mod->gameName().toLower(), mod->nexusId()));
|
||||
}
|
||||
m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
|
||||
}
|
||||
|
||||
if (!finalMods.empty() && organizedGames.empty())
|
||||
if (!data.finalMods.empty() && organizedGames.empty())
|
||||
log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."));
|
||||
|
||||
for (auto game : organizedGames)
|
||||
for (const auto& game : organizedGames) {
|
||||
NexusInterface::instance().requestUpdates(game.second, this, QVariant(), game.first, QString());
|
||||
|
||||
disconnect(sender());
|
||||
delete sender();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID)
|
||||
@@ -2928,13 +2928,15 @@ void MainWindow::nxmUpdatesAvailable(QString gameName, int modID, QVariant userD
|
||||
if (foundUpdate) {
|
||||
// Just get the standard data updates for endorsements and descriptions
|
||||
mod->setLastNexusUpdate(QDateTime::currentDateTimeUtc());
|
||||
m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name()));
|
||||
} else {
|
||||
// Scrape mod data here so we can use the mod version if no file update was located
|
||||
requiresInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
// invalidate the filter to display mods with an update
|
||||
ui->modList->invalidateFilter();
|
||||
|
||||
if (requiresInfo)
|
||||
NexusInterface::instance().requestModInfo(gameNameReal, modID, this, QVariant(), QString());
|
||||
}
|
||||
|
||||
+12
-2
@@ -143,6 +143,9 @@ public slots:
|
||||
void refresherProgress(const DirectoryRefreshProgress* p);
|
||||
|
||||
signals:
|
||||
// emitted after the information dialog has been closed, used by tutorials
|
||||
//
|
||||
void modInfoDisplayed();
|
||||
|
||||
/**
|
||||
* @brief emitted when the selected style changes
|
||||
@@ -224,6 +227,13 @@ private:
|
||||
void toggleMO2EndorseState();
|
||||
void toggleUpdateAction();
|
||||
|
||||
// update info
|
||||
struct NxmUpdateInfoData {
|
||||
QString game;
|
||||
std::set<ModInfo::Ptr> finalMods;
|
||||
};
|
||||
void finishUpdateInfo(const NxmUpdateInfoData& data);
|
||||
|
||||
private:
|
||||
|
||||
static const char *PATTERN_BACKUP_GLOB;
|
||||
@@ -331,10 +341,10 @@ private slots:
|
||||
|
||||
void modInstalled(const QString &modName);
|
||||
|
||||
void finishUpdateInfo();
|
||||
// update info
|
||||
void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID);
|
||||
|
||||
void nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int);
|
||||
void nxmUpdateInfoAvailable(QString gameName, QVariant userData, QVariant resultData, int requestID);
|
||||
void nxmUpdatesAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
|
||||
void nxmModInfoAvailable(QString gameName, int modID, QVariant userData, QVariant resultData, int requestID);
|
||||
void nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int);
|
||||
|
||||
+39
-2
@@ -1199,7 +1199,7 @@ p, li { white-space: pre-wrap; }
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QListWidget" name="savegameList">
|
||||
<widget class="QTreeWidget" name="savegameList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
@@ -1215,6 +1215,9 @@ p, li { white-space: pre-wrap; }
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -1224,9 +1227,43 @@ p, li { white-space: pre-wrap; }
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<property name="indentation">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="headerCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignLeading|AlignVCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignLeading|AlignVCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -108,6 +108,12 @@ bool ModListSortProxy::lessThan(const QModelIndex &left,
|
||||
if (sortColumn() != ModList::COL_PRIORITY) {
|
||||
return QSortFilterProxyModel::lessThan(left, right);
|
||||
}
|
||||
else if (qobject_cast<QtGroupingProxy*>(sourceModel())) {
|
||||
// if the underlying proxy is a QtGroupingProxy we need to rely on
|
||||
// Qt::DisplayRole because the other roles are not correctly handled
|
||||
// by that kind of proxy
|
||||
return left.data(Qt::DisplayRole).toInt() < right.data(Qt::DisplayRole).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
bool lOk, rOk;
|
||||
|
||||
@@ -299,6 +299,11 @@ std::optional<unsigned int> ModListView::prevMod(unsigned int modIndex) const
|
||||
return {};
|
||||
}
|
||||
|
||||
void ModListView::invalidateFilter()
|
||||
{
|
||||
m_sortProxy->invalidate();
|
||||
}
|
||||
|
||||
void ModListView::setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria)
|
||||
{
|
||||
m_sortProxy->setCriteria(criteria);
|
||||
|
||||
@@ -112,6 +112,10 @@ signals:
|
||||
|
||||
public slots:
|
||||
|
||||
// invalidate (refresh) the filter (similar to a layout changed event)
|
||||
//
|
||||
void invalidateFilter();
|
||||
|
||||
// set the filter criteria/options for mods
|
||||
//
|
||||
void setFilterCriteria(const std::vector<ModListSortProxy::Criteria>& criteria);
|
||||
|
||||
+55
-28
@@ -145,9 +145,10 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const
|
||||
return;
|
||||
}
|
||||
|
||||
// find the priority before refresh() otherwise the index might not be valid
|
||||
const int newPriority = findInstallPriority(index);
|
||||
m_core.refresh();
|
||||
|
||||
const int newPriority = findInstallPriority(index);
|
||||
const auto mIndex = ModInfo::getIndex(name);
|
||||
if (newPriority >= 0) {
|
||||
m_core.modList()->changeModPriority(mIndex, newPriority);
|
||||
@@ -520,6 +521,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, unsigned in
|
||||
|
||||
modInfo->saveMeta();
|
||||
m_core.modList()->modInfoChanged(modInfo);
|
||||
emit modInfoDisplayed();
|
||||
}
|
||||
|
||||
if (m_core.currentProfile()->modEnabled(modIndex) && !modInfo->isForeign()) {
|
||||
@@ -567,47 +569,72 @@ void ModListViewActions::sendModsToPriority(const QModelIndexList& indexes) cons
|
||||
void ModListViewActions::sendModsToSeparator(const QModelIndexList& indexes) const
|
||||
{
|
||||
QStringList separators;
|
||||
auto indexesByPriority = m_core.currentProfile()->getAllIndexesByPriority();
|
||||
for (auto iter = indexesByPriority.begin(); iter != indexesByPriority.end(); iter++) {
|
||||
if ((iter->second != UINT_MAX)) {
|
||||
ModInfo::Ptr modInfo = ModInfo::getByIndex(iter->second);
|
||||
const auto& ibp = m_core.currentProfile()->getAllIndexesByPriority();
|
||||
for (const auto& [priority, index] : ibp) {
|
||||
if (index < ModInfo::getNumMods()) {
|
||||
ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
|
||||
if (modInfo->isSeparator()) {
|
||||
separators << modInfo->name().chopped(10); // Chops the "_separator" away from the name
|
||||
separators << modInfo->name().chopped(10); // chops the "_separator" away from the name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// in descending order, reverse the separator
|
||||
if (m_view->sortOrder() == Qt::DescendingOrder) {
|
||||
std::reverse(separators.begin(), separators.end());
|
||||
}
|
||||
|
||||
ListDialog dialog(m_parent);
|
||||
dialog.setWindowTitle("Select a separator...");
|
||||
dialog.setChoices(separators);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString result = dialog.getChoice();
|
||||
if (!result.isEmpty()) {
|
||||
result += "_separator";
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
int newPriority = Profile::MaximumPriority;
|
||||
bool foundSection = false;
|
||||
for (auto mod : m_core.modList()->allModsByProfilePriority()) {
|
||||
unsigned int modIndex = ModInfo::getIndex(mod);
|
||||
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
|
||||
if (!foundSection && result.compare(mod) == 0) {
|
||||
foundSection = true;
|
||||
}
|
||||
else if (foundSection && modInfo->isSeparator()) {
|
||||
newPriority = m_core.currentProfile()->getModPriority(modIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const QString result = dialog.getChoice();
|
||||
if (result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indexes.size() == 1
|
||||
&& m_core.currentProfile()->getModPriority(indexes[0].data(ModList::IndexRole).toInt()) < newPriority) {
|
||||
--newPriority;
|
||||
}
|
||||
const auto sepPriority = m_core.currentProfile()->getModPriority(
|
||||
ModInfo::getIndex(result + "_separator"));
|
||||
|
||||
m_core.modList()->changeModsPriority(indexes, newPriority);
|
||||
auto isSeparator = [](const auto& p) {
|
||||
return ModInfo::getByIndex(p.second)->isSeparator();
|
||||
};
|
||||
|
||||
|
||||
// start right after/before the current priority and look for the next
|
||||
// separator
|
||||
int priority = -1;
|
||||
if (m_view->sortOrder() == Qt::AscendingOrder) {
|
||||
auto it = std::find_if(ibp.find(sepPriority + 1), ibp.end(), isSeparator);
|
||||
if (it != ibp.end()) {
|
||||
priority = it->first;
|
||||
}
|
||||
else {
|
||||
priority = Profile::MaximumPriority;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto it = std::find_if(--std::reverse_iterator{ ibp.find(sepPriority - 1) }, ibp.rend(), isSeparator);
|
||||
if (it != ibp.rend()) {
|
||||
priority = it->first + 1;
|
||||
}
|
||||
else {
|
||||
// create "before" priority 0, i.e. at the end in descending priority.
|
||||
priority = Profile::MinimumPriority;
|
||||
}
|
||||
}
|
||||
|
||||
// when the priority of a single mod is incremented, we need to shift the
|
||||
// target priority, otherwise we will miss the target by one
|
||||
if (indexes.size() == 1 && indexes[0].data(ModList::PriorityRole).toInt() < sepPriority) {
|
||||
priority--;
|
||||
}
|
||||
|
||||
m_core.modList()->changeModsPriority(indexes, priority);
|
||||
}
|
||||
|
||||
void ModListViewActions::sendModsToFirstConflict(const QModelIndexList& indexes) const
|
||||
|
||||
@@ -138,6 +138,10 @@ signals:
|
||||
//
|
||||
void originModified(int originId) const;
|
||||
|
||||
// emitted when the mod info dialog has been shown and closed
|
||||
//
|
||||
void modInfoDisplayed() const;
|
||||
|
||||
private:
|
||||
|
||||
// find the priority where to install or create a mod for the
|
||||
|
||||
+507
-501
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
||||
#include <ctime>
|
||||
#include <deque>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@@ -268,6 +267,3 @@
|
||||
#include <QtPlugin>
|
||||
#include <QtTest/QtTest>
|
||||
#include <QStandardPaths>
|
||||
|
||||
// curl
|
||||
#include <curl/curl.h>
|
||||
|
||||
+15
-15
@@ -42,9 +42,10 @@ namespace bf = boost::fusion;
|
||||
// the one corresponding to the currently managed games.
|
||||
// - If a plugin has a master plugin (IPlugin::master()), it cannot be enabled/disabled by users,
|
||||
// and will follow the enabled/disabled state of its parent.
|
||||
// - Each plugin has an "enabled" setting stored in persistence. A plugin is considered disabled
|
||||
// if the setting is false.
|
||||
// - If the setting is true or does not exist, a plugin is considered disabled if one of its
|
||||
// - Each plugin has an "enabled" setting stored in persistence. If the setting does not exist,
|
||||
// the plugin's enabledByDefault is used instead.
|
||||
// - A plugin is considered disabled if the setting is false.
|
||||
// - If the setting is true, a plugin is considered disabled if one of its
|
||||
// requirements is not met.
|
||||
// - Users cannot enable a plugin if one of its requirements is not met.
|
||||
//
|
||||
@@ -81,7 +82,6 @@ template <> struct PluginTypeName<MOBase::IPluginPreview> { static QString value
|
||||
template <> struct PluginTypeName<MOBase::IPluginTool> { static QString value() { return QT_TR_NOOP("Tool"); } };
|
||||
template <> struct PluginTypeName<MOBase::IPluginProxy> { static QString value() { return QT_TR_NOOP("Proxy"); } };
|
||||
template <> struct PluginTypeName<MOBase::IPluginFileMapper> { static QString value() { return QT_TR_NOOP("File Mapper"); } };
|
||||
template <> struct PluginTypeName<MOBase::IPluginRepository> { static QString value() { return QT_TR_NOOP("Repository"); } };
|
||||
|
||||
|
||||
QStringList PluginContainer::pluginInterfaces()
|
||||
@@ -511,16 +511,6 @@ IPlugin* PluginContainer::registerPlugin(QObject *plugin, const QString& filepat
|
||||
return modPage;
|
||||
}
|
||||
}
|
||||
{ // repo plugin
|
||||
IPluginRepository *repo = qobject_cast<IPluginRepository*>(plugin);
|
||||
if (repo) {
|
||||
if (initPlugin(repo, pluginProxy, skipInit)) {
|
||||
bf::at_key<IPluginRepository>(m_Plugins).push_back(repo);
|
||||
emit pluginRegistered(repo);
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // game plugin
|
||||
IPluginGame *game = qobject_cast<IPluginGame*>(plugin);
|
||||
if (game) {
|
||||
@@ -610,7 +600,7 @@ bool PluginContainer::isEnabled(IPlugin* plugin) const
|
||||
}
|
||||
|
||||
// Check if the plugin is enabled:
|
||||
if (!m_Organizer->persistent(plugin->name(), "enabled", true).toBool()) {
|
||||
if (!m_Organizer->persistent(plugin->name(), "enabled", plugin->enabledByDefault()).toBool()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -963,6 +953,16 @@ void PluginContainer::reloadPlugin(QString const& filepath)
|
||||
|
||||
void PluginContainer::unloadPlugins()
|
||||
{
|
||||
if (m_Organizer) {
|
||||
// this will clear several structures that can hold on to pointers to
|
||||
// plugins, as well as read the plugin blacklist from the ini file, which
|
||||
// is used in loadPlugins() below to skip plugins
|
||||
//
|
||||
// note that the first thing loadPlugins() does is call unloadPlugins(),
|
||||
// so this makes sure the blacklist is always available
|
||||
m_Organizer->settings().plugins().clearPlugins();
|
||||
}
|
||||
|
||||
bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); });
|
||||
bf::for_each(m_AccessPlugins, [](auto& t) { t.second.clear(); });
|
||||
m_Requirements.clear();
|
||||
|
||||
@@ -13,7 +13,6 @@ class IUserInterface;
|
||||
#include <ipluginproxy.h>
|
||||
#include <iplugininstaller.h>
|
||||
#include <ipluginfilemapper.h>
|
||||
#include <ipluginrepository.h>
|
||||
#include <QtPlugin>
|
||||
#include <QPluginLoader>
|
||||
#include <QFile>
|
||||
@@ -143,8 +142,7 @@ private:
|
||||
boost::fusion::pair<MOBase::IPluginPreview, std::vector<MOBase::IPluginPreview*>>,
|
||||
boost::fusion::pair<MOBase::IPluginTool, std::vector<MOBase::IPluginTool*>>,
|
||||
boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>,
|
||||
boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>,
|
||||
boost::fusion::pair<MOBase::IPluginRepository, std::vector<MOBase::IPluginRepository*>>
|
||||
boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>
|
||||
>;
|
||||
|
||||
using AccessPluginMap = boost::fusion::map<
|
||||
@@ -168,9 +166,9 @@ private:
|
||||
boost::mp11::mp_transform<
|
||||
std::add_pointer_t,
|
||||
boost::mp11::mp_list<
|
||||
MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, MOBase::IPluginRepository,
|
||||
MOBase::IPluginPreview, MOBase::IPluginProxy, MOBase::IPluginTool, MOBase::IPluginDiagnose,
|
||||
MOBase::IPluginFileMapper, MOBase::IPlugin
|
||||
MOBase::IPluginGame, MOBase::IPluginInstaller, MOBase::IPluginModPage, MOBase::IPluginPreview,
|
||||
MOBase::IPluginProxy, MOBase::IPluginTool, MOBase::IPluginDiagnose, MOBase::IPluginFileMapper,
|
||||
MOBase::IPlugin
|
||||
>
|
||||
>;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user