diff --git a/src/downloadlist.h b/src/downloadlist.h index c9adaa7f2..49011e1e8 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -88,7 +88,8 @@ public slots: /** * @brief used to inform the model that data has changed * - * @param row the row that changed. This can be negative to update the whole view + * @param row the row that changed. This can be negative to update the whole view, + * but you have to call aboutToUpdate() before update(-1) and only for that case. **/ void update(int row); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index ce6ba665d..57afbbcee 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -30,10 +30,8 @@ along with Mod Organizer. If not, see . #include "selectiondialog.h" #include "shared/util.h" #include "utility.h" -#include #include #include -#include #include #include @@ -84,21 +82,22 @@ DownloadManager::DownloadInfo::createFromMeta(const QString& filePath, bool show const QString outputDirectory, std::optional fileSize) { - DownloadInfo* info = new DownloadInfo; - QString metaFileName = filePath + ".meta"; QFileInfo metaFileInfo(metaFileName); if (QDir::fromNativeSeparators(metaFileInfo.path()) .compare(QDir::fromNativeSeparators(outputDirectory), Qt::CaseInsensitive) != 0) return nullptr; + QSettings metaFile(metaFileName, QSettings::IniFormat); - if (!showHidden && metaFile.value("removed", false).toBool()) { + bool hidden = metaFile.value("removed", false).toBool(); + if (!showHidden && hidden) { return nullptr; - } else { - info->m_Hidden = metaFile.value("removed", false).toBool(); } + DownloadInfo* info = new DownloadInfo; + info->m_Hidden = hidden; + QString fileName = QFileInfo(filePath).fileName(); if (fileName.endsWith(UNFINISHED)) { @@ -339,7 +338,7 @@ void DownloadManager::refreshList() // avoid triggering other refreshes ScopedDisableDirWatcher scopedDirWatcher(this); - int downloadsBefore = m_ActiveDownloads.size(); + // int downloadsBefore = m_ActiveDownloads.size(); // unused // remove finished downloads for (QVector::iterator iter = m_ActiveDownloads.begin(); @@ -447,7 +446,7 @@ void DownloadManager::refreshList() void DownloadManager::queryDownloadListInfo() { int incompleteCount = 0; - for (size_t i = 0; i < m_ActiveDownloads.size(); i++) { + for (qsizetype i = 0; i < m_ActiveDownloads.size(); i++) { if (isInfoIncomplete(i)) { incompleteCount++; } @@ -464,7 +463,7 @@ void DownloadManager::queryDownloadListInfo() TimeThis tt("DownloadManager::queryDownloadListInfo()"); log::info("Querying metadata for every download with incomplete info..."); startDisableDirWatcher(); - for (size_t i = 0; i < m_ActiveDownloads.size(); i++) { + for (qsizetype i = 0; i < m_ActiveDownloads.size(); i++) { if (isInfoIncomplete(i)) { queryInfoMd5(i, false); } @@ -508,6 +507,8 @@ bool DownloadManager::addDownload(const QStringList& URLs, QString gameName, int QNetworkRequest request(preferredUrl); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, + QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); @@ -567,7 +568,7 @@ void DownloadManager::removePending(QString gameName, int modID, int fileID) QString gameShortName = gameName; QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); - for (auto game : games) { + for (const auto& game : games) { MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { @@ -576,7 +577,7 @@ void DownloadManager::removePending(QString gameName, int modID, int fileID) } } emit aboutToUpdate(); - for (auto iter : m_PendingDownloads) { + for (auto& iter : m_PendingDownloads) { if (gameShortName.compare(std::get<0>(iter), Qt::CaseInsensitive) == 0 && (std::get<1>(iter) == modID) && (std::get<2>(iter) == fileID)) { m_PendingDownloads.removeAt(m_PendingDownloads.indexOf(iter)); @@ -603,7 +604,7 @@ void DownloadManager::startDownload(QNetworkReply* reply, DownloadInfo* newDownl } newDownload->m_StartTime.start(); - createMetaFile(newDownload); + // createMetaFile(newDownload); // only create meta file on finished download if (!newDownload->m_Output.open(mode)) { reportError(tr("failed to download %1: could not open output file: %2") @@ -625,10 +626,9 @@ void DownloadManager::startDownload(QNetworkReply* reply, DownloadInfo* newDownl removePending(newDownload->m_FileInfo->gameName, newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); - emit aboutToUpdate(); m_ActiveDownloads.append(newDownload); - emit update(-1); + emit update(indexByInfo(newDownload)); emit downloadAdded(); if (QFile::exists(m_OutputDirectory + "/" + newDownload->m_FileName)) { @@ -654,22 +654,22 @@ void DownloadManager::startDownload(QNetworkReply* reply, DownloadInfo* newDownl else setState(newDownload, STATE_DOWNLOADING); } - } else - connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + } + } + // always connect finished to avoid missing the finished signal in certain flows + connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - QCoreApplication::processEvents(); + QCoreApplication::processEvents(); - if (newDownload->m_State != STATE_DOWNLOADING && - newDownload->m_State != STATE_READY && - newDownload->m_State != STATE_FETCHINGMODINFO && reply->isFinished()) { - int index = indexByInfo(newDownload); - if (index >= 0) { - downloadFinished(index); - } - return; + if (newDownload->m_State != STATE_DOWNLOADING && + newDownload->m_State != STATE_READY && + newDownload->m_State != STATE_FETCHINGMODINFO && reply->isFinished()) { + int index = indexByInfo(newDownload); + if (index >= 0) { + downloadFinished(index); } - } else - connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + return; + } } void DownloadManager::addNXMDownload(const QString& url) @@ -680,7 +680,7 @@ void DownloadManager::addNXMDownload(const QString& url) MOBase::IPluginGame* foundGame = nullptr; validGames.append(m_ManagedGame->gameShortName()); validGames.append(m_ManagedGame->validShortNames()); - for (auto game : validGames) { + for (const auto& game : validGames) { MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); // some game plugins give names in validShortNames() that may refer to other @@ -712,11 +712,11 @@ void DownloadManager::addNXMDownload(const QString& url) return; } - for (auto tuple : m_PendingDownloads) { + for (const auto& tuple : m_PendingDownloads) { if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == - 0, + 0 && std::get<1>(tuple) == nxmInfo.modId() && - std::get<2>(tuple) == nxmInfo.fileId()) { + std::get<2>(tuple) == nxmInfo.fileId()) { const auto infoStr = tr("There is already a download queued for this file.\n\nMod %1\nFile %2") .arg(nxmInfo.modId()) @@ -1116,7 +1116,7 @@ void DownloadManager::queryInfo(int index) if (choices.size() == 1) { info->m_FileInfo->gameName = choices[0].first; } else { - for (auto choice : choices) { + for (const auto& choice : choices) { selection.addChoice(choice.first, choice.second, choice.first); } if (selection.exec() == QDialog::Accepted) { @@ -1597,6 +1597,13 @@ QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply* reply) return MOBase::sanitizeFileName(QString::fromUtf8(result.str(1).c_str())); } } + // fallback to url path + if (reply->url().isValid()) { + auto filename = QFileInfo(reply->url().path()).fileName(); + std::regex exp("^.+\\..+$"); + if (std::regex_search(filename.toStdString(), exp)) + return MOBase::sanitizeFileName(filename); + } return QString(); } @@ -1677,7 +1684,6 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) try { DownloadInfo* info = findDownload(this->sender(), &index); if (info != nullptr) { - info->m_HasData = true; if (info->m_State == STATE_CANCELING) { setState(info, STATE_CANCELED); } else if (info->m_State == STATE_PAUSING) { @@ -1922,7 +1928,7 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); - for (auto game : games) { + for (const auto& game : games) { MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { @@ -1971,20 +1977,33 @@ bool ServerByPreference(const ServerList::container& preferredServers, return (a > b); } +int DownloadManager::generateRandomDownloadID() +{ + QRandomGenerator* generator = QRandomGenerator::global(); + auto currentDownloadSet = std::set(); + for (const auto& download : m_ActiveDownloads) { + currentDownloadSet.insert(download->m_DownloadID); + } + int newID; + do { + newID = generator->generate(); + } while (currentDownloadSet.contains(newID)); + return newID; +} + int DownloadManager::startDownloadURLs(const QStringList& urls) { ModRepositoryFileInfo info; addDownload(urls, "", -1, -1, &info); - return m_ActiveDownloads.size() - 1; + return generateRandomDownloadID(); } int DownloadManager::startDownloadNexusFile(const QString& gameName, int modID, int fileID) { - int newID = m_ActiveDownloads.size(); addNXMDownload( QString("nxm://%1/mods/%2/files/%3").arg(gameName).arg(modID).arg(fileID)); - return newID; + return generateRandomDownloadID(); } QString DownloadManager::downloadPath(int id) @@ -2159,7 +2178,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use QString gameShortName = gameName; QStringList games(m_ManagedGame->validShortNames()); games += m_ManagedGame->gameShortName(); - for (auto game : games) { + for (const auto& game : games) { MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); if (gamePlugin != nullptr && gamePlugin->gameNexusName().compare(gameName, Qt::CaseInsensitive) == 0) { @@ -2242,20 +2261,21 @@ void DownloadManager::downloadFinished(int index) info = m_ActiveDownloads[index]; else { info = findDownload(this->sender(), &index); - if (info == nullptr && index == 0) { - info = m_ActiveDownloads[index]; + if (info == nullptr && index == 0 && !m_ActiveDownloads.isEmpty()) { + info = m_ActiveDownloads.last(); } } if (info != nullptr) { QNetworkReply* reply = info->m_Reply; - QByteArray data; - if (reply->isOpen() && info->m_HasData) { - data = reply->readAll(); - info->m_Output.write(data); + if (reply->isFinished()) { + writeData(info); + info->m_Output.close(); + TaskProgressManager::instance().forgetMe(info->m_TaskProgressId); + } else { + emit showMessage( + tr("Download finished event received but download is not finished?")); } - info->m_Output.close(); - TaskProgressManager::instance().forgetMe(info->m_TaskProgressId); bool error = false; if ((info->m_State != STATE_CANCELING) && (info->m_State != STATE_PAUSING)) { @@ -2287,16 +2307,16 @@ void DownloadManager::downloadFinished(int index) if (info->m_State == STATE_CANCELING) { setState(info, STATE_CANCELED); } else if (info->m_State == STATE_PAUSING) { - if (info->m_Output.isOpen() && info->m_HasData) { - info->m_Output.write(info->m_Reply->readAll()); - } + writeData(info); setState(info, STATE_PAUSED); } + bool cancelled = false; if (info->m_State == STATE_CANCELED || (info->m_Tries == 0 && error)) { emit aboutToUpdate(); info->m_Output.remove(); delete info; + cancelled = true; m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); if (error) emit showMessage( @@ -2304,12 +2324,10 @@ void DownloadManager::downloadFinished(int index) "There may be an issue with the Nexus servers.")); emit update(-1); } else if (info->isPausedState() || info->m_State == STATE_PAUSING) { - emit aboutToUpdate(); info->m_Output.close(); createMetaFile(info); emit update(index); } else { - emit aboutToUpdate(); QString url = info->m_Urls[info->m_CurrentUrl]; if (info->m_FileInfo->userData.contains("downloadMap")) { foreach (const QVariant& server, @@ -2358,7 +2376,7 @@ void DownloadManager::downloadFinished(int index) reply->close(); reply->deleteLater(); - if ((info->m_Tries > 0) && error) { + if (!cancelled && (info->m_Tries > 0) && error) { --info->m_Tries; resumeDownloadInt(index); } @@ -2429,20 +2447,26 @@ void DownloadManager::checkDownloadTimeout() void DownloadManager::writeData(DownloadInfo* info) { - if (info != nullptr) { - qint64 ret = info->m_Output.write(info->m_Reply->readAll()); - if (ret < info->m_Reply->size()) { + if (info != nullptr && info->m_Reply != nullptr && info->m_Reply->isOpen() && + info->m_Reply->bytesAvailable() > 0) { + QByteArray data = info->m_Reply->readAll(); + if (data.isEmpty()) { + return; + } + qint64 ret = info->m_Output.write(data); + if (ret < data.size()) { QString fileName = info->m_FileName; // m_FileName may be destroyed after setState setState(info, DownloadState::STATE_CANCELED); - log::error("Unable to write download \"{}\" to drive (return {})", - info->m_FileName, ret); + log::error("Unable to write download \"{}\" to drive (wrote {} of {})", + info->m_FileName, ret, data.size()); - reportError(tr("Unable to write download to drive (return %1).\n" - "Check the drive's available storage.\n\n" - "Canceling download \"%2\"...") + reportError(tr("Unable to write download to drive (wrote %1).\n" + "Check the drive's available storage (need %2).\n\n" + "Canceling download \"%3\"...") .arg(ret) + .arg(data.size()) .arg(fileName)); } } diff --git a/src/downloadmanager.h b/src/downloadmanager.h index abf909c86..4b9edea3d 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -94,7 +94,6 @@ class DownloadManager : public QObject QElapsedTimer m_StartTime; qint64 m_PreResumeSize; std::pair m_Progress; - bool m_HasData; DownloadState m_State; int m_CurrentUrl; QStringList m_Urls; @@ -143,8 +142,8 @@ class DownloadManager : public QObject private: DownloadInfo() - : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_HasData(false), - m_AskIfNotFound(true), m_DownloadTimeLast(0), m_DownloadLast(0), + : m_TotalSize(0), m_ReQueried(false), m_Hidden(false), m_AskIfNotFound(true), + m_DownloadTimeLast(0), m_DownloadLast(0), m_DownloadAcc(tag::rolling_window::window_size = 200), m_DownloadTimeAcc(tag::rolling_window::window_size = 200) {} @@ -543,6 +542,7 @@ private slots: private: void createMetaFile(DownloadInfo* info); + int generateRandomDownloadID(); DownloadManager::DownloadInfo* getDownloadInfo(QString fileName); public: diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index fa4dd7294..5276f08bd 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -986,6 +986,8 @@ void NexusInterface::nextRequest() url = info.m_URL; } QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, + QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); @@ -1069,13 +1071,16 @@ void NexusInterface::requestFinished(std::list::iterator iter) iter->m_UserData, iter->m_ID, statusCode, errorMsg); } else { int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode == 301) { + if (statusCode >= 301 && statusCode <= 308) { // redirect request, return request to queue iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); iter->m_Reroute = true; m_RequestQueue.enqueue(*iter); // nextRequest(); + log::debug("redirected download of " + reply->url().toString() + " to " + + iter->m_URL + " with status code " + + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); return; } QByteArray data = reply->readAll(); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3efdcda01..ade36e3ab 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -162,11 +162,9 @@ NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) onConnected(); }); - QObject::connect(&m_socket, - qOverload(&QWebSocket::error), - [&](auto&& e) { - onError(e); - }); + QObject::connect(&m_socket, &QWebSocket::errorOccurred, [&](auto&& e) { + onError(e); + }); QObject::connect(&m_socket, &QWebSocket::sslErrors, [&](auto&& errors) { onSslErrors(errors); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index b4d852907..b3613a975 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1008,145 +1008,150 @@ p, li { white-space: pre-wrap; } + Visit the uploader's profile + + + + Open File - + Open Meta File - - - + + + Reveal in Explorer - - + + Delete... - + Un-Hide - + Hide - - + + Cancel - + Pause - + Resume - + Delete Installed Downloads... - + Delete Uninstalled Downloads... - + Delete All Downloads... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - + Delete download - + Move to the Recycle Bin - - - + + + Delete Files? - + This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed? - - - + + + Hide Files? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). @@ -1154,22 +1159,22 @@ Are you absolutely sure you want to proceed? DownloadManager - + failed to rename "%1" to "%2" - + Memory allocation error (in refreshing directory). - + Query Metadata - + There are %1 downloads with incomplete metadata. Do you want to fetch all incomplete metadata? @@ -1177,32 +1182,32 @@ API requests will be consumed, and Mod Organizer may stutter. - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -1210,12 +1215,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -1223,303 +1228,318 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the Nexus mod ID - + Mod ID: - + Please select the source game code for %1 - + Hashing download file '%1' - + Cancel - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + + VisitUploaderProfile: invalid download index %1 + + + + + Uploader for this Mod is unknown + + + + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Archived - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - - + + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + %1% - %2 - ~%3 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + + Download finished event received but download is not finished? + + + + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - - Unable to write download to drive (return %1). -Check the drive's available storage. + + Unable to write download to drive (wrote %1). +Check the drive's available storage (need %2). -Canceling download "%2"... +Canceling download "%3"... DownloadsTab - + Query Metadata - + Cannot query metadata while offline mode is enabled. Do you want to disable offline mode? @@ -1722,16 +1742,27 @@ Right now the only case I know of where this needs to be overwritten is for the - This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + Mod Organizer will minimize to the system tray while this executable is running. It will reappear after it finishes. - Hide in user interface + Minimize to system tray while running + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + + + Hide in user interface + + + + (*) Profile specific @@ -1751,68 +1782,68 @@ Right now the only case I know of where this needs to be overwritten is for the - + Empty output mod - + The output mod for %2 is empty. - + Output mod not found - + The output mod '%1' for %2 does not exist. - + Reset plugin executables - + This will restore all the executables provided by the game plugin. If there are existing executables with the same names, they will be automatically renamed and left unchanged. - - + + New Executable - + Select a directory - + Executables (*.exe *.bat *.jar) - + All Files (*.*) - + Select an executable - + Java required - + MO requires Java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. @@ -2522,96 +2553,96 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Switching instances - + Mod Organizer must restart to manage the instance '%1'. - + This confirmation can be disabled in the settings. - + Restart Mod Organizer - - + + Cancel - - + + Rename instance - + The active instance cannot be renamed. - + Instance name - + Error - + Failed to rename "%1" to "%2": %3 - - - + + + Deleting instance - + The active instance cannot be deleted. - + These files and folders will be deleted - + All checked items will be deleted. - + Move to the recycle bin - + Delete permanently - + Nothing to delete. - + A portable instance already exists. @@ -3029,7 +3060,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Sort the plugins using LOOT. @@ -3175,7 +3206,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Name @@ -3433,7 +3464,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Endorse Mod Organizer @@ -3517,169 +3548,169 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Toolbar and Menu - + Desktop - + Start Menu - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - + Endorse - + Won't Endorse - + First Steps Translation strings for tutorial names - + Conflict Resolution - + Overview - + Help on UI - + Documentation - - + + Game Support Wiki - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help" menu. - + Never ask to show tutorials - + Do you know how to mod this game? Do you need to learn? There's a game support wiki available! Click OK to open the wiki. In the future, you can access this link from the "Help" menu. - + Category Setup - + Please choose how to handle the default category setup. If you've already connected to Nexus, you can automatically import Nexus categories for this game (if applicable). Otherwise, use the old Mod Organizer default category structure, or leave the categories blank (for manual setup). - - + + &Import Nexus Categories - + Use &Old Category Defaults - + Do &Nothing - + This is your first time running version 2.5 or higher with an old MO2 instance. The category system now relies on an updated system to map Nexus categories. In order to assign Nexus categories automatically, you will need to import the Nexus categories for the currently managed game and map them to your preferred category structure. @@ -3690,326 +3721,326 @@ As a final option, you can disable Nexus category mapping altogether, which can - + &Open Categories Dialog - + &Disable Nexus Mappings - + &Close - + &Don't show this again - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + None of your %1 mods appear to have had recent file updates. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + This file might be left over following a crash or power loss event. Check its contents before restoring. - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -4478,12 +4509,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4584,178 +4615,198 @@ p, li { white-space: pre-wrap; } - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + This file has been marked as "Old". There is most likely an updated version of this file available. - + This file has been marked as "Deleted"! You may want to check for an update or remove the nexus ID from this mod! - + %1 minute(s) and %2 second(s) - + This mod will be available to check in %2. - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + Confirm - + Are you sure you want to remove "%1"? - + Conflicts - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + + Author + + + + + Uploader + + + + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Primary category of the mod. - + + Author(s) of the mod. + + + + + Uploader of the mod. This is not necessarily the same as the author. + + + + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Indicators of file conflicts between mods. - + Emblems to highlight things that might require attention. - + Depicts the content of the mod: - + Time this mod was installed - + User notes about the mod @@ -4852,8 +4903,8 @@ p, li { white-space: pre-wrap; } - - + + Open in Explorer @@ -4869,13 +4920,13 @@ p, li { white-space: pre-wrap; } - + Select Color... - + Reset Color @@ -4891,121 +4942,127 @@ p, li { white-space: pre-wrap; } - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - - + + + Visit the uploader's profile + + + + + Visit on %1 - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - + Enable selected - + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Create Backup - + Restore hidden files - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Remap Category (From Nexus) - + Start tracking - + Stop tracking - + Tracked state unknown @@ -5124,7 +5181,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -5153,17 +5210,17 @@ Please enter the name: - + Exception: - + Unknown exception - + <Multiple> @@ -5182,7 +5239,7 @@ Please enter the name: - + Create Mod... @@ -5194,7 +5251,7 @@ Please enter a name: - + A mod with this name already exists @@ -5226,8 +5283,8 @@ Please enter a name: - - + + Confirm @@ -5239,8 +5296,8 @@ Please enter a name: - - + + Are you sure? @@ -5317,178 +5374,197 @@ You can also use online editors and converters instead. - Nexus_ID + Mod_Author - Mod_Nexus_URL + Mod_Uploader - Mod_Version + Nexus_ID - Install_Date + Mod_Nexus_URL + Mod_Uploader_URL + + + + + Mod_Version + + + + + Install_Date + + + + Download_File_Name - + export failed: %1 - + Failed to display overwrite dialog: %1 - + Set Priority - + Set the priority of the selected mods - + failed to rename mod: %1 - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - - Opening Nexus Links + + Nexus Links - - You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? + + + Web Pages - - - Opening Web Pages + + Uploader Profiles - - - You are trying to open %1 Web Pages. Are you sure you want to do this? + + Opening %1 - - - + + You are trying to open %1 %2. Are you sure you want to do this? + + + + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Restore all hidden files in the following mods?<br><ul>%1</ul> - + About to restore all hidden files in: - + Endorsing multiple mods will take a while. Please wait... - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - + failed to rename "%1" to "%2" - + Move successful. - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + About to recursively delete: @@ -5553,32 +5629,32 @@ Please enter a name: NexusInterface - + Please pick the mod ID for "%1" - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -5669,208 +5745,208 @@ Please enter a name: OrganizerCore - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + Failed to write settings - + An error occurred trying to write back MO settings to %1: %2 - + Download started - + Download failed - + The selected profile '%1' does not exist. The profile '%2' will be used instead - + Installation cancelled - + Another installation is currently in progress. - + Installation successful - + Configure Mod - + This mod contains ini tweaks. Do you want to configure them now? - + mod not found: %1 - + Extraction cancelled - + The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files. - + file not found: %1 - - + + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. @@ -5996,48 +6072,48 @@ Continue? PluginContainer - + Plugin error - + Mod Organizer failed to load the plugin '%1' last time it was started. - + The plugin can be skipped for this session, blacklisted, or loaded normally, in which case it might fail again. Blacklisted plugins can be re-enabled later in the settings. - + Skip this plugin - + Blacklist this plugin - + Load this plugin - + Some plugins could not be loaded - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: @@ -6991,7 +7067,7 @@ p, li { white-space: pre-wrap; } - + Portable @@ -7304,22 +7380,22 @@ Destination: - + Cannot open instance '%1', failed to read INI file %2. - + Cannot open instance '%1', the managed game was not found in the INI file %2. Select the game managed by this instance. - + Cannot open instance '%1', the game plugin '%2' doesn't exist. It may have been deleted by an antivirus. Select another instance. - + Cannot open instance '%1', the game directory '%2' doesn't exist or the game plugin '%3' doesn't recognize it. Select the game managed by this instance. @@ -7336,7 +7412,7 @@ Destination: - + @@ -7346,7 +7422,7 @@ Destination: - + Failed to create "%1". Your user account probably lacks permission. @@ -7438,38 +7514,38 @@ Destination: - + Mod Organizer - + An instance of Mod Organizer is already running - + <Unmanaged> - + Please use "Help" from the toolbar to get usage instructions to all elements - + Visit %1 on Nexus - - + + <Manage...> - + failed to parse profile %1: %2 @@ -7607,7 +7683,7 @@ Destination: - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. @@ -9325,6 +9401,19 @@ programs you are intentionally running. + + SystemTrayManager + + + Mod Organizer + + + + + Exit + + + T