浏览代码

Merge branch 'Logic' of LotRO_Legacy/Legacy_v2 into master

Ivan Arkhipov 4 年之前
父节点
当前提交
d6689d6eec
共有 46 个文件被更改,包括 3067 次插入1864 次删除
  1. 198 82
      import/EasyLogging++/easylogging++.cc
  2. 117 154
      import/EasyLogging++/easylogging++.h
  3. 74 11
      import/LotroDat/DatSubsystems/DatStatus.h
  4. 16 6
      src/Legacy/Legacy.pro
  5. 116 0
      src/Legacy/legacyapplication.cpp
  6. 27 0
      src/Legacy/legacyapplication.h
  7. 4 42
      src/Legacy/main.cpp
  8. 68 49
      src/Legacy/models/downloader.cpp
  9. 61 18
      src/Legacy/models/downloader.h
  10. 6 0
      src/Legacy/models/filesystem.h
  11. 0 7
      src/Legacy/models/internallogicmanager.cpp
  12. 0 91
      src/Legacy/models/internallogicmanager.h
  13. 133 382
      src/Legacy/models/lotrodatmanager.cpp
  14. 35 38
      src/Legacy/models/lotrodatmanager.h
  15. 340 0
      src/Legacy/models/patch/graphicspatch.cpp
  16. 42 0
      src/Legacy/models/patch/graphicspatch.h
  17. 203 0
      src/Legacy/models/patch/patch.cpp
  18. 97 0
      src/Legacy/models/patch/patch.h
  19. 211 0
      src/Legacy/models/patch/soundspatch.cpp
  20. 35 0
      src/Legacy/models/patch/soundspatch.h
  21. 239 0
      src/Legacy/models/patch/textspatch.cpp
  22. 35 0
      src/Legacy/models/patch/textspatch.h
  23. 142 0
      src/Legacy/models/patch/videospatch.cpp
  24. 31 0
      src/Legacy/models/patch/videospatch.h
  25. 0 299
      src/Legacy/models/patchdownloader.cpp
  26. 0 79
      src/Legacy/models/patchdownloader.h
  27. 168 0
      src/Legacy/models/patchlist.cpp
  28. 68 0
      src/Legacy/models/patchlist.h
  29. 31 45
      src/Legacy/models/settings.h
  30. 13 4
      src/Legacy/object_script.Legacy.Debug
  31. 13 4
      src/Legacy/object_script.Legacy.Release
  32. 9 11
      src/Legacy/widgets/aboutwidget.cpp
  33. 4 8
      src/Legacy/widgets/aboutwidget.h
  34. 1 1
      src/Legacy/widgets/aboutwidget.ui
  35. 4 7
      src/Legacy/widgets/chooseversiondialog.cpp
  36. 1 4
      src/Legacy/widgets/chooseversiondialog.h
  37. 9 4
      src/Legacy/widgets/helpwidget.cpp
  38. 5 6
      src/Legacy/widgets/helpwidget.h
  39. 151 157
      src/Legacy/widgets/mainwindow.cpp
  40. 35 33
      src/Legacy/widgets/mainwindow.h
  41. 1 1
      src/Legacy/widgets/mainwindow.ui
  42. 10 34
      src/Legacy/widgets/settingswidget.cpp
  43. 5 15
      src/Legacy/widgets/settingswidget.h
  44. 199 110
      src/Legacy/widgets/statuswidget.cpp
  45. 19 16
      src/Legacy/widgets/statuswidget.h
  46. 91 146
      src/Legacy/widgets/statuswidget.ui

+ 198 - 82
import/EasyLogging++/easylogging++.cc

@@ -1,16 +1,16 @@
 //
 //  Bismillah ar-Rahmaan ar-Raheem
 //
-//  Easylogging++ v9.95.3
+//  Easylogging++ v9.96.7
 //  Cross-platform logging library for C++ applications
 //
-//  Copyright (c) 2017 muflihun.com
+//  Copyright (c) 2012-2018 Zuhd Web Services
+//  Copyright (c) 2012-2018 @abumusamq
 //
 //  This library is released under the MIT Licence.
-//  http://labs.muflihun.com/easyloggingpp/licence.php
+//  https://github.com/zuhd-org/easyloggingpp/blob/master/LICENSE
 //
-//  https://github.com/muflihun/easyloggingpp
-//  https://muflihun.github.io/easyloggingpp
+//  https://zuhd.org
 //  http://muflihun.com
 //
 
@@ -22,8 +22,95 @@ INITIALIZE_EASYLOGGINGPP
 
 namespace el {
 
-// el::base::utils
+// el::base
 namespace base {
+// el::base::consts
+namespace consts {
+
+// Level log values - These are values that are replaced in place of %level format specifier
+// Extra spaces after format specifiers are only for readability purposes in log files
+static const base::type::char_t* kInfoLevelLogValue     =   ELPP_LITERAL("INFO");
+static const base::type::char_t* kDebugLevelLogValue    =   ELPP_LITERAL("DEBUG");
+static const base::type::char_t* kWarningLevelLogValue  =   ELPP_LITERAL("WARNING");
+static const base::type::char_t* kErrorLevelLogValue    =   ELPP_LITERAL("ERROR");
+static const base::type::char_t* kFatalLevelLogValue    =   ELPP_LITERAL("FATAL");
+static const base::type::char_t* kVerboseLevelLogValue  =
+  ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level
+static const base::type::char_t* kTraceLevelLogValue    =   ELPP_LITERAL("TRACE");
+static const base::type::char_t* kInfoLevelShortLogValue     =   ELPP_LITERAL("I");
+static const base::type::char_t* kDebugLevelShortLogValue    =   ELPP_LITERAL("D");
+static const base::type::char_t* kWarningLevelShortLogValue  =   ELPP_LITERAL("W");
+static const base::type::char_t* kErrorLevelShortLogValue    =   ELPP_LITERAL("E");
+static const base::type::char_t* kFatalLevelShortLogValue    =   ELPP_LITERAL("F");
+static const base::type::char_t* kVerboseLevelShortLogValue  =   ELPP_LITERAL("V");
+static const base::type::char_t* kTraceLevelShortLogValue    =   ELPP_LITERAL("T");
+// Format specifiers - These are used to define log format
+static const base::type::char_t* kAppNameFormatSpecifier          =      ELPP_LITERAL("%app");
+static const base::type::char_t* kLoggerIdFormatSpecifier         =      ELPP_LITERAL("%logger");
+static const base::type::char_t* kThreadIdFormatSpecifier         =      ELPP_LITERAL("%thread");
+static const base::type::char_t* kSeverityLevelFormatSpecifier    =      ELPP_LITERAL("%level");
+static const base::type::char_t* kSeverityLevelShortFormatSpecifier    =      ELPP_LITERAL("%levshort");
+static const base::type::char_t* kDateTimeFormatSpecifier         =      ELPP_LITERAL("%datetime");
+static const base::type::char_t* kLogFileFormatSpecifier          =      ELPP_LITERAL("%file");
+static const base::type::char_t* kLogFileBaseFormatSpecifier      =      ELPP_LITERAL("%fbase");
+static const base::type::char_t* kLogLineFormatSpecifier          =      ELPP_LITERAL("%line");
+static const base::type::char_t* kLogLocationFormatSpecifier      =      ELPP_LITERAL("%loc");
+static const base::type::char_t* kLogFunctionFormatSpecifier      =      ELPP_LITERAL("%func");
+static const base::type::char_t* kCurrentUserFormatSpecifier      =      ELPP_LITERAL("%user");
+static const base::type::char_t* kCurrentHostFormatSpecifier      =      ELPP_LITERAL("%host");
+static const base::type::char_t* kMessageFormatSpecifier          =      ELPP_LITERAL("%msg");
+static const base::type::char_t* kVerboseLevelFormatSpecifier     =      ELPP_LITERAL("%vlevel");
+static const char* kDateTimeFormatSpecifierForFilename            =      "%datetime";
+// Date/time
+static const char* kDays[7]                         =      { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
+static const char* kDaysAbbrev[7]                   =      { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static const char* kMonths[12]                      =      { "January", "February", "March", "Apri", "May", "June", "July", "August",
+                                                             "September", "October", "November", "December"
+                                                           };
+static const char* kMonthsAbbrev[12]                =      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+static const char* kDefaultDateTimeFormat           =      "%Y-%M-%d %H:%m:%s,%g";
+static const char* kDefaultDateTimeFormatInFilename =      "%Y-%M-%d_%H-%m";
+static const int kYearBase                          =      1900;
+static const char* kAm                              =      "AM";
+static const char* kPm                              =      "PM";
+// Miscellaneous constants
+
+static const char* kNullPointer                            =      "nullptr";
+#if ELPP_VARIADIC_TEMPLATES_SUPPORTED
+#endif  // ELPP_VARIADIC_TEMPLATES_SUPPORTED
+static const base::type::VerboseLevel kMaxVerboseLevel     =      9;
+static const char* kUnknownUser                            =      "user";
+static const char* kUnknownHost                            =      "unknown-host";
+
+
+//---------------- DEFAULT LOG FILE -----------------------
+
+#if defined(ELPP_NO_DEFAULT_LOG_FILE)
+#  if ELPP_OS_UNIX
+static const char* kDefaultLogFile                         =      "/dev/null";
+#  elif ELPP_OS_WINDOWS
+static const char* kDefaultLogFile                         =      "nul";
+#  endif  // ELPP_OS_UNIX
+#elif defined(ELPP_DEFAULT_LOG_FILE)
+static const char* kDefaultLogFile                         =      ELPP_DEFAULT_LOG_FILE;
+#else
+static const char* kDefaultLogFile                         =      "myeasylog.log";
+#endif // defined(ELPP_NO_DEFAULT_LOG_FILE)
+
+
+#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
+static const char* kDefaultLogFileParam                    =      "--default-log-file";
+#endif  // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
+#if defined(ELPP_LOGGING_FLAGS_FROM_ARG)
+static const char* kLoggingFlagsParam                      =      "--logging-flags";
+#endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
+static const char* kValidLoggerIdSymbols                   =
+  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._";
+static const char* kConfigurationComment                   =      "##";
+static const char* kConfigurationLevel                     =      "*";
+static const char* kConfigurationLoggerId                  =      "--";
+}
+// el::base::utils
 namespace utils {
 
 /// @brief Aborts application due with user-defined status
@@ -276,11 +363,7 @@ void Configurations::set(Configuration* conf) {
 
 void Configurations::setToDefault(void) {
   setGlobally(ConfigurationType::Enabled, std::string("true"), true);
-#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
   setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true);
-#else
-  ELPP_UNUSED(base::consts::kDefaultLogFile);
-#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
 #if defined(ELPP_NO_LOG_TO_FILE)
   setGlobally(ConfigurationType::ToFile, std::string("false"), true);
 #else
@@ -309,9 +392,7 @@ void Configurations::setRemainingToDefault(void) {
 #else
   unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true"));
 #endif // defined(ELPP_NO_LOG_TO_FILE)
-#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
   unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile));
-#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
   unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true"));
   unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3"));
   unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true"));
@@ -569,7 +650,6 @@ void Logger::configure(const Configurations& configurations) {
   if (m_typedConfigurations != nullptr) {
     Configurations* c = const_cast<Configurations*>(m_typedConfigurations->configurations());
     if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) {
-      // This check is definitely needed for cases like ELPP_NO_DEFAULT_LOG_FILE
       flush();
     }
   }
@@ -613,10 +693,11 @@ void Logger::flush(Level level, base::type::fstream_t* fs) {
   }
   if (fs != nullptr) {
     fs->flush();
-    std::map<Level, unsigned int>::iterator iter = m_unflushedCount.find(level);
+    std::unordered_map<Level, unsigned int>::iterator iter = m_unflushedCount.find(level);
     if (iter != m_unflushedCount.end()) {
       iter->second = 0;
     }
+    Helpers::validateFileRolling(this, level);
   }
 }
 
@@ -675,10 +756,9 @@ std::size_t File::getSizeOfFile(base::type::fstream_t* fs) {
   if (fs == nullptr) {
     return 0;
   }
-  std::streampos currPos = fs->tellg();
-  fs->seekg(0, fs->end);
+  // Since the file stream is appended to or truncated, the current
+  // offset is the file size.
   std::size_t size = static_cast<std::size_t>(fs->tellg());
-  fs->seekg(currPos);
   return size;
 }
 
@@ -850,7 +930,7 @@ void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::st
   std::size_t foundAt = base::type::string_t::npos;
   while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) {
     if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) {
-      str.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
+      str.erase(foundAt - 1, 1);
       ++foundAt;
     } else {
       str.replace(foundAt, replaceWhat.length(), replaceWith);
@@ -997,8 +1077,9 @@ const std::string OS::getBashOutput(const char* command) {
   char hBuff[4096];
   if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) {
     pclose(proc);
-    if (hBuff[strlen(hBuff) - 1] == '\n') {
-      hBuff[strlen(hBuff) - 1] = '\0';
+    const std::size_t buffLen = strlen(hBuff);
+    if (buffLen > 0 && hBuff[buffLen - 1] == '\n') {
+      hBuff[buffLen - 1] = '\0';
     }
     return std::string(hBuff);
   } else {
@@ -1272,7 +1353,7 @@ bool CommandLineArgs::hasParamWithValue(const char* paramKey) const {
 }
 
 const char* CommandLineArgs::getParamValue(const char* paramKey) const {
-  std::map<std::string, std::string>::const_iterator iter = m_paramsWithValue.find(std::string(paramKey));
+  std::unordered_map<std::string, std::string>::const_iterator iter = m_paramsWithValue.find(std::string(paramKey));
   return iter != m_paramsWithValue.end() ? iter->second.c_str() : "";
 }
 
@@ -1418,7 +1499,7 @@ void LogFormat::parseFromFormat(const base::type::string_t& userFormat) {
         if (hasFlag(flag)) {
           // If we already have flag we remove the escape chars so that '%%' is turned to '%'
           // even after specifier resolution - this is because we only replaceFirst specifier
-          formatCopy.erase(foundAt > 0 ? foundAt - 1 : 0, 1);
+          formatCopy.erase(foundAt - 1, 1);
           ++foundAt;
         }
       } else {
@@ -1621,10 +1702,11 @@ void TypedConfigurations::build(Configurations* configurations) {
     } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) {
       setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap);
     } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) {
-      setValue(conf->level(), static_cast<std::size_t>(getULong(conf->value())), &m_maxLogFileSizeMap);
-#if !defined(ELPP_NO_DEFAULT_LOG_FILE)
-      withFileSizeLimit.push_back(conf);
-#endif  // !defined(ELPP_NO_DEFAULT_LOG_FILE)
+      auto v = getULong(conf->value());
+      setValue(conf->level(), static_cast<std::size_t>(v), &m_maxLogFileSizeMap);
+      if (v != 0) {
+        withFileSizeLimit.push_back(conf);
+      }
     } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) {
       setValue(conf->level(), static_cast<std::size_t>(getULong(conf->value())), &m_logFlushThresholdMap);
     }
@@ -1698,12 +1780,6 @@ std::string TypedConfigurations::resolveFilename(const std::string& filename) {
 }
 
 void TypedConfigurations::insertFile(Level level, const std::string& fullFilename) {
-#if defined(ELPP_NO_LOG_TO_FILE)
-  setValue(level, false, &m_toFileMap);
-  ELPP_UNUSED(fullFilename);
-  m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(nullptr)));
-  return;
-#endif
   std::string resolvedFilename = resolveFilename(fullFilename);
   if (resolvedFilename.empty()) {
     std::cerr << "Could not load empty file for logging, please re-check your configurations for level ["
@@ -1839,8 +1915,10 @@ bool RegisteredLoggers::remove(const std::string& id) {
   if (id == base::consts::kDefaultLoggerId) {
     return false;
   }
+  // get has internal lock
   Logger* logger = base::utils::Registry<Logger, std::string>::get(id);
   if (logger != nullptr) {
+    // unregister has internal lock
     unregister(logger);
   }
   return true;
@@ -1948,7 +2026,7 @@ bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char* file) {
   } else {
     char baseFilename[base::consts::kSourceFilenameMaxLength] = "";
     base::utils::File::buildBaseFilename(file, baseFilename);
-    std::map<std::string, base::type::VerboseLevel>::iterator it = m_modules.begin();
+    std::unordered_map<std::string, base::type::VerboseLevel>::iterator it = m_modules.begin();
     for (; it != m_modules.end(); ++it) {
       if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) {
         return vlevel <= it->second;
@@ -1989,20 +2067,26 @@ Storage::Storage(const LogBuilderPtr& defaultLogBuilder) :
   m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)),
   m_flags(ELPP_DEFAULT_LOGGING_FLAGS),
   m_vRegistry(new base::VRegistry(0, &m_flags)),
+
 #if ELPP_ASYNC_LOGGING
   m_asyncLogQueue(new base::AsyncLogQueue()),
   m_asyncDispatchWorker(asyncDispatchWorker),
 #endif  // ELPP_ASYNC_LOGGING
+
   m_preRollOutCallback(base::defaultPreRollOutCallback) {
   // Register default logger
   m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId));
   // We register default logger anyway (worse case it's not going to register) just in case
   m_registeredLoggers->get("default");
+
+#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
   // Register performance logger and reconfigure format
   Logger* performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId));
   m_registeredLoggers->get("performance");
   performanceLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%datetime %level %msg"));
   performanceLogger->reconfigure();
+#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
+
 #if defined(ELPP_SYSLOG)
   // Register syslog logger and reconfigure format
   Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId));
@@ -2045,7 +2129,7 @@ Storage::~Storage(void) {
 }
 
 bool Storage::hasCustomFormatSpecifier(const char* formatSpecifier) {
-  base::threading::ScopedLock scopedLock(lock());
+  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
   return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(),
                    formatSpecifier) != m_customFormatSpecifiers.end();
 }
@@ -2054,12 +2138,12 @@ void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier& customFo
   if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) {
     return;
   }
-  base::threading::ScopedLock scopedLock(lock());
+  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
   m_customFormatSpecifiers.push_back(customFormatSpecifier);
 }
 
 bool Storage::uninstallCustomFormatSpecifier(const char* formatSpecifier) {
-  base::threading::ScopedLock scopedLock(lock());
+  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
   std::vector<CustomFormatSpecifier>::iterator it = std::find(m_customFormatSpecifiers.begin(),
       m_customFormatSpecifiers.end(), formatSpecifier);
   if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) {
@@ -2097,9 +2181,33 @@ void Storage::setApplicationArguments(int argc, char** argv) {
 #endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
 }
 
+} // namespace base
+
+// LogDispatchCallback
+void LogDispatchCallback::handle(const LogDispatchData* data) {
+#if defined(ELPP_THREAD_SAFE)
+  base::threading::ScopedLock scopedLock(m_fileLocksMapLock);
+  std::string filename = data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level());
+  auto lock = m_fileLocks.find(filename);
+  if (lock == m_fileLocks.end()) {
+    m_fileLocks.emplace(std::make_pair(filename, std::unique_ptr<base::threading::Mutex>(new base::threading::Mutex)));
+  }
+#endif
+}
+
+base::threading::Mutex& LogDispatchCallback::fileHandle(const LogDispatchData* data) {
+  auto it = m_fileLocks.find(data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level()));
+  return *(it->second.get());
+}
+
+namespace base {
 // DefaultLogDispatchCallback
 
 void DefaultLogDispatchCallback::handle(const LogDispatchData* data) {
+#if defined(ELPP_THREAD_SAFE)
+  LogDispatchCallback::handle(data);
+  base::threading::ScopedLock scopedLock(fileHandle(data));
+#endif
   m_data = data;
   dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(),
            m_data->dispatchAction() == base::DispatchAction::NormalLog));
@@ -2348,7 +2456,7 @@ base::type::string_t DefaultLogBuilder::build(const LogMessage* logMessage, bool
     base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier, logMessage->message());
   }
 #if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS)
-  el::base::threading::ScopedLock lock_(ELPP->lock());
+  el::base::threading::ScopedLock lock_(ELPP->customFormatSpecifiersLock());
   ELPP_UNUSED(lock_);
   for (std::vector<CustomFormatSpecifier>::const_iterator it = ELPP->customFormatSpecifiers()->begin();
        it != ELPP->customFormatSpecifiers()->end(); ++it) {
@@ -2370,9 +2478,15 @@ void LogDispatcher::dispatch(void) {
   if (!m_proceed) {
     return;
   }
-  base::TypedConfigurations* tc = m_logMessage.logger()->m_typedConfigurations;
+#ifndef ELPP_NO_GLOBAL_LOCK
+  // see https://github.com/muflihun/easyloggingpp/issues/580
+  // global lock is turned off by default unless
+  // ELPP_NO_GLOBAL_LOCK is defined
+  base::threading::ScopedLock scopedLock(ELPP->lock());
+#endif
+  base::TypedConfigurations* tc = m_logMessage->logger()->m_typedConfigurations;
   if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) {
-    tc->validateFileRolling(m_logMessage.level(), ELPP->preRollOutCallback());
+    tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback());
   }
   LogDispatchCallback* callback = nullptr;
   LogDispatchData data;
@@ -2380,7 +2494,7 @@ void LogDispatcher::dispatch(void) {
        : ELPP->m_logDispatchCallbacks) {
     callback = h.second.get();
     if (callback != nullptr && callback->enabled()) {
-      data.setLogMessage(&m_logMessage);
+      data.setLogMessage(m_logMessage);
       data.setDispatchAction(m_dispatchAction);
       callback->handle(&data);
     }
@@ -2427,6 +2541,7 @@ Writer& Writer::construct(int count, const char* loggerIds, ...) {
     va_list loggersList;
     va_start(loggersList, loggerIds);
     const char* id = loggerIds;
+    m_loggerIds.reserve(count);
     for (int i = 0; i < count; ++i) {
       m_loggerIds.push_back(std::string(id));
       id = va_arg(loggersList, const char*);
@@ -2446,7 +2561,6 @@ void Writer::initializeLogger(const std::string& loggerId, bool lookup, bool nee
   }
   if (m_logger == nullptr) {
     {
-      base::threading::ScopedLock scopedLock(ELPP->lock());
       if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) {
         // Somehow default logger has been unregistered. Not good! Register again
         ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId));
@@ -2512,8 +2626,13 @@ void Writer::processDispatch() {
 
 void Writer::triggerDispatch(void) {
   if (m_proceed) {
-    base::LogDispatcher(m_proceed, LogMessage(m_level, m_file, m_line, m_func, m_verboseLevel,
-                        m_logger), m_dispatchAction).dispatch();
+    if (m_msg == nullptr) {
+      LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel,
+                     m_logger);
+      base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch();
+    } else {
+      base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch();
+    }
   }
   if (m_logger != nullptr) {
     m_logger->stream().str(ELPP_LITERAL(""));
@@ -2642,18 +2761,19 @@ namespace debug {
 
 // StackTrace
 
-StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const char* loc, const char* demang, const char* hex,
-    const char* addr) {
-  m_index = index;
-  m_location = std::string(loc);
-  m_demangled = std::string(demang);
-  m_hex = std::string(hex);
-  m_addr = std::string(addr);
+StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang,
+    const std::string& hex,
+    const std::string& addr) :
+  m_index(index),
+  m_location(loc),
+  m_demangled(demang),
+  m_hex(hex),
+  m_addr(addr) {
 }
 
 std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) {
-  ss << "[" << si.m_index << "] " << si.m_location << (si.m_demangled.empty() ? "" : ":") << si.m_demangled
-     << (si.m_hex.empty() ? "" : "+") << si.m_hex << si.m_addr;
+  ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr <<
+     (si.m_demangled.empty() ? "" : ":") << si.m_demangled;
   return ss;
 }
 
@@ -2673,44 +2793,40 @@ void StackTrace::generateNew(void) {
   char** strings = backtrace_symbols(stack, size);
   if (size > kStackStart) {  // Skip StackTrace c'tor and generateNew
     for (std::size_t i = kStackStart; i < size; ++i) {
-      char* mangName = nullptr;
-      char* hex = nullptr;
-      char* addr = nullptr;
-      for (char* c = strings[i]; *c; ++c) {
-        switch (*c) {
-        case '(':
-          mangName = c;
-          break;
-        case '+':
-          hex = c;
-          break;
-        case ')':
-          addr = c;
-          break;
-        default:
-          break;
-        }
+      std::string mangName;
+      std::string location;
+      std::string hex;
+      std::string addr;
+
+      // entry: 2   crash.cpp.bin                       0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21
+      const std::string line(strings[i]);
+      auto p = line.find("_");
+      if (p != std::string::npos) {
+        mangName = line.substr(p);
+        mangName = mangName.substr(0, mangName.find(" +"));
+      }
+      p = line.find("0x");
+      if (p != std::string::npos) {
+        addr = line.substr(p);
+        addr = addr.substr(0, addr.find("_"));
       }
       // Perform demangling if parsed properly
-      if (mangName != nullptr && hex != nullptr && addr != nullptr && mangName < hex) {
-        *mangName++ = '\0';
-        *hex++ = '\0';
-        *addr++ = '\0';
+      if (!mangName.empty()) {
         int status = 0;
-        char* demangName = abi::__cxa_demangle(mangName, 0, 0, &status);
+        char* demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status);
         // if demangling is successful, output the demangled function name
         if (status == 0) {
           // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html)
-          StackTraceEntry entry(i - 1, strings[i], demangName, hex, addr);
+          StackTraceEntry entry(i - 1, location, demangName, hex, addr);
           m_stack.push_back(entry);
         } else {
           // Not successful - we will use mangled name
-          StackTraceEntry entry(i - 1, strings[i], mangName, hex, addr);
+          StackTraceEntry entry(i - 1, location, mangName, hex, addr);
           m_stack.push_back(entry);
         }
         free(demangName);
       } else {
-        StackTraceEntry entry(i - 1, strings[i]);
+        StackTraceEntry entry(i - 1, line);
         m_stack.push_back(entry);
       }
     }
@@ -2744,6 +2860,9 @@ static std::string crashReason(int sig) {
 }
 /// @brief Logs reason of crash from sig
 static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) {
+  if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) {
+    return;
+  }
   std::stringstream ss;
   ss << "CRASH HANDLED; ";
   ss << crashReason(sig);
@@ -2822,7 +2941,6 @@ void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, c
 // Loggers
 
 Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) {
-  base::threading::ScopedLock scopedLock(ELPP->lock());
   return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable);
 }
 
@@ -2831,12 +2949,10 @@ void Loggers::setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr) {
 }
 
 bool Loggers::unregisterLogger(const std::string& identity) {
-  base::threading::ScopedLock scopedLock(ELPP->lock());
   return ELPP->registeredLoggers()->remove(identity);
 }
 
 bool Loggers::hasLogger(const std::string& identity) {
-  base::threading::ScopedLock scopedLock(ELPP->lock());
   return ELPP->registeredLoggers()->has(identity);
 }
 
@@ -2986,11 +3102,11 @@ void Loggers::clearVModules(void) {
 // VersionInfo
 
 const std::string VersionInfo::version(void) {
-  return std::string("9.95.3");
+  return std::string("9.96.7");
 }
 /// @brief Release date of current version
 const std::string VersionInfo::releaseDate(void) {
-  return std::string("13-10-2017 1134hrs");
+  return std::string("24-11-2018 0728hrs");
 }
 
 } // namespace el

+ 117 - 154
import/EasyLogging++/easylogging++.h

@@ -1,25 +1,25 @@
 //
 //  Bismillah ar-Rahmaan ar-Raheem
 //
-//  Easylogging++ v9.95.3
+//  Easylogging++ v9.96.7
 //  Single-header only, cross-platform logging library for C++ applications
 //
-//  Copyright (c) 2017 muflihun.com
+//  Copyright (c) 2012-2018 Zuhd Web Services
+//  Copyright (c) 2012-2018 @abumusamq
 //
 //  This library is released under the MIT Licence.
-//  http://labs.muflihun.com/easyloggingpp/licence.php
+//  https://github.com/zuhd-org/easyloggingpp/blob/master/LICENSE
 //
-//  https://github.com/muflihun/easyloggingpp
-//  https://muflihun.github.io/easyloggingpp
+//  https://zuhd.org
 //  http://muflihun.com
 //
+
 #ifndef EASYLOGGINGPP_H
 #define EASYLOGGINGPP_H
 
-#define ELPP_FEATURE_CRASH_LOG
-#define ELPP_HANDLE_SIGABRT
-#define ELPP_DEFAULT_LOG_FILE "dat_library.log"
-#define ELPP_DEBUG_ERRORS
+#define ELPP_HANDLE_SIGABRT 
+#define ELPP_UNICODE
+#define ELPP_NO_DEFAULT_LOG_FILE
 
 // Compilers and C++0x/C++11 Evaluation
 #if __cplusplus >= 201103L
@@ -119,8 +119,13 @@
 #else
 #  define ELPP_OS_NETBSD 0
 #endif
+#if defined(__EMSCRIPTEN__)
+#  define ELPP_OS_EMSCRIPTEN 1
+#else
+#  define ELPP_OS_EMSCRIPTEN 0
+#endif
 // Unix
-#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_NETBSD || ELPP_OS_SOLARIS || ELPP_OS_AIX) && (!ELPP_OS_WINDOWS))
+#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_NETBSD || ELPP_OS_SOLARIS || ELPP_OS_AIX || ELPP_OS_EMSCRIPTEN) && (!ELPP_OS_WINDOWS))
 #  define ELPP_OS_UNIX 1
 #else
 #  define ELPP_OS_UNIX 0
@@ -205,7 +210,7 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre
 #  define ELPP_INTERNAL_INFO(lvl, msg)
 #endif  // (defined(ELPP_DEBUG_INFO))
 #if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG))
-#  if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_OS_ANDROID)
+#  if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_OS_ANDROID && !ELPP_OS_EMSCRIPTEN)
 #    define ELPP_STACKTRACE 1
 #  else
 #      if ELPP_COMPILER_MSVC
@@ -384,6 +389,7 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre
 #include <string>
 #include <vector>
 #include <map>
+#include <unordered_map>
 #include <utility>
 #include <functional>
 #include <algorithm>
@@ -421,9 +427,6 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre
 #  if defined(ELPP_LOG_STD_ARRAY)
 #      include <array>
 #  endif  // defined(ELPP_LOG_STD_ARRAY)
-#  if defined(ELPP_LOG_UNORDERED_MAP)
-#      include <unordered_map>
-#  endif  // defined(ELPP_LOG_UNORDERED_MAP)
 #  if defined(ELPP_LOG_UNORDERED_SET)
 #      include <unordered_set>
 #  endif  // defined(ELPP_UNORDERED_SET)
@@ -591,6 +594,16 @@ enum class Level : base::type::EnumType {
   /// @brief Represents unknown level
   Unknown = 1010
 };
+} // namespace el
+namespace std {
+template<> struct hash<el::Level> {
+ public:
+  std::size_t operator()(const el::Level& l) const {
+    return hash<el::base::type::EnumType> {}(static_cast<el::base::type::EnumType>(l));
+  }
+};
+}
+namespace el {
 /// @brief Static class that contains helper functions for el::Level
 class LevelHelper : base::StaticClass {
  public:
@@ -713,115 +726,43 @@ enum class LoggingFlag : base::type::EnumType {
   /// @brief Adds spaces b/w logs that separated by left-shift operator
   AutoSpacing = 8192,
   /// @brief Preserves time format and does not convert it to sec, hour etc (performance tracking only)
-  FixedTimeFormat = 16384
+  FixedTimeFormat = 16384,
+  // @brief Ignore SIGINT or crash
+  IgnoreSigInt = 32768,
 };
 namespace base {
 /// @brief Namespace containing constants used internally.
 namespace consts {
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-variable"
-#endif
-// Level log values - These are values that are replaced in place of %level format specifier
-// Extra spaces after format specifiers are only for readability purposes in log files
-static const base::type::char_t* kInfoLevelLogValue     =   ELPP_LITERAL("INFO");
-static const base::type::char_t* kDebugLevelLogValue    =   ELPP_LITERAL("DEBUG");
-static const base::type::char_t* kWarningLevelLogValue  =   ELPP_LITERAL("WARNING");
-static const base::type::char_t* kErrorLevelLogValue    =   ELPP_LITERAL("ERROR");
-static const base::type::char_t* kFatalLevelLogValue    =   ELPP_LITERAL("FATAL");
-static const base::type::char_t* kVerboseLevelLogValue  =
-  ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level
-static const base::type::char_t* kTraceLevelLogValue    =   ELPP_LITERAL("TRACE");
-static const base::type::char_t* kInfoLevelShortLogValue     =   ELPP_LITERAL("I");
-static const base::type::char_t* kDebugLevelShortLogValue    =   ELPP_LITERAL("D");
-static const base::type::char_t* kWarningLevelShortLogValue  =   ELPP_LITERAL("W");
-static const base::type::char_t* kErrorLevelShortLogValue    =   ELPP_LITERAL("E");
-static const base::type::char_t* kFatalLevelShortLogValue    =   ELPP_LITERAL("F");
-static const base::type::char_t* kVerboseLevelShortLogValue  =   ELPP_LITERAL("V");
-static const base::type::char_t* kTraceLevelShortLogValue    =   ELPP_LITERAL("T");
-// Format specifiers - These are used to define log format
-static const base::type::char_t* kAppNameFormatSpecifier          =      ELPP_LITERAL("%app");
-static const base::type::char_t* kLoggerIdFormatSpecifier         =      ELPP_LITERAL("%logger");
-static const base::type::char_t* kThreadIdFormatSpecifier         =      ELPP_LITERAL("%thread");
-static const base::type::char_t* kSeverityLevelFormatSpecifier    =      ELPP_LITERAL("%level");
-static const base::type::char_t* kSeverityLevelShortFormatSpecifier    =      ELPP_LITERAL("%levshort");
-static const base::type::char_t* kDateTimeFormatSpecifier         =      ELPP_LITERAL("%datetime");
-static const base::type::char_t* kLogFileFormatSpecifier          =      ELPP_LITERAL("%file");
-static const base::type::char_t* kLogFileBaseFormatSpecifier      =      ELPP_LITERAL("%fbase");
-static const base::type::char_t* kLogLineFormatSpecifier          =      ELPP_LITERAL("%line");
-static const base::type::char_t* kLogLocationFormatSpecifier      =      ELPP_LITERAL("%loc");
-static const base::type::char_t* kLogFunctionFormatSpecifier      =      ELPP_LITERAL("%func");
-static const base::type::char_t* kCurrentUserFormatSpecifier      =      ELPP_LITERAL("%user");
-static const base::type::char_t* kCurrentHostFormatSpecifier      =      ELPP_LITERAL("%host");
-static const base::type::char_t* kMessageFormatSpecifier          =      ELPP_LITERAL("%msg");
-static const base::type::char_t* kVerboseLevelFormatSpecifier     =      ELPP_LITERAL("%vlevel");
-static const char* kDateTimeFormatSpecifierForFilename            =      "%datetime";
-// Date/time
-static const char* kDays[7]                         =      { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
-static const char* kDaysAbbrev[7]                   =      { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-static const char* kMonths[12]                      =      { "January", "February", "March", "Apri", "May", "June", "July", "August",
-                                                             "September", "October", "November", "December"
-                                                           };
-static const char* kMonthsAbbrev[12]                =      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-static const char* kDefaultDateTimeFormat           =      "%Y-%M-%d %H:%m:%s,%g";
-static const char* kDefaultDateTimeFormatInFilename =      "%Y-%M-%d_%H-%m";
-static const int kYearBase                          =      1900;
-static const char* kAm                              =      "AM";
-static const char* kPm                              =      "PM";
-// Miscellaneous constants
+static const char  kFormatSpecifierCharValue               =      'v';
+static const char  kFormatSpecifierChar                    =      '%';
+static const unsigned int kMaxLogPerCounter                =      100000;
+static const unsigned int kMaxLogPerContainer              =      100;
+static const unsigned int kDefaultSubsecondPrecision       =      3;
+
 #ifdef ELPP_DEFAULT_LOGGER
 static const char* kDefaultLoggerId                        =      ELPP_DEFAULT_LOGGER;
 #else
 static const char* kDefaultLoggerId                        =      "default";
 #endif
+
+#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
 #ifdef ELPP_DEFAULT_PERFORMANCE_LOGGER
 static const char* kPerformanceLoggerId                    =      ELPP_DEFAULT_PERFORMANCE_LOGGER;
 #else
 static const char* kPerformanceLoggerId                    =      "performance";
+#endif // ELPP_DEFAULT_PERFORMANCE_LOGGER
 #endif
+
 #if defined(ELPP_SYSLOG)
 static const char* kSysLogLoggerId                         =      "syslog";
 #endif  // defined(ELPP_SYSLOG)
-static const char* kNullPointer                            =      "nullptr";
-static const char  kFormatSpecifierChar                    =      '%';
-#if ELPP_VARIADIC_TEMPLATES_SUPPORTED
-static const char  kFormatSpecifierCharValue               =      'v';
-#endif  // ELPP_VARIADIC_TEMPLATES_SUPPORTED
-static const unsigned int kMaxLogPerContainer              =      100;
-static const unsigned int kMaxLogPerCounter                =      100000;
-static const unsigned int kDefaultSubsecondPrecision       =      3;
-static const base::type::VerboseLevel kMaxVerboseLevel     =      9;
-static const char* kUnknownUser                            =      "user";
-static const char* kUnknownHost                            =      "unknown-host";
-#if defined(ELPP_DEFAULT_LOG_FILE)
-static const char* kDefaultLogFile                         =      ELPP_DEFAULT_LOG_FILE;
-#else
-#  if ELPP_OS_UNIX
-#      if ELPP_OS_ANDROID
-static const char* kDefaultLogFile                         =      "logs/myeasylog.log";
-#      else
-static const char* kDefaultLogFile                         =      "logs/myeasylog.log";
-#      endif  // ELPP_OS_ANDROID
-#  elif ELPP_OS_WINDOWS
-static const char* kDefaultLogFile                         =      "logs\\myeasylog.log";
-#  endif  // ELPP_OS_UNIX
-#endif  // defined(ELPP_DEFAULT_LOG_FILE)
-#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
-static const char* kDefaultLogFileParam                    =      "--default-log-file";
-#endif  // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
-#if defined(ELPP_LOGGING_FLAGS_FROM_ARG)
-static const char* kLoggingFlagsParam                      =      "--logging-flags";
-#endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
+
 #if ELPP_OS_WINDOWS
 static const char* kFilePathSeperator                      =      "\\";
 #else
 static const char* kFilePathSeperator                      =      "/";
 #endif  // ELPP_OS_WINDOWS
-static const char* kValidLoggerIdSymbols                   =
-  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._";
-static const char* kConfigurationComment                   =      "##";
-static const char* kConfigurationLevel                     =      "*";
-static const char* kConfigurationLoggerId                  =      "--";
+
 static const std::size_t kSourceFilenameMaxLength          =      100;
 static const std::size_t kSourceLineMaxLength              =      10;
 static const Level kPerformanceTrackerDefaultLevel         =      Level::Info;
@@ -866,9 +807,6 @@ const struct {
   },
 };
 static const int kCrashSignalsCount                          =      sizeof(kCrashSignals) / sizeof(kCrashSignals[0]);
-#if defined(__GNUC__) && !defined(__clang__)
-#pragma GCC diagnostic pop
-#endif
 }  // namespace consts
 }  // namespace base
 typedef std::function<void(const char*, std::size_t)> PreRollOutCallback;
@@ -1268,8 +1206,8 @@ class DateTime : base::StaticClass {
       base::TimestampUnit timestampUnit);
 
 
- private:
   static struct ::tm* buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo);
+ private:
   static char* parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo,
                            std::size_t msec, const base::SubsecondPrecision* ssPrec);
 };
@@ -1308,7 +1246,7 @@ class CommandLineArgs {
  private:
   int m_argc;
   char** m_argv;
-  std::map<std::string, std::string> m_paramsWithValue;
+  std::unordered_map<std::string, std::string> m_paramsWithValue;
   std::vector<std::string> m_params;
 };
 /// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type.
@@ -1433,7 +1371,7 @@ class AbstractRegistry : public base::threading::ThreadSafe {
 ///         of AbstractRegistry<T_Ptr, Container>. Any implementation of this class should be
 ///         explicitly (by using lock functions)
 template <typename T_Ptr, typename T_Key = const char*>
-class Registry : public AbstractRegistry<T_Ptr, std::map<T_Key, T_Ptr*>> {
+class Registry : public AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr*>> {
  public:
   typedef typename Registry<T_Ptr, T_Key>::iterator iterator;
   typedef typename Registry<T_Ptr, T_Key>::const_iterator const_iterator;
@@ -1497,7 +1435,7 @@ class Registry : public AbstractRegistry<T_Ptr, std::map<T_Key, T_Ptr*>> {
   }
 
  private:
-  virtual void deepCopy(const AbstractRegistry<T_Ptr, std::map<T_Key, T_Ptr*>>& sr) ELPP_FINAL {
+  virtual void deepCopy(const AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr*>>& sr) ELPP_FINAL {
     for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it) {
       registerNew(it->first, new T_Ptr(*it->second));
     }
@@ -1597,7 +1535,7 @@ class RegistryWithPred : public AbstractRegistry<T_Ptr, std::vector<T_Ptr*>> {
 class Utils {
  public:
   template <typename T, typename TPtr>
-  static bool installCallback(const std::string& id, std::map<std::string, TPtr>* mapT) {
+  static bool installCallback(const std::string& id, std::unordered_map<std::string, TPtr>* mapT) {
     if (mapT->find(id) == mapT->end()) {
       mapT->insert(std::make_pair(id, TPtr(new T())));
       return true;
@@ -1606,15 +1544,15 @@ class Utils {
   }
 
   template <typename T, typename TPtr>
-  static void uninstallCallback(const std::string& id, std::map<std::string, TPtr>* mapT) {
+  static void uninstallCallback(const std::string& id, std::unordered_map<std::string, TPtr>* mapT) {
     if (mapT->find(id) != mapT->end()) {
       mapT->erase(id);
     }
   }
 
   template <typename T, typename TPtr>
-  static T* callback(const std::string& id, std::map<std::string, TPtr>* mapT) {
-    typename std::map<std::string, TPtr>::iterator iter = mapT->find(id);
+  static T* callback(const std::string& id, std::unordered_map<std::string, TPtr>* mapT) {
+    typename std::unordered_map<std::string, TPtr>::iterator iter = mapT->find(id);
     if (iter != mapT->end()) {
       return static_cast<T*>(iter->second.get());
     }
@@ -1959,7 +1897,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
 
 namespace base {
 typedef std::shared_ptr<base::type::fstream_t> FileStreamPtr;
-typedef std::map<std::string, FileStreamPtr> LogStreamsReferenceMap;
+typedef std::unordered_map<std::string, FileStreamPtr> LogStreamsReferenceMap;
 /// @brief Configurations with data types.
 ///
 /// @detail el::Configurations have string based values. This is whats used internally in order to read correct configurations.
@@ -1996,16 +1934,16 @@ class TypedConfigurations : public base::threading::ThreadSafe {
 
  private:
   Configurations* m_configurations;
-  std::map<Level, bool> m_enabledMap;
-  std::map<Level, bool> m_toFileMap;
-  std::map<Level, std::string> m_filenameMap;
-  std::map<Level, bool> m_toStandardOutputMap;
-  std::map<Level, base::LogFormat> m_logFormatMap;
-  std::map<Level, base::SubsecondPrecision> m_subsecondPrecisionMap;
-  std::map<Level, bool> m_performanceTrackingMap;
-  std::map<Level, base::FileStreamPtr> m_fileStreamMap;
-  std::map<Level, std::size_t> m_maxLogFileSizeMap;
-  std::map<Level, std::size_t> m_logFlushThresholdMap;
+  std::unordered_map<Level, bool> m_enabledMap;
+  std::unordered_map<Level, bool> m_toFileMap;
+  std::unordered_map<Level, std::string> m_filenameMap;
+  std::unordered_map<Level, bool> m_toStandardOutputMap;
+  std::unordered_map<Level, base::LogFormat> m_logFormatMap;
+  std::unordered_map<Level, base::SubsecondPrecision> m_subsecondPrecisionMap;
+  std::unordered_map<Level, bool> m_performanceTrackingMap;
+  std::unordered_map<Level, base::FileStreamPtr> m_fileStreamMap;
+  std::unordered_map<Level, std::size_t> m_maxLogFileSizeMap;
+  std::unordered_map<Level, std::size_t> m_logFlushThresholdMap;
   base::LogStreamsReferenceMap* m_logStreamsReference;
 
   friend class el::Helpers;
@@ -2015,21 +1953,21 @@ class TypedConfigurations : public base::threading::ThreadSafe {
   friend class el::base::LogDispatcher;
 
   template <typename Conf_T>
-  inline Conf_T getConfigByVal(Level level, const std::map<Level, Conf_T>* confMap, const char* confName) {
+  inline Conf_T getConfigByVal(Level level, const std::unordered_map<Level, Conf_T>* confMap, const char* confName) {
     base::threading::ScopedLock scopedLock(lock());
     return unsafeGetConfigByVal(level, confMap, confName);  // This is not unsafe anymore - mutex locked in scope
   }
 
   template <typename Conf_T>
-  inline Conf_T& getConfigByRef(Level level, std::map<Level, Conf_T>* confMap, const char* confName) {
+  inline Conf_T& getConfigByRef(Level level, std::unordered_map<Level, Conf_T>* confMap, const char* confName) {
     base::threading::ScopedLock scopedLock(lock());
     return unsafeGetConfigByRef(level, confMap, confName);  // This is not unsafe anymore - mutex locked in scope
   }
 
   template <typename Conf_T>
-  Conf_T unsafeGetConfigByVal(Level level, const std::map<Level, Conf_T>* confMap, const char* confName) {
+  Conf_T unsafeGetConfigByVal(Level level, const std::unordered_map<Level, Conf_T>* confMap, const char* confName) {
     ELPP_UNUSED(confName);
-    typename std::map<Level, Conf_T>::const_iterator it = confMap->find(level);
+    typename std::unordered_map<Level, Conf_T>::const_iterator it = confMap->find(level);
     if (it == confMap->end()) {
       try {
         return confMap->at(Level::Global);
@@ -2044,9 +1982,9 @@ class TypedConfigurations : public base::threading::ThreadSafe {
   }
 
   template <typename Conf_T>
-  Conf_T& unsafeGetConfigByRef(Level level, std::map<Level, Conf_T>* confMap, const char* confName) {
+  Conf_T& unsafeGetConfigByRef(Level level, std::unordered_map<Level, Conf_T>* confMap, const char* confName) {
     ELPP_UNUSED(confName);
-    typename std::map<Level, Conf_T>::iterator it = confMap->find(level);
+    typename std::unordered_map<Level, Conf_T>::iterator it = confMap->find(level);
     if (it == confMap->end()) {
       try {
         return confMap->at(Level::Global);
@@ -2060,14 +1998,15 @@ class TypedConfigurations : public base::threading::ThreadSafe {
   }
 
   template <typename Conf_T>
-  void setValue(Level level, const Conf_T& value, std::map<Level, Conf_T>* confMap, bool includeGlobalLevel = true) {
+  void setValue(Level level, const Conf_T& value, std::unordered_map<Level, Conf_T>* confMap,
+                bool includeGlobalLevel = true) {
     // If map is empty and we are allowed to add into generic level (Level::Global), do it!
     if (confMap->empty() && includeGlobalLevel) {
       confMap->insert(std::make_pair(Level::Global, value));
       return;
     }
     // If same value exist in generic level already, dont add it to explicit level
-    typename std::map<Level, Conf_T>::iterator it = confMap->find(Level::Global);
+    typename std::unordered_map<Level, Conf_T>::iterator it = confMap->find(Level::Global);
     if (it != confMap->end() && it->second == value) {
       return;
     }
@@ -2229,21 +2168,26 @@ class LogDispatchData {
   inline base::DispatchAction dispatchAction(void) const {
     return m_dispatchAction;
   }
- private:
-  LogMessage* m_logMessage;
-  base::DispatchAction m_dispatchAction;
-  friend class base::LogDispatcher;
-
   inline void setLogMessage(LogMessage* logMessage) {
     m_logMessage = logMessage;
   }
   inline void setDispatchAction(base::DispatchAction dispatchAction) {
     m_dispatchAction = dispatchAction;
   }
+ private:
+  LogMessage* m_logMessage;
+  base::DispatchAction m_dispatchAction;
+  friend class base::LogDispatcher;
+
 };
 class LogDispatchCallback : public Callback<LogDispatchData> {
+ protected:
+  virtual void handle(const LogDispatchData* data);
+  base::threading::Mutex& fileHandle(const LogDispatchData* data);
  private:
   friend class base::LogDispatcher;
+  std::unordered_map<std::string, std::unique_ptr<base::threading::Mutex>> m_fileLocks;
+  base::threading::Mutex m_fileLocksMapLock;
 };
 class PerformanceTrackingCallback : public Callback<PerformanceTrackingData> {
  private:
@@ -2361,7 +2305,7 @@ inline void FUNCTION_NAME(const T&);
   std::string m_parentApplicationName;
   bool m_isConfigured;
   Configurations m_configurations;
-  std::map<Level, unsigned int> m_unflushedCount;
+  std::unordered_map<Level, unsigned int> m_unflushedCount;
   base::LogStreamsReferenceMap* m_logStreamsReference;
   LogBuilderPtr m_logBuilder;
 
@@ -2467,7 +2411,7 @@ class RegisteredLoggers : public base::utils::Registry<Logger, std::string> {
   LogBuilderPtr m_defaultLogBuilder;
   Configurations m_defaultConfigurations;
   base::LogStreamsReferenceMap m_logStreamsReference;
-  std::map<std::string, base::type::LoggerRegistrationCallbackPtr> m_loggerRegistrationCallbacks;
+  std::unordered_map<std::string, base::type::LoggerRegistrationCallbackPtr> m_loggerRegistrationCallbacks;
   friend class el::base::Storage;
 
   void unsafeFlushAll(void);
@@ -2493,7 +2437,7 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe {
 
   bool allowed(base::type::VerboseLevel vlevel, const char* file);
 
-  inline const std::map<std::string, base::type::VerboseLevel>& modules(void) const {
+  inline const std::unordered_map<std::string, base::type::VerboseLevel>& modules(void) const {
     return m_modules;
   }
 
@@ -2507,7 +2451,7 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe {
  private:
   base::type::VerboseLevel m_level;
   base::type::EnumType* m_pFlags;
-  std::map<std::string, base::type::VerboseLevel> m_modules;
+  std::unordered_map<std::string, base::type::VerboseLevel> m_modules;
 };
 }  // namespace base
 class LogMessage {
@@ -2691,6 +2635,10 @@ class Storage : base::NoCopy, public base::threading::ThreadSafe {
     return &m_customFormatSpecifiers;
   }
 
+  base::threading::Mutex& customFormatSpecifiersLock() {
+    return m_customFormatSpecifiersLock;
+  }
+
   inline void setLoggingLevel(Level level) {
     m_loggingLevel = level;
   }
@@ -2731,12 +2679,13 @@ class Storage : base::NoCopy, public base::threading::ThreadSafe {
   /// @brief Sets thread name for current thread. Requires std::thread
   inline void setThreadName(const std::string& name) {
     if (name.empty()) return;
-    base::threading::ScopedLock scopedLock(lock());
+    base::threading::ScopedLock scopedLock(m_threadNamesLock);
     m_threadNames[base::threading::getCurrentThreadId()] = name;
   }
 
   inline std::string getThreadName(const std::string& threadId) {
-    std::map<std::string, std::string>::const_iterator it = m_threadNames.find(threadId);
+    base::threading::ScopedLock scopedLock(m_threadNamesLock);
+    std::unordered_map<std::string, std::string>::const_iterator it = m_threadNames.find(threadId);
     if (it == m_threadNames.end()) {
       return threadId;
     }
@@ -2753,10 +2702,12 @@ class Storage : base::NoCopy, public base::threading::ThreadSafe {
 #endif  // ELPP_ASYNC_LOGGING
   base::utils::CommandLineArgs m_commandLineArgs;
   PreRollOutCallback m_preRollOutCallback;
-  std::map<std::string, base::type::LogDispatchCallbackPtr> m_logDispatchCallbacks;
-  std::map<std::string, base::type::PerformanceTrackingCallbackPtr> m_performanceTrackingCallbacks;
-  std::map<std::string, std::string> m_threadNames;
+  std::unordered_map<std::string, base::type::LogDispatchCallbackPtr> m_logDispatchCallbacks;
+  std::unordered_map<std::string, base::type::PerformanceTrackingCallbackPtr> m_performanceTrackingCallbacks;
+  std::unordered_map<std::string, std::string> m_threadNames;
   std::vector<CustomFormatSpecifier> m_customFormatSpecifiers;
+  base::threading::Mutex m_customFormatSpecifiersLock;
+  base::threading::Mutex m_threadNamesLock;
   Level m_loggingLevel;
 
   friend class el::Helpers;
@@ -2799,7 +2750,7 @@ class AsyncDispatchWorker : public base::IWorker, public base::threading::Thread
   void run(void);
 
   void setContinueRunning(bool value) {
-    base::threading::ScopedLock scopedLock(m_continueRunningMutex);
+    base::threading::ScopedLock scopedLock(m_continueRunningLock);
     m_continueRunning = value;
   }
 
@@ -2809,7 +2760,7 @@ class AsyncDispatchWorker : public base::IWorker, public base::threading::Thread
  private:
   std::condition_variable cv;
   bool m_continueRunning;
-  base::threading::Mutex m_continueRunningMutex;
+  base::threading::Mutex m_continueRunningLock;
 };
 #endif  // ELPP_ASYNC_LOGGING
 }  // namespace base
@@ -2821,9 +2772,9 @@ class DefaultLogBuilder : public LogBuilder {
 /// @brief Dispatches log messages
 class LogDispatcher : base::NoCopy {
  public:
-  LogDispatcher(bool proceed, LogMessage&& logMessage, base::DispatchAction dispatchAction) :
+  LogDispatcher(bool proceed, LogMessage* logMessage, base::DispatchAction dispatchAction) :
     m_proceed(proceed),
-    m_logMessage(std::move(logMessage)),
+    m_logMessage(logMessage),
     m_dispatchAction(std::move(dispatchAction)) {
   }
 
@@ -2831,7 +2782,7 @@ class LogDispatcher : base::NoCopy {
 
  private:
   bool m_proceed;
-  LogMessage m_logMessage;
+  LogMessage* m_logMessage;
   base::DispatchAction m_dispatchAction;
 };
 #if defined(ELPP_STL_LOGGING)
@@ -3244,10 +3195,15 @@ class Writer : base::NoCopy {
   Writer(Level level, const char* file, base::type::LineNumber line,
          const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog,
          base::type::VerboseLevel verboseLevel = 0) :
-    m_level(level), m_file(file), m_line(line), m_func(func), m_verboseLevel(verboseLevel),
+    m_msg(nullptr), m_level(level), m_file(file), m_line(line), m_func(func), m_verboseLevel(verboseLevel),
     m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) {
   }
 
+  Writer(LogMessage* msg, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog) :
+    m_msg(msg), m_level(msg != nullptr ? msg->level() : Level::Unknown),
+    m_line(0), m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) {
+  }
+
   virtual ~Writer(void) {
     processDispatch();
   }
@@ -3278,6 +3234,7 @@ class Writer : base::NoCopy {
   Writer& construct(Logger* logger, bool needLock = true);
   Writer& construct(int count, const char* loggerIds, ...);
  protected:
+  LogMessage* m_msg;
   Level m_level;
   const char* m_file;
   const base::type::LineNumber m_line;
@@ -3615,8 +3572,9 @@ class StackTrace : base::NoCopy {
   static const unsigned int kStackStart = 2;  // We want to skip c'tor and StackTrace::generateNew()
   class StackTraceEntry {
    public:
-    StackTraceEntry(std::size_t index, const char* loc, const char* demang, const char* hex, const char* addr);
-    StackTraceEntry(std::size_t index, char* loc) :
+    StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, const std::string& hex,
+                    const std::string& addr);
+    StackTraceEntry(std::size_t index, const std::string& loc) :
       m_index(index),
       m_location(loc) {
     }
@@ -3801,6 +3759,11 @@ class Helpers : base::StaticClass {
   static inline const el::base::utils::CommandLineArgs* commandLineArgs(void) {
     return ELPP->commandLineArgs();
   }
+  /// @brief Reserve space for custom format specifiers for performance
+  /// @see std::vector::reserve
+  static inline void reserveCustomFormatSpecifiers(std::size_t size) {
+    ELPP->m_customFormatSpecifiers.reserve(size);
+  }
   /// @brief Installs user defined format specifier and handler
   static inline void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) {
     ELPP->installCustomFormatSpecifier(customFormatSpecifier);
@@ -3814,7 +3777,7 @@ class Helpers : base::StaticClass {
     return ELPP->hasCustomFormatSpecifier(formatSpecifier);
   }
   static inline void validateFileRolling(Logger* logger, Level level) {
-    if (logger == nullptr) return;
+    if (ELPP == nullptr || logger == nullptr) return;
     logger->m_typedConfigurations->validateFileRolling(level, ELPP->preRollOutCallback());
   }
 };

+ 74 - 11
import/LotroDat/DatSubsystems/DatStatus.h

@@ -2,6 +2,8 @@
 #define LOTRO_DAT_LIBRARY_DATSTATUS_H
 
 #include <string>
+#include <functional>
+#include <set>
 
 namespace LOTRO_DAT {
     class DatFile;
@@ -31,6 +33,50 @@ namespace LOTRO_DAT {
             E_FREE
         };
 
+        struct ProgressInfo {
+            DAT_STATUS status;
+            double percentage;
+            unsigned long long finished_parts;
+            unsigned long long total_parts;
+        };
+
+        using Callback = std::function<void(ProgressInfo)>; 
+        
+        struct CallbackStorage{
+        public:
+            CallbackStorage(Callback function_handle) {
+                func_ = function_handle;
+                handler_ = free_handler_++;
+            }
+
+            CallbackStorage(unsigned long long handler) {
+                func_ = nullptr;
+                handler_ = handler;
+            }
+
+            unsigned long long GetHandler() const {
+                return handler_;
+            }
+
+            bool operator<(const CallbackStorage& other) const {
+                return handler_ < other.handler_;
+            }
+
+            bool operator==(const CallbackStorage& other) const {
+                return handler_ == other.handler_;
+            }
+            
+            template<class ... Types>
+            void operator()(Types ... args) const {
+                func_(args...);
+            }
+
+        private:
+            Callback func_;
+            unsigned long long handler_;
+            static unsigned long long free_handler_;
+        };
+        
         DatStatus() = delete;
 
         DatStatus(const DatStatus &other) = delete;
@@ -41,28 +87,45 @@ namespace LOTRO_DAT {
 
         explicit DatStatus(DatFile *datFilePtr);
 
-        void SetPercentage(unsigned percent);
+        void SetStatus(DAT_STATUS status);
+
+        void SetFinishedParts(unsigned long long finished_parts);
 
-        unsigned GetPercentage();
+        void SetTotalParts(unsigned long long total_parts);
 
         void SetDebugMessage(const std::string &message);
 
-        std::string GetDebugMessage();
+        DAT_STATUS GetStatus();
 
-        void SetStatus(DAT_STATUS status);
+        double GetPercentage();
 
-        DAT_STATUS GetStatus();
+        unsigned long long GetFinishedParts();
+
+        unsigned long long GetTotalParts(); 
+
+        std::string GetDebugMessage();
+
+        void SetDefaultStatus();
 
         bool CheckIfNotPatched();
+        
+        unsigned long long AddStatusChangedCallbackFunction(Callback func);
 
-        void ClearAll();
+        void RemoveStatusChangedCallbackFunction(unsigned long long handler);
 
-    private:
-        DatFile *dat;
-        unsigned percentage_;
-        DAT_STATUS status_;
+        void EraseAllCallbackFunctions();
 
-        std::string debug_message;
+        void InvokeCallbackFunctions();
+
+    private:
+        DatFile *dat = nullptr;
+        DAT_STATUS status_ = DAT_STATUS::E_FREE;
+        unsigned long long finished_parts_ = 0;
+        unsigned long long total_parts_ = 0;
+        double percentage_ = 0;
+        std::string debug_message_ = "";
+
+        std::set<CallbackStorage> callback_functions_;
     };
 }
 

+ 16 - 6
src/Legacy/Legacy.pro

@@ -17,9 +17,7 @@ SOURCES += \
     main.cpp \
     models/downloader.cpp \
     models/lotrodatmanager.cpp \
-    models/patchdownloader.cpp \
     models/selfupdater.cpp \
-    models/internallogicmanager.cpp \
     widgets/helpwidget.cpp \
     widgets/mainwindow.cpp \
     widgets/menuentry.cpp \
@@ -33,15 +31,20 @@ SOURCES += \
     widgets/weeklycodewidget.cpp \
     widgets/dialogwindow.cpp \
     widgets/qsmoothscrollarea.cpp \
-    fonts.cpp
+    fonts.cpp \
+    models/patch/patch.cpp \
+    models/patch/soundspatch.cpp \
+    models/patch/videospatch.cpp \
+    models/patch/textspatch.cpp \
+    models/patch/graphicspatch.cpp \
+    models/patchlist.cpp \
+    legacyapplication.cpp
 
 HEADERS += \
     models/downloader.h \
     models/filesystem.h \
     models/lotrodatmanager.h \
-    models/patchdownloader.h \
     models/selfupdater.h \
-    models/internallogicmanager.h \
     widgets/helpwidget.h \
     widgets/mainwindow.h \
     widgets/menuentry.h \
@@ -57,7 +60,14 @@ HEADERS += \
     widgets/qsmoothscrollarea.h \
     constants.h \
     fonts.h \
-    models/settings.h
+    models/settings.h \
+    models/patch/patch.h \
+    models/patch/soundspatch.h \
+    models/patch/videospatch.h \
+    models/patch/textspatch.h \
+    models/patch/graphicspatch.h \
+    models/patchlist.h \
+    legacyapplication.h
 
 FORMS += \
     widgets/helpwidget.ui \

+ 116 - 0
src/Legacy/legacyapplication.cpp

@@ -0,0 +1,116 @@
+#include "legacyapplication.h"
+
+#include <QLockFile>
+#include <QMessageBox>
+#include <QResource>
+#include <QFontDatabase>
+#include <QMessageLogContext>
+#include <QTextStream>
+
+void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+    QString message_type;
+
+    switch (type) {
+    case QtDebugMsg:
+        message_type = "[DEBG]";
+        break;
+    case QtInfoMsg:
+        message_type = "[INFO]";
+        break;
+    case QtWarningMsg:
+        message_type = "[WARN]";
+        break;
+    case QtCriticalMsg:
+        message_type = "[CRIT]";
+        break;
+    case QtFatalMsg:
+        message_type = "[FATL]";
+        break;
+    default:
+        message_type = "[UNKN]";
+        break;
+    }
+
+    QString filename = context.category;
+    QString function = context.function;
+    QString line_number = QString::number(context.line);
+
+    QString s = QString("%1: %2\n")
+                .arg(message_type)
+                .arg(msg);
+
+    QFile log_file("legacy_v2.log");
+    log_file.open(QIODevice::ReadWrite | QIODevice::Append);
+    QTextStream stream(&log_file);
+    stream << s;
+    log_file.close();
+    fprintf(stderr, "%s", qUtf8Printable(s));
+    fflush(stderr);
+}
+
+LegacyApplication::LegacyApplication(int &argc, char **argv)
+    : QApplication(argc, argv)
+    , lotro_dat_manager(nullptr)
+    , lotro_dat_manager_thread(nullptr)
+    , patch_list(nullptr)
+    , gui(nullptr)
+{
+    qInstallMessageHandler(logMessageHandler);
+}
+
+bool LegacyApplication::init()
+{
+    qDebug() << "Starting initialisation...";
+
+    QCoreApplication::setOrganizationName("LotroLegacy");
+    QCoreApplication::setOrganizationDomain("translate.lotros.ru");
+    QCoreApplication::setApplicationName("Legacy_v2");
+
+    QSettings::setDefaultFormat(QSettings::IniFormat);
+    QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QApplication::applicationDirPath());
+    QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QApplication::applicationDirPath());
+
+    qDebug() << "Checking if there's another instance of Legacy";
+
+    QLockFile lockFile(QDir::temp().absoluteFilePath("rulotro.lock"));
+    if(!lockFile.tryLock(1)){
+        qDebug() << "Lock file already exists! Some other application is already running...";
+        QMessageBox msgBox;
+        msgBox.setIcon(QMessageBox::Warning);
+        msgBox.setText("Приложение уже запущено.\nРазрешено запускать только один экземпляр приложения.");
+        msgBox.exec();
+        return false;
+    }
+
+    qDebug() << "Initialising fonts and resources";
+
+    QResource::registerResource(QApplication::applicationDirPath() + "/data01.gtr");
+    QResource::registerResource(QApplication::applicationDirPath() + "/data02.gtr");
+
+    QFontDatabase::addApplicationFont(":/fonts/trpro.ttf");
+    QFontDatabase::addApplicationFont(":/fonts/CrimsonText.ttf");
+    QFontDatabase::addApplicationFont(":/fonts/EBGaramond.ttf");
+    QFontDatabase::addApplicationFont(":/fonts/aniron.ttf");
+
+    qDebug() << "Starting Lotro Manager initialization...";
+
+    lotro_dat_manager = new LotroDatManager();
+    lotro_dat_manager_thread = new QThread();
+    connect(lotro_dat_manager_thread, &QThread::finished, lotro_dat_manager, &LotroDatManager::deleteLater);
+    lotro_dat_manager->moveToThread(lotro_dat_manager_thread);
+    lotro_dat_manager_thread->start();
+
+    qDebug() << "Starting Patch list initialisation...";
+
+    patch_list = new PatchList(lotro_dat_manager);
+
+    qDebug() << "Starting GUI initialisation...";
+
+    gui = new MainWindow(patch_list);
+    qDebug() << "Legacy Initialization finished!";
+
+    patch_list->startAutoUpdate();
+    patch_list->initialize();
+    return true;
+}

+ 27 - 0
src/Legacy/legacyapplication.h

@@ -0,0 +1,27 @@
+#ifndef LEGACYAPPLICATION_H
+#define LEGACYAPPLICATION_H
+
+#include <QApplication>
+#include <QThread>
+
+#include "models/lotrodatmanager.h"
+#include "models/patchlist.h"
+#include "widgets/mainwindow.h"
+
+class LegacyApplication : public QApplication
+{
+public:
+    LegacyApplication(int &argc, char** argv);
+
+    bool init();
+
+private:
+    LotroDatManager *lotro_dat_manager;
+    QThread *lotro_dat_manager_thread;
+
+    PatchList *patch_list;
+
+    MainWindow *gui;
+};
+
+#endif // LEGACYAPPLICATION_H

+ 4 - 42
src/Legacy/main.cpp

@@ -1,14 +1,5 @@
-#include <QApplication>
-#include <QMessageBox>
-#include <QLockFile>
-#include <QDir>
+#include "legacyapplication.h"
 
-#include "widgets/mainwindow.h"
-
-#include <QTextCodec>
-#include <QDebug>
-#include <QResource>
-#include <QFontDatabase>
 
 // Global. Should be updated only by MainWindow!!!
 double window_height = 1000;
@@ -17,40 +8,11 @@ double window_width = 648;
 int main(int argc, char *argv[])
 {
     setlocale(LC_ALL,"Russian");
-    QApplication a(argc, argv);
-
-    QCoreApplication::setOrganizationName("LotroLegacy");
-    QCoreApplication::setOrganizationDomain("translate.lotros.ru");
-    QCoreApplication::setApplicationName("Legacy_v2");
-    QSettings::setDefaultFormat(QSettings::IniFormat);
-    QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QApplication::applicationDirPath());
-    QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QApplication::applicationDirPath());
+    LegacyApplication app(argc, argv);
 
-    qDebug() << "Checking if there's another instance of Legacy";
-
-    QLockFile lockFile(QDir::temp().absoluteFilePath("rulotro.lock"));
-    if(!lockFile.tryLock(1)){
-        QMessageBox msgBox;
-        msgBox.setIcon(QMessageBox::Warning);
-        msgBox.setText("Приложение уже запущено.\nРазрешено запускать только один экземпляр приложения.");
-        msgBox.exec();
+    if (!app.init()) {
         return 1;
     }
 
-    qDebug() << "Initialising fonts and resources";
-    QResource::registerResource(QApplication::applicationDirPath() + "/data01.gtr");
-    QResource::registerResource(QApplication::applicationDirPath() + "/data02.gtr");
-
-    QFontDatabase::addApplicationFont(":/fonts/trpro.ttf");
-    QFontDatabase::addApplicationFont(":/fonts/CrimsonText.ttf");
-    QFontDatabase::addApplicationFont(":/fonts/EBGaramond.ttf");
-    QFontDatabase::addApplicationFont(":/fonts/aniron.ttf");
-
-    qDebug() << "Starting main frame initialisation...";
-
-    MainWindow* main_window = new MainWindow(nullptr);
-    main_window->setAttribute(Qt::WA_DeleteOnClose);
-
-    qDebug() << "Finished initialisation, starting application...";
-    return a.exec();
+    return app.exec();
 }

+ 68 - 49
src/Legacy/models/downloader.cpp

@@ -4,32 +4,28 @@
 #include <QDebug>
 #include <QTime>
 
-Downloader::Downloader(QObject *parent) :QObject(parent), busy(false)
+Downloader::Downloader(QObject *parent) :QObject(parent), busy_(false)
 {
+    qRegisterMetaType<Downloader::Status>();
     connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(onDownloadFinished(QNetworkReply*)));
-    bytes_downloaded = 0;
-    bytes_total = 0;
-    download_speed = 0;
-    bytesReceivedBeforeSecond = 0;
-    timeElapsedBeforeSecond = 0;
 }
 
 Downloader::~Downloader() {
 }
 
-QUrl Downloader::getUrl()
+QUrl Downloader::getUrl() const
 {
-    return url;
+    return url_;
 }
 
 void Downloader::setUrl(const QUrl &_url)
 {
-    url = _url;
+    url_ = _url;
 }
 
-void Downloader::waitForDownloaded()
+void Downloader::waitForDownloaded() const
 {
-    if (!busy)
+    if (!busy_)
         return;
 
     QEventLoop loop;
@@ -37,34 +33,52 @@ void Downloader::waitForDownloaded()
     loop.exec();
 }
 
-bool Downloader::isStarted()
+bool Downloader::isStarted() const
 {
-    return busy;
+    return busy_;
 }
 
-double Downloader::getPercent()
+double Downloader::getPercent() const
 {
-    return double(std::ceil(((double)bytes_downloaded/ bytes_total) * 100 * 10)) / 10.0;
+    return double(bytes_downloaded_) * 100.0 / double(bytes_total_);
 }
 
-quint64 Downloader::getBytesTotal()
+quint64 Downloader::getBytesTotal() const
 {
-    return bytes_total;
+    return bytes_total_;
 }
 
-quint64 Downloader::getBytesDownloaded()
+quint64 Downloader::getBytesDownloaded() const
 {
-    return bytes_downloaded;
+    return bytes_downloaded_;
 }
 
-quint64 Downloader::getElapsedTime()
+quint64 Downloader::getElapsedTime() const
 {
-    return (getBytesTotal() - getBytesDownloaded()) / qMax(quint64(1), getSpeed());
+    quint64 delta_size = getBytesTotal() - getBytesDownloaded();
+    quint64 avg_speed = qMax(quint64(1), getAverageSpeed());
+
+    while (delta_size > 1024 && avg_speed > 1024) { // Making precision smaller to get smoother dynamics
+        delta_size /= 1024;
+        avg_speed /= 1024;
+    }
+
+    return delta_size / avg_speed;
+}
+
+quint64 Downloader::getCurrentSpeed() const
+{
+    return download_speed_;
+}
+
+quint64 Downloader::getAverageSpeed() const
+{
+    return average_speed_;
 }
 
-quint64 Downloader::getSpeed()
+Downloader::Status Downloader::getDownloadStatus() const
 {
-    return download_speed;
+    return {isStarted(), getPercent(), getBytesTotal(), getBytesDownloaded(), getCurrentSpeed(), getAverageSpeed(), getElapsedTime()};
 }
 
 QString Downloader::getSpeedFormatted(quint64 speed_bytes_per_sec)
@@ -90,17 +104,19 @@ QString Downloader::getSizeFormatted(quint64 bytes)
     QString unit;
     if (size < 1024) {
         unit = "байт";
-    } else if (size < 1024*1024) {
+    } else if (size < 1024 * 1024) {
         size /= 1024;
-        unit = "kB";
-    } else {
+        unit = "кб";
+    } else if (size < 1024 * 1024 * 1024){
         size /= 1024*1024;
-        unit = "MB";
+        unit = "мб";
+    } else {
+        size /= 1024 * 1024 * 1024;
+        unit = "гб";
     }
     return QString::number(size, 'f', 1) + " " + unit;
 }
 
-
 QString Downloader::getElapsedTimeFormatted(quint64 elapsed_time_secs)
 {
     qint64 secs = elapsed_time_secs;
@@ -160,26 +176,26 @@ QString Downloader::getElapsedTimeFormatted(quint64 elapsed_time_secs)
 
     if (result == "")
         result = "совсем чуть-чуть";
+
     return result;
 }
 
 void Downloader::start()
 {
-    if (busy) {
-        qDebug() << "Cannot download " << url << ", downloader is busy!";
+    if (busy_) {
+        qDebug() << "Cannot download " << url_ << ", downloader is busy!";
         return;
     }
-    qDebug() << "Starting download " << url;
+    qDebug() << "Starting download " << url_;
 
-    downloadTime.restart();
-    bytes_downloaded = 0;
-    bytes_total = 0;
-    download_speed = 0;
-    bytesReceivedBeforeSecond = 0;
-    timeElapsedBeforeSecond = 0;
+    last_tick_time_.restart();
+    bytes_downloaded_ = 0;
+    bytes_total_ = 0;
+    download_speed_ = 0;
+    bytes_downloaded_before_tick_ = 0;
 
-    busy = true;
-    QNetworkRequest request(url);
+    busy_ = true;
+    QNetworkRequest request(url_);
     m_CurrentReply = m_WebCtrl.get(request);
     m_CurrentReply->setReadBufferSize(download_speed_limit);
     connect(m_CurrentReply, &QNetworkReply::readyRead, this, &Downloader::onReadyRead);
@@ -195,15 +211,18 @@ void Downloader::updateDownloadSpeedLimit(int bytes_per_sec)
 
 void Downloader::onDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal)
 {
+    bytes_downloaded_ = bytesReceived;
+    bytes_total_ = bytesTotal;
+
+    if (last_tick_time_.elapsed() >= 300) {
+        download_speed_ = (bytes_downloaded_ - bytes_downloaded_before_tick_) * 1000.0 / (last_tick_time_.elapsed());
 
-    bytes_downloaded = bytesReceived;
-    bytes_total = bytesTotal;
+        average_speed_ = (average_speed_ * update_ticks_counter_ + download_speed_) / (update_ticks_counter_ + 1);
+        ++update_ticks_counter_;
 
-    if (downloadTime.elapsed() - timeElapsedBeforeSecond >= 300) {
-        download_speed = (bytes_downloaded - bytesReceivedBeforeSecond) * 1000.0 / (downloadTime.elapsed() - timeElapsedBeforeSecond);
-        timeElapsedBeforeSecond = downloadTime.elapsed();
-        bytesReceivedBeforeSecond = bytes_downloaded;
-        emit progressChanged(this);
+        last_tick_time_.restart();
+        bytes_downloaded_before_tick_ = bytes_downloaded_;
+        emit progressChanged(this, getDownloadStatus());
     }
 }
 
@@ -212,15 +231,15 @@ void Downloader::stop()
     if (m_CurrentReply) {
         m_CurrentReply->abort();
     }
-    busy = false;
+    busy_ = false;
 }
 
 void Downloader::onDownloadFinished(QNetworkReply*) {
     if (m_CurrentReply)
         m_CurrentReply->deleteLater();
 
-    download_speed = 0;
-    busy = false;
+    download_speed_ = 0;
+    busy_ = false;
 
     emit downloadFinished(this);
 }

+ 61 - 18
src/Legacy/models/downloader.h

@@ -14,6 +14,41 @@ class Downloader : public QObject
     Q_OBJECT
 
 public:
+    struct Status {
+        bool running = false;
+        double percent = 0.0;
+
+        quint64 total_bytes = 0;
+        quint64 downloaded_bytes = 0;
+        quint64 current_speed = 0;
+        quint64 average_speed = 0;
+        quint64 elapsed_time = 0;
+    };
+
+    friend Status operator+(const Status& a, const Status &b) {
+        Status result;
+        result.running = a.running || b.running;
+        result.total_bytes = a.total_bytes + b.total_bytes;
+        result.downloaded_bytes = a.downloaded_bytes + b.downloaded_bytes;
+        result.percent = double(result.downloaded_bytes) * 100.0 / double(result.total_bytes);
+        result.average_speed = a.average_speed + b.average_speed;
+        result.current_speed = a.current_speed + b.current_speed;
+
+
+        // Counting elapsed time
+        quint64 delta_size = result.total_bytes - result.downloaded_bytes;
+        quint64 avg_speed = qMax(quint64(1), result.average_speed);
+
+        while (delta_size > 1024 && avg_speed > 1024) { // Making precision smaller to get smoother dynamics
+            delta_size /= 1024;
+            avg_speed /= 1024;
+        }
+        result.elapsed_time = delta_size / avg_speed;
+
+
+        return result;
+    }
+
     explicit Downloader(QObject *parent = 0);
 private:
     Q_DISABLE_COPY(Downloader)
@@ -21,23 +56,27 @@ private:
 public:
     virtual ~Downloader();
 
-    QUrl getUrl();
+    QUrl getUrl() const;
     void setUrl(const QUrl& _url);
-    void waitForDownloaded();
-
-    bool isStarted();
-    double getPercent();
-    quint64 getBytesTotal();
-    quint64 getBytesDownloaded();
-    quint64 getElapsedTime();
-    quint64 getSpeed();
+    void waitForDownloaded() const;
+
+    bool isStarted() const;
+    double getPercent() const;
+    quint64 getBytesTotal() const;
+    quint64 getBytesDownloaded() const;
+    quint64 getElapsedTime() const;
+    quint64 getCurrentSpeed() const;
+    quint64 getAverageSpeed() const;
+
+    Status getDownloadStatus() const;
+
     static QString getSizeFormatted(quint64 bytes);
     static QString getSpeedFormatted(quint64 speed_bytes_per_sec);
     static QString getElapsedTimeFormatted(quint64 elapsed_time_secs);
 
 signals:
     void downloadFinished(Downloader* this_downloader_ptr);
-    void progressChanged(Downloader* this_downloader_ptr);
+    void progressChanged(Downloader* this_downloader_ptr, Status status);
 
 public slots:
     void start();
@@ -54,21 +93,25 @@ public:
     QByteArray* targetBytearray {nullptr};
 
 private:
-    QTime downloadTime;
-    quint64 download_speed; // bytes per second
+    QTime last_tick_time_;
+    quint64 download_speed_ = 0; // bytes per second
+    quint64 average_speed_ = 0;
+    quint64 update_ticks_counter_ = 0;
 
-    quint64 bytes_total;
-    quint64 bytes_downloaded;
+    quint64 bytes_total_ = 0;
+    quint64 bytes_downloaded_ = 0;
 
-    quint64 bytesReceivedBeforeSecond; // Нужны для подсчёта текущей скорости скачивания, а не
-    quint64 timeElapsedBeforeSecond;   // средней за всё время.
+    quint64 bytes_downloaded_before_tick_ = 0; // Нужны для подсчёта текущей скорости скачивания, а не
 
-    bool busy;
-    QUrl url;
+    bool busy_;
+    QUrl url_;
     QNetworkReply* m_CurrentReply {nullptr};
     QNetworkAccessManager m_WebCtrl;
 
     unsigned download_speed_limit {0};
 };
 
+
+Q_DECLARE_METATYPE(Downloader::Status)
+
 #endif // INCLUDEILEDOWNLOADER_H

+ 6 - 0
src/Legacy/models/filesystem.h

@@ -7,6 +7,7 @@
 #include <QFileInfo>
 #include <QCryptographicHash>
 #include <QDebug>
+#include <QSettings>
 
 namespace FileSystem {
     static bool fileExists(QString path) {
@@ -18,6 +19,11 @@ namespace FileSystem {
         return QDir(path).exists();
     }
 
+    static bool createFilePath(QString file_path) { // Creates all necessary directories for file
+        QDir dir;
+        return dir.mkpath(QFileInfo(file_path).absoluteDir().absolutePath());
+    }
+
     static QString fileHash(const QString &fileName, QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Md5) {
         QFile file(fileName);
         if (file.open(QIODevice::ReadOnly)) {

+ 0 - 7
src/Legacy/models/internallogicmanager.cpp

@@ -1,7 +0,0 @@
-#include "internallogicmanager.h"
-#include "widgets/mainwindow.h"
-
-InternalLogicManager::InternalLogicManager(MainWindow *gui_, QSettings *settings_, PatchDownloader *patch_downloader_, LotroDatManager *lotro_manager_, QObject *parent)
-{
-
-}

+ 0 - 91
src/Legacy/models/internallogicmanager.h

@@ -1,91 +0,0 @@
-#ifndef INTERNALLOGICMANAGER_H
-#define INTERNALLOGICMANAGER_H
-
-#include <QObject>
-#include <QSettings>
-
-#include "models/patchdownloader.h"
-#include "models/lotrodatmanager.h"
-#include "models/filesystem.h"
-
-/*
-Stage 1 - check if user opened application for the first time - then we need to show
-          hello messages.
-
-Stage 2 - check if lotro folder is correct (there's file LotroLauncher.exe in it).
-          If no - then show him settings (ask to find lotro folder) where user can set it.
-
-Stage 3 - check if there are client_local_English.dat and client_general.dat files.
-          If no - then remove file and show message that we need to download them.
-
-Stage 4 - try to open client_local_English.dat and client_general.dat. If at least one of them
-          fails to open at all - show that there may be other processes taken ownage.
-
-Stage 5 - if .dat files are opened, but there was an error telling that file has incorrect version -
-          remove file and show message that we need to download them.
-
---- After this assuming that .dat files are correct and opened.
-
-Stage 6 - if user runs for the first time, then ask to create backup
-
-Stage 7 - check that user has chosen patches options. Otherwise move user to settings with enabled
-          position of choosing (this skips stage 8 and goes to st.9)
-
-Stage 8 - if there are flags that patches already applied but this is not true with .dat - ask to
-          apply them once more
-
-Stage 9 - All settings are checked and set. If user is for the first time - show message "ready to begin"
-          Start check for updates. Start check for updates timer.
-*/
-
-class MainWindow;
-
-class InternalLogicManager : public QObject
-{
-    Q_OBJECT
-public:
-    explicit InternalLogicManager(MainWindow *gui_, QSettings *settings_, PatchDownloader *patch_downloader_, LotroDatManager *lotro_manager_, QObject *parent = nullptr);
-
-signals:
-    void initPipelineStarted();
-    void initPipelineFinished();
-
-    // Semi-critical sections signals. Example: patch files downloading
-    void semiCriticalSectionEntered();
-    void semiCriticalSectionLeaved();
-
-    // Critical sections. Example: patch applying, game updating, prelauncher files moving
-    void criticalSectionEntered();
-    void criticalSectionLeaved();
-
-//public slots:
-//    void startInitPipeline();
-
-//    // Actions from UI -> general window
-//    void startGame(QString locale = "Original"); // Original/RU
-
-//    // Actions from UI -> settings window
-//    void lotroSettingsUpdated();
-//    void createLotroFilesBackup();
-//    void removeLotroFilesBackup();
-//    void applySelectedPatches();
-//    void updateSettingsChanged();
-
-//private slots:
-//    void checkFirstTimeOpening();
-//    void checkGameFolderCorrectness();
-//    void checkDatFilesExist();
-//    void processLotroManagerInit();
-//    void processPatchDownloaderInit(); // There begins also checkForUpdates cycle;
-//    void checkBackupExist();
-//    void checkUserHasEnteredPatchOptions();
-//    void checkDatFileWasPatched();
-
-private:
-    PatchDownloader *patch_downloader;
-    LotroDatManager *lotro_manager;
-    QSettings *settings;
-    MainWindow *gui;
-};
-
-#endif // INTERNALLOGICMANAGER_H

+ 133 - 382
src/Legacy/models/lotrodatmanager.cpp

@@ -1,143 +1,136 @@
 #include "lotrodatmanager.h"
-#include "filesystem.h"
-#include "LotroDat/Subfiles/TextSubFile.h"
-#include "models/patchdownloader.h"
+#include "models/filesystem.h"
 #include "models/settings.h"
 
-#include <QtConcurrent/QtConcurrent>
-#include <QFontDatabase>
-#include <QMessageBox>
 #include <QDebug>
-
-#include <string>
-#include <iostream>
-#include <fstream>
+#include <QProcess>
 
 Q_DECLARE_METATYPE(LOTRO_DAT::DatLocaleManager::LOCALE)
-Q_DECLARE_METATYPE(QVector<QVariant>)
+Q_DECLARE_METATYPE(LotroDatManager::Category)
+
 
 LotroDatManager::LotroDatManager(QObject *parent) :
     QObject(parent) {
 
-    qRegisterMetaType<QVector<QVariant>>();
     qRegisterMetaType<LOTRO_DAT::DatLocaleManager::LOCALE>();
+    client_local_file_.GetStatusModule().AddStatusChangedCallbackFunction(file_status_updated_callback_);
 }
 
-bool LotroDatManager::Initialised()
+LotroDatManager::~LotroDatManager()
 {
-    return client_local_file.Initialized() && client_general_file.Initialized();
+    deinitializeManager();
 }
 
-bool LotroDatManager::NotPatched()
+bool LotroDatManager::Initialised()
 {
-    return !client_local_file.GetStatusModule().CheckIfNotPatched() && !client_local_file.GetStatusModule().CheckIfNotPatched();
+    return client_general_file_.Initialized() && client_local_file_.Initialized();
 }
 
-unsigned LotroDatManager::getPercent()
+bool LotroDatManager::NotPatched()
 {
-    return client_local_file.GetStatusModule().GetPercentage();
+    return !client_local_file_.GetStatusModule().CheckIfNotPatched() && !client_local_file_.GetStatusModule().CheckIfNotPatched();
 }
 
-QString LotroDatManager::getCurrentInstallingPatchName()
+void LotroDatManager::initializeManager()
 {
-    return current_installing_patch_name;
-}
+    emit operationStarted("initializeManager");
+    qDebug() << __FUNCTION__ << "Starting initialisation of LotroDatManager";
 
-void LotroDatManager::InitialiseManager()
-{
-    qDebug() << "Initialising .dat manager";
-    emit processStarted("InitialiseManager");
     QString game_folder = Settings::getValue("Lotro/game_path").toString();
     QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
 
-    if (game_folder == "none" || !FileSystem::fileExists(game_folder + "client_local_" + locale_prefix + ".dat")
-            || !FileSystem::fileExists(game_folder + "client_general.dat")) {
+    QString client_local_filepath = game_folder + "/client_local_" + locale_prefix + ".dat";
+    QString client_general_filepath = game_folder + "/client_general.dat";
 
-        qDebug() << "Finished initialisation .dat manager - error: required files not found!";
+    if (game_folder == "none") {
+        qDebug() << __FUNCTION__ << "Finished initialisation LotroDatManager - error: .dat files not found!";
+        emit errorOccured("initializeManager", {}, "FolderNotDefined");
+        emit operationFinished("initializeManager", {}, false);
+        return;
+    }
 
-        emit caughtError(QString("InitialiseManager"), {"FileNotFound"});
-        emit processFinished("InitialiseManager");
+    if (!FileSystem::fileExists(client_local_filepath) || !FileSystem::fileExists(client_general_filepath)) {
+        emit errorOccured("initializeManager", {}, "DatFilesNotFound");
+        emit operationFinished("initializeManager", {}, false);
         return;
     }
 
     // Updating file permissions to be sure, that they're not in read-only mode
 
-    if (!QFile::setPermissions((game_folder + "/client_local_" + locale_prefix + ".dat"), QFileDevice::Permission(0x6666))) {
-        qDebug() << "Unable to update permissions on client_local_* file!";
+    if (!QFile::setPermissions(client_local_filepath, QFileDevice::Permission(0x6666))) {
+        qDebug() << __FUNCTION__ << "Unable to update permissions on client_local_* file!";
     }
 
-    if (!QFile::setPermissions((game_folder + "/client_general.dat"), QFileDevice::Permission(0x6666))) {
-        qDebug() << "Unable to update permissions on client_general* file!";
+    if (!QFile::setPermissions(client_general_filepath, QFileDevice::Permission(0x6666))) {
+        qDebug() << __FUNCTION__ << "Unable to update permissions on client_general* file!";
     }
 
     // Initialising client_local_*.dat file and client_general.dat
 
-    auto client_local_init_res = client_local_file.Initialise((game_folder + "/client_local_" + locale_prefix + ".dat").toStdString(), 0);
-    auto client_general_init_res = client_general_file.Initialise((game_folder + "/client_general.dat").toStdString(), 1);
+    auto client_local_init_res = client_local_file_.Initialise(client_local_filepath.toStdString(), 0);
+    auto client_general_init_res = client_general_file_.Initialise(client_general_filepath.toStdString(), 1);
 
     if (client_local_init_res.result != LOTRO_DAT::SUCCESS
-            || client_general_init_res.result != LOTRO_DAT::SUCCESS) {
-        client_local_file.Deinitialize();
-        client_general_file.Deinitialize();
+        || client_general_init_res.result != LOTRO_DAT::SUCCESS)
+    {
+        client_local_file_.Deinitialize();
+        client_general_file_.Deinitialize();
 
-        qDebug() << "Finished initialisation .dat manager - error: DatFile initialisation error!";
+        qDebug() << __FUNCTION__ << "Finished LotroDatManager initialisation - error: DatFile initialisation error!";
 
-        emit caughtError(QString("InitialiseManager"), {"InitialisationError"});
-        emit processFinished("InitialiseManager");
+        emit errorOccured("initializeManager", {}, "DatInitError");
+        emit operationFinished("initializeManager", {}, false);
         return;
     }
 
-    emit processFinished("InitialiseManager");
+    emit operationFinished("initializeManager", {}, true);
 }
 
-void LotroDatManager::DeinitialiseManager()
+void LotroDatManager::deinitializeManager()
 {
-    emit processStarted("DeinitialiseManager");
-    qDebug() << "Started DEinitialisation .dat manager.";
-    client_local_file.Deinitialize();
-    client_general_file.Deinitialize();
-    qDebug() << "Finished DEinitialisation .dat manager.";
-    emit processFinished("DeinitialiseManager");
+    emit operationStarted("deinitializeManager");
+    client_local_file_.Deinitialize();
+    client_general_file_.Deinitialize();
+    emit operationFinished("deinitializeManager");
 }
 
-void LotroDatManager::StartGame(LOTRO_DAT::DatLocaleManager::LOCALE locale)
+void LotroDatManager::startGame(LOTRO_DAT::DatLocaleManager::LOCALE locale)
 {
-    emit processStarted("StartGame");
-    qDebug() << "Starting game!";
+    emit operationStarted("startGame", {locale});
 
-    client_general_file.GetLocaleManager().SetLocale(locale);
-    client_local_file.GetLocaleManager().SetLocale(locale);
+    client_general_file_.GetLocaleManager().SetLocale(locale);
+    client_local_file_.GetLocaleManager().SetLocale(locale);
 
     QString game_folder = Settings::getValue("Lotro/game_path").toString();
 
     if (game_folder == "none") {
-        emit caughtError("StartGame", {"GameFolderNotSet"});
-        qDebug() << "Starting game FAILED - game folder wasnt set!";
-        emit processFinished("StartGame");
+        qDebug() << __FUNCTION__ << "Starting game FAILED - game folder wasnt set!";
+        emit errorOccured("startGame", {locale}, "GameFolderNotSet");
+        emit operationFinished("startGame", {locale}, false);
         return;
     }
 
     if (!FileSystem::fileExists(QApplication::applicationDirPath() + "/Launcher.exe")) {
-        emit caughtError("StartGame", {"NoGameLauncherInLegacyDir"});
-        qDebug() << "Starting game FAILED - no game launcher in legacy directory found!";
-        emit processFinished("StartGame");
+        qDebug() << __FUNCTION__ << "Starting game FAILED - no game launcher in legacy directory found!";
+        emit errorOccured("startGame", {locale}, "NoGameLauncherInLegacyDir");
+        emit operationFinished("startGame", {locale}, false);
         return;
     }
 
     if (locale == LOTRO_DAT::DatLocaleManager::PATCHED) {
         QFile::remove(game_folder + "/lotro_ru.exe");
         if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/lotro_ru.exe")) {
-            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
-            qDebug() << "Starting game FAILED - cannot copy LotroLauncher to LotroLauncher_fwd!!";
-            emit processFinished("StartGame");
+            qDebug() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher to lotro_ru.exe!!";
+            emit errorOccured("startGame", {locale}, "LauncherCopyFailed");
+            emit operationFinished("startGame", {locale}, false);
             return;
         }
 
         QFile::remove(game_folder + "/LotroLauncher.exe");
         if (!QFile::copy(QApplication::applicationDirPath() + "/Launcher.exe", game_folder + "/LotroLauncher.exe")) {
-            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
-            qDebug() << "Starting game FAILED - cannot copy GameLauncher to LotroLauncher!!";
-            emit processFinished("StartGame");
+            qDebug() << __FUNCTION__ << "Starting game FAILED - cannot copy GameLauncher to LotroLauncher!!";
+            emit errorOccured("startGame", {locale}, "NoAccessToGameLauncher");
+            emit operationFinished("startGame", {locale}, false);
             return;
         }
 
@@ -149,358 +142,117 @@ void LotroDatManager::StartGame(LOTRO_DAT::DatLocaleManager::LOCALE locale)
     } else {
         QFile::remove(game_folder + "/LotroLauncher.exe");
         if (!QFile::copy(QApplication::applicationDirPath() + "/LotroLauncher.exe", game_folder + "/LotroLauncher.exe")) {
-            qDebug() << QApplication::applicationDirPath() + "/LotroLauncher.exe";
-            qDebug() << game_folder + "/LotroLauncher.exe";
-            emit caughtError("StartGame", {"NoAccessToGameLauncher"});
-            qDebug() << "Starting game FAILED - cannot copy LotroLauncher from working dir to LotroLauncher in lotro dir!!";
-            emit processFinished("StartGame");
-            return;
-        }
-    }
-
-    qDebug() << "Starting game finished!!";
-    startLotroLauncherWithParameters(locale);
-    emit processFinished("StartGame");
-}
-
-void LotroDatManager::ChangeTranslationLanguage()
-{
-    DeinitialiseManager();
-    InitialiseManager();
-}
-
-void LotroDatManager::InstallActivePatches()
-{
-    SetupAllPatches();   // Setup means inserting necessary data
-    ApplyTexts();       // Applying means activating patched data
-    ApplyImages();
-    ApplySounds();
-}
-
-void LotroDatManager::SetupAllPatches()
-{
-    foreach (QString patch_name, all_patch_names) {
-        if (Settings::getValue("DatabaseDownload/" + patch_name).toString() == "Disabled") {
-            continue;
-        }
-
-        emit processStarted("SetupPatch", {patch_name});
-        current_installing_patch_name = patch_name;
-
-        if (patch_name == "video") {
-            InstallVideos();
-            Settings::setValue("DatabaseApplied/" + patch_name, "True");
-            current_installing_patch_name = "none";
-            emit processFinished("SetupPatch", {patch_name});
-            return;
-        }
-
-        if (patch_name == "loadscreen") {
-            InstallLoadscreens();
-            Settings::setValue("DatabaseApplied/" + patch_name, "True");
-            current_installing_patch_name = "none";
-            emit processFinished("SetupPatch", {patch_name});
+            qDebug() << __FUNCTION__ << "Starting game FAILED - cannot copy LotroLauncher from working dir to LotroLauncher in lotro dir!!";
+            emit errorOccured("startGame", {locale}, "NoAccessToGameLauncher");
+            emit operationFinished("startGame", {locale}, false);
             return;
         }
-
-        QString database_path = Settings::getValue("DatabasePath/" + patch_name).toString(); //patch_downloader->getDatabasePathByPatchName(patch_name);
-        if (database_path == "none") {
-            qDebug() << __FUNCTION__ << "Database for " << patch_name << " has no database path! Skipping!";
-            continue;
-        }
-
-        LOTRO_DAT::Database db;
-        if (!db.InitDatabase(database_path.toStdString())) {
-            emit caughtError("InstallPatches", {"ErrorInitDatabase"});
-            continue;
-        }
-
-        if (client_local_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
-            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_local"});
-
-        if (client_general_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
-            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_general"});
-
-        db.CloseDatabase();
-
-        Settings::setValue("DatabaseApplied/" + patch_name, "True");
-        current_installing_patch_name = "none";
-        emit processFinished("SetupPatch", {patch_name});
     }
-}
-
-void LotroDatManager::InstallLoadscreens()
-{
-    emit processStarted("InstallLoadscreens");
 
-    if (Settings::getValue("DatabaseDownload/loadscreen").toString() == "Disabled") {
-        emit processFinished("InstallLoadscreens", {"Success"});
+    if (!startLotroLauncherWithParameters(locale)) {
+        emit errorOccured("startGame", {locale}, "StartLotroLauncherWithParametersFailed");
+        emit operationFinished("startGame", {locale}, false);
         return;
     }
 
-    QString game_folder = Settings::getValue("Lotro/game_path").toString();
-    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
-
-    QString raw_folder;
-    if (locale_prefix == "English")
-        raw_folder = "en";
-    if (locale_prefix == "DE")
-        raw_folder = "de";
-    if (locale_prefix == "FR")
-        raw_folder = "fr";
+    emit operationFinished("startGame", {locale}, true);
+}
 
-    QString folder = game_folder + "/raw/" + raw_folder + "/logo/";
+void LotroDatManager::installPatch(QString patch_name, QString database_path)
+{
+    emit operationStarted("installPatch", {patch_name, database_path});
+    LOTRO_DAT::Database db;
 
-    if (!FileSystem::folderExists(folder)) {
-        emit processFinished("InstallLoadscreens", {"Error", "Incorrect folder"});
+    if (!db.InitDatabase(database_path.toStdString())) {
+        emit errorOccured("installPatch", {patch_name, database_path}, "ErrorInitDatabase");
+        emit operationFinished("installPatch", {patch_name, database_path}, false);
         return;
     }
 
-    QString mainscreen =
-            game_folder == "en"
-            ? "lotro_ad_pregame.jpg"
-            : "lotro_ad_pregame_" + game_folder + ".jpg";
-
-    QStringList filenames;
-    filenames << mainscreen
-              << "lotro_generic_teleport_screen_01.jpg"
-              << "lotro_generic_teleport_screen_02.jpg"
-              << "lotro_generic_teleport_screen_03.jpg"
-              << "lotro_generic_teleport_screen_04.jpg"
-              << "lotro_generic_teleport_screen_05.jpg"
-              << "lotro_generic_teleport_screen_06.jpg"
-              << "lotro_generic_teleport_screen_07.jpg"
-              << "lotro_generic_teleport_screen_08.jpg"
-              << "lotro_generic_teleport_screen_09.jpg"
-              << "lotro_generic_teleport_screen_10.jpg";
-
-
-    QString database_path = Settings::getValue("DatabasePath/loadscreen").toString();
-    if (database_path == "none") {
-        qDebug() << __FUNCTION__ << "No path to loadscreens patch found!";
+    if (client_local_file_.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS) {
+        db.CloseDatabase();
+        emit errorOccured("installPatch", {patch_name, database_path}, "ErrorPatchClientLocal");
+        emit operationFinished("installPatch", {patch_name, database_path}, false);
         return;
     }
 
-    LOTRO_DAT::Database db;
-    if (!db.InitDatabase(database_path.toStdString())) {
-        emit caughtError("InstallLoadscreens", {"ErrorInitDatabase"});
-        emit processFinished("InstallLoadscreens");
+    if (client_general_file_.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS) {
+        db.CloseDatabase();
+        emit errorOccured("installPatch", {patch_name, database_path});
+        emit operationFinished("installPatch", {patch_name, database_path}, false);
         return;
     }
 
-    for (size_t i = 0; i < qMin(db.CountRows(), size_t(filenames.size())); i++) {
-        LOTRO_DAT::SubfileData subfile = db.GetNextFile();
-
-        if (subfile.binary_data.Empty())
-            continue;
-
-        QFile::remove(folder + filenames[i]);
-        subfile.binary_data.WriteToFile((folder + filenames[i]).toStdString());
-    }
     db.CloseDatabase();
-    emit processFinished("InstallLoadscreens");
+    emit operationFinished("installPatch", {patch_name, database_path}, true);
 }
 
-void LotroDatManager::InstallVideos()
+void LotroDatManager::enableCategory(QString patch_name, LotroDatManager::Category category)
 {
-    if (Settings::getValue("DatabaseDownload/video").toString() == "Disabled")
-        return;
-
-    emit processStarted("InstallVideos");
-
-    QString game_folder = Settings::getValue("Lotro/game_path").toString();
+    emit operationStarted("enableCategory", {patch_name, category});
 
-    if (game_folder == "none") {
-        qDebug() << __FUNCTION__ << "No path to game folder found!";
-        emit caughtError("InstallVideos", {"ErrorGameFolderNotFound"});
-        emit processFinished("InstallVideos");
-        return;
-    }
-
-    QString database_path = Settings::getValue("DatabasePath/video").toString();
-
-    if (database_path == "none") {
-        qDebug() << __FUNCTION__ << "No path to videos patch found!";
-        emit caughtError("InstallVideos", {"ErrorFindDatabase"});
-        emit processFinished("InstallVideos");
+    if (client_local_file_.GetLocaleManager().EnableCategory(category).result != LOTRO_DAT::SUCCESS) {
+        emit errorOccured("enableCategory", {patch_name, category}, "ErrorEnableCategoryClientLocal");
+        emit operationFinished("enableCategory", {patch_name, category}, false);
         return;
     }
 
-    LOTRO_DAT::Database db;
-    if (!db.InitDatabase(database_path.toStdString())) {
-        emit caughtError("InstallVideos", {"ErrorInitDatabase"});
-        emit processFinished("InstallVideos");
+    if (client_general_file_.GetLocaleManager().EnableCategory(category).result != LOTRO_DAT::SUCCESS) {
+        emit errorOccured("enableCategory", {patch_name, category}, "ErrorEnableCategoryClientGeneral");
+        emit operationFinished("enableCategory", {patch_name, category}, false);
         return;
     }
 
-    for(size_t i = 0; i < db.CountRows(); i++) {
-        LOTRO_DAT::SubfileData subfile = db.GetNextFile();
-
-        if (subfile.Empty())
-            continue;
-
-        QString filename = QString::fromStdString(subfile.options["name"].as<std::string>());
-        QUrl url = QString::fromStdString(subfile.options["url"].as<std::string>());
-        QString hash = QString::fromStdString(subfile.options["hash"].as<std::string>());
-        QString target_folder = game_folder + "/" + QString::fromStdString(subfile.options["folder"].as<std::string>()) + "/";
-
-        if (!QDir(target_folder).exists())
-            QDir(target_folder).mkpath(target_folder);
-
-        if (FileSystem::fileExists(target_folder + filename)) {
-            if (FileSystem::fileHash(target_folder + filename) == hash) {
-                continue;
-            } else {
-                QFile::remove(target_folder + filename);
-            }
-        }
-
-        Downloader video_downloader;
-        video_downloader.setUrl(url);
-        video_downloader.targetFile = new QFile(target_folder + filename);
-        video_downloader.targetFile->open(QIODevice::ReadWrite);
-        video_downloader.start();
-// TODO: Progress notification on video download!
-//        connect(&video_downloader, &Downloader::progressChanged, this, &LotroDatManager::VideoDownloadProgressChanged);
-        video_downloader.waitForDownloaded();
-        video_downloader.targetFile->close();
-        video_downloader.targetFile->deleteLater();
-
-        if (FileSystem::fileHash(target_folder + filename) != hash) {
-            emit caughtError("InstallVideos", {"IncorrectHash", hash, FileSystem::fileHash(target_folder + filename), target_folder, filename, url});
-        }
-    }
-    db.CloseDatabase();
-
-    emit processFinished("InstallVideos");
+    emit operationFinished("enableCategory", {patch_name, category});
 }
 
-void LotroDatManager::InstallUpdates()
+void LotroDatManager::disableCategory(QString patch_name, LotroDatManager::Category category)
 {
-    emit processStarted("InstallUpdates");
-    foreach (QString patch_name, all_patch_names) {
-        if (Settings::getValue("DatabaseDownload/" + patch_name).toString() == "Disabled" ||
-            Settings::getValue("DatabaseApplied/" + patch_name).toString() == "True")
-        {
-            continue;
-        }
-
-        emit processStarted("SetupPatch", {patch_name});
-        current_installing_patch_name = patch_name;
-
-        if (patch_name == "video") {
-            InstallVideos();
-            Settings::setValue("DatabaseApplied/" + patch_name, "True");
-            current_installing_patch_name = "none";
-        }
-
-        if (patch_name == "loadscreen") {
-            InstallLoadscreens();
-            Settings::setValue("DatabaseApplied/" + patch_name, "True");
-            current_installing_patch_name = "none";
-        }
+    emit operationStarted("disableCategory", {patch_name, category});
 
-        QString database_path = Settings::getValue("PatchPath/" + patch_name).toString();
-        if (database_path == "none") {
-            qDebug() << __FUNCTION__ << "Database for " << patch_name << " has no database path! Skipping!";
-            continue;
-        }
-
-        LOTRO_DAT::Database db;
-        if (!db.InitDatabase(database_path.toStdString())) {
-            emit caughtError("InstallPatches", {"ErrorInitDatabase"});
-            continue;
-        }
-
-        if (client_local_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
-            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_local"});
-
-        if (client_general_file.GetPatcher().PatchAllDatabase(&db).result != LOTRO_DAT::SUCCESS)
-            emit caughtError("InstallPatches", {"ErrorCannotPatch", "client_general"});
-
-        db.CloseDatabase();
+    if (client_local_file_.GetLocaleManager().DisableCategory(category).result != LOTRO_DAT::SUCCESS) {
+        emit errorOccured("disableCategory", {patch_name, category}, "ErrorDisableCategoryClientLocal");
+        emit operationFinished("disableCategory", {patch_name, category}, false);
+        return;
+    }
 
-        Settings::setValue("DatabaseApplied/" + patch_name, "True");
-        current_installing_patch_name = "none";
-        emit processFinished("SetupPatch", {patch_name});
+    if (client_general_file_.GetLocaleManager().DisableCategory(category).result != LOTRO_DAT::SUCCESS) {
+        emit errorOccured("disableCategory", {patch_name, category}, "ErrorDisableCategoryClientGeneral");
+        emit operationFinished("disableCategory", {patch_name, category}, false);
+        return;
     }
-}
 
-void LotroDatManager::InstallMicroPatch()
-{
-    // TODO
+    emit operationFinished("disableCategory", {patch_name, category}, true);
 }
 
-void LotroDatManager::CreateBackup()
+void LotroDatManager::createBackup()
 {
-    emit processStarted("CreateBackup");
+    // TODO: Error handling
+    emit operationStarted("createBackup");
     QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
-    client_local_file.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
-    client_general_file.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
-    emit processFinished("CreateBackup");
+    client_local_file_.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
+    client_general_file_.GetBackupManager().CreateBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
+    emit operationFinished("createBackup");
 }
 
-void LotroDatManager::RestoreFromBackup()
+void LotroDatManager::restoreFromBackup()
 {
-    emit processStarted("CreateBackup");
+    // TODO: Error handling
+    emit operationStarted("restoreFromBackup");
     QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
-    client_local_file.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
-    client_general_file.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
-    emit processFinished("CreateBackup");
+    client_local_file_.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
+    client_general_file_.GetBackupManager().RestoreFromBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
+    emit operationFinished("restoreFromBackup");
 }
 
-void LotroDatManager::RemoveBackup()
+void LotroDatManager::removeBackup()
 {
-    emit processStarted("CreateBackup");
+    // TODO: Error handling
+    emit operationStarted("removeBackup");
     QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
-    client_local_file.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
-    client_general_file.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
-    emit processFinished("CreateBackup");
-}
-
-void LotroDatManager::ApplyTexts()
-{
-    if (Settings::getValue("Components/texts_main").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(100);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(100);
-    }
-
-    if (Settings::getValue("Components/texts_emotes").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(101);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(101);
-    }
-
-    if (Settings::getValue("Components/texts_items").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(102);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(102);
-    }
-}
-
-void LotroDatManager::ApplyImages() {
-    if (Settings::getValue("Components/maps").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(200);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(200);
-    }
-}
-
-void LotroDatManager::ApplySounds() {
-    emit processStarted("ApplySounds");
-    if (Settings::getValue("Components/sounds").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(300);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(300);
-    }
-
-    if (Settings::getValue("Components/videos").toString() == "Enabled") {
-        client_local_file.GetLocaleManager().EnableCategory(103);
-    } else {
-        client_local_file.GetLocaleManager().DisableCategory(103);
-    }
-    emit processFinished("ApplySounds");
+    client_local_file_.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_local_" + locale_prefix + ".dat").toStdString());
+    client_general_file_.GetBackupManager().RemoveBackup((QApplication::applicationDirPath() + "/backup/client_general.dat").toStdString());
+    emit operationFinished("removeBackup");
 }
 
 bool LotroDatManager::startLotroLauncherWithParameters(LOTRO_DAT::DatLocaleManager::LOCALE locale)
@@ -518,31 +270,30 @@ bool LotroDatManager::startLotroLauncherWithParameters(LOTRO_DAT::DatLocaleManag
         args << "gamelaunch" << "-disablePatch";
     }
 
-    client_general_file.Deinitialize();
-    client_local_file.Deinitialize();
+    client_general_file_.Deinitialize();
+    client_local_file_.Deinitialize();
 
-    if(FileSystem::fileExists(QApplication::applicationDirPath() + "/user.ini")){
-        QSettings login(QApplication::applicationDirPath() + "/user.ini", QSettings::IniFormat);
-        login.beginGroup("Account");
-        QString username = login.value("username", "").toString();
-        QString password = login.value("password", "").toString();
-        login.endGroup();
+    QString username = Settings::getValue("Account/username").toString();
+    QString password = Settings::getValue("Account/password").toString();
+    if (!username.isEmpty() && !password.isEmpty()) {
         args << "-username" << username << "-password" << password;
     }
 
-    qDebug() << "Запускаем игру со следующими аргументами: " << args;
+    qDebug() << __FUNCTION__ << "Starting game with arguments: " << args;
 
     QFile f(Settings::getValue("Lotro/game_path").toString() + "/LotroLauncher.exe");
     QProcess process;
 
     if (FileSystem::fileExists(f.fileName())) {
-        if(f.fileName().contains(" ")) f.setFileName("\"" + f.fileName() + "\"");
+        if (f.fileName().contains(" ")) {
+            f.setFileName("\"" + f.fileName() + "\"");
+        }
+
         process.startDetached(f.fileName(), args);
         process.waitForFinished(-1);
         QApplication::quit();
         return true;
     } else {
-        emit caughtError("startLotroLauncherWithParameters", {"LotroLauncherNotFound"});
         return false;
     }
 }

+ 35 - 38
src/Legacy/models/lotrodatmanager.h

@@ -9,73 +9,70 @@
 
 #include <LotroDat/LotroDat.h>
 
-class PatchDownloader;
+using LOTRO_DAT::DatStatus;
 
 class LotroDatManager : public QObject
 {
     Q_OBJECT
 
+public:
+    using Status = LOTRO_DAT::DatStatus::ProgressInfo;
+
+    enum Category : int {
+        E_TEXTS_COMMON = 100,
+        E_TEXTS_ITEMS = 101,
+        E_TEXTS_EMOTES = 102,
+        E_TEXTS_VIDEOS_REFS = 103,
+        E_MAPS_COMMON = 200,
+        E_TEXTURES_COMMON = 201,
+        E_AUDIOS_COMMON = 300,
+        E_FONTS_COMMON = 400
+    };
+
 public:
     explicit LotroDatManager(QObject *parent = nullptr);
 
+    ~LotroDatManager();
+
     bool Initialised();
 
     bool NotPatched();
 
-// TODO:
-//    bool IsRusificationActive();
-
-    unsigned getPercent();
-
-    QString getCurrentInstallingPatchName();
 
 public slots:
-    void InitialiseManager();
+    void initializeManager();
 
-    void DeinitialiseManager();
+    void deinitializeManager();
 
-    void StartGame(LOTRO_DAT::DatLocaleManager::LOCALE locale);
+    void startGame(LOTRO_DAT::DatLocaleManager::LOCALE locale);
 
-    void ChangeTranslationLanguage();
+    void installPatch(QString patch_name, QString database_path);
 
-    void InstallActivePatches();
+    void enableCategory(QString patch_name, LotroDatManager::Category category);
 
-    void SetupAllPatches();
+    void disableCategory(QString patch_name, LotroDatManager::Category category);
 
-    void InstallUpdates();
+    void createBackup();
 
-    void InstallMicroPatch();
+    void restoreFromBackup();
 
-    void CreateBackup();
-
-    void RestoreFromBackup();
-
-    void RemoveBackup();
+    void removeBackup();
 
 private:
     bool startLotroLauncherWithParameters(LOTRO_DAT::DatLocaleManager::LOCALE locale);
-    void ApplyTexts();
-    void ApplyImages();
-    void ApplySounds();
-    void InstallLoadscreens();
-    void InstallVideos();
 
 signals:
-    // general signals. First argument is process_name, second - processed values
-    void processStarted(QString, QVector<QVariant> parameters = QVector<QVariant>());
-    void processFinished(QString, QVector<QVariant> parameters = QVector<QVariant>());
-    void progressChanged();
-    void caughtError(QString, QVector<QVariant>);
+    void operationStarted(QString operation_name, QVector<QVariant> args = {});
+    void errorOccured(QString operation_name, QVector<QVariant> args = {}, QString message = "No error message provided");
+    void operationFinished(QString operation_name, QVector<QVariant> args = {}, bool successful = true);
+    void statusChanged(Status status);
 
 private:
-    const QStringList all_patch_names = {"sound", "text", "image", "loadscreen", "texture", "font", "video"};
-
-    QString current_installing_patch_name;
-    int video_percent;
-    int loadscreens_percent;
-
-    LOTRO_DAT::DatFile client_local_file;
-    LOTRO_DAT::DatFile client_general_file;
+    LOTRO_DAT::DatFile client_local_file_;
+    LOTRO_DAT::DatFile client_general_file_;
+    const LOTRO_DAT::DatStatus::Callback file_status_updated_callback_ = [&](Status status) {
+        emit statusChanged(status);
+    };
 };
 
 #endif // LEGACYAPP_H

+ 340 - 0
src/Legacy/models/patch/graphicspatch.cpp

@@ -0,0 +1,340 @@
+#include "graphicspatch.h"
+#include "LotroDat/LotroDat.h"
+#include "LotroDat/Database.h"
+#include "models/lotrodatmanager.h"
+
+#include <QUrlQuery>
+#include <QSet>
+
+GraphicsPatch::GraphicsPatch(LotroDatManager *mgr, QObject *parent) : Patch("GraphicsPatch", mgr, parent)
+{
+    connect(lotro_mgr_, &LotroDatManager::operationStarted, this, &GraphicsPatch::onLotroDatManagerOperationStarted);
+    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &GraphicsPatch::onLotroDatManagerOperationFinished);
+    connect(lotro_mgr_, &LotroDatManager::statusChanged, this, &GraphicsPatch::onLotroDatManagerStatusChanged);
+}
+
+void GraphicsPatch::checkForUpdates()
+{
+    QUrlQuery query; // query for building GET-request aka patch-version
+
+    foreach (QString db_name, databases_names) {
+        query.addQueryItem(db_name, "100");
+    }
+
+    QUrl target_url;
+    target_url.setUrl(Settings::getValue("Network/patch_updates_url").toString());
+    target_url.setQuery(query);
+
+    QByteArray target_array;
+    Downloader downloader;
+    downloader.setUrl(target_url);
+    downloader.targetBytearray = &target_array;
+    downloader.start();
+    downloader.waitForDownloaded();
+
+    if (target_array.isEmpty()) {
+        qWarning() << *this << "Cannot check for updates, target_array is empty!";
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    QStringList patch_info = QString(target_array).split('|');
+    if (patch_info.size() != databases_names.size()) {
+        qCritical() << __FUNCTION__ << "Incorrect patches number! Data: " << patch_info;
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    for (int i = 0; i < databases_names.size(); ++i) {
+        QStringList patch_data = patch_info[i].split(":::");
+        if (patch_data.size() != 3) {
+            qCritical() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << patch_data;
+            emit errorOccured(E_CHECKFORUPDATES, this, "");
+            emit operationFinished(E_CHECKFORUPDATES, this);
+            return;
+        }
+
+        QString patch_filename = Settings::getValue("General/PatchDownloadDir").toString() + "/" + QUrl(patch_data[0]).fileName();
+
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/url", patch_data[0]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/hashsum", patch_data[1]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/datetime", patch_data[2]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/path", patch_filename);
+    }
+
+    emit operationFinished(E_CHECKFORUPDATES, this);
+}
+
+void GraphicsPatch::download()
+{
+    foreach (QString db_name, databases_names) {
+        QString settings_prefix = "PatchDatabases/" + db_name;
+
+        QString target_filename = QApplication::applicationDirPath() + "/" + Settings::getValue(settings_prefix + "/path").toString();
+        qDebug() << patch_name_ << ": Checking if file " << target_filename << " matches its hashsum";
+
+        if (FileSystem::fileHash(target_filename) == Settings::getValue(settings_prefix + "/hashsum").toString()) {
+            qInfo() << *this << ": file " << target_filename << " is up-to-date, no need to download";
+            continue;
+        }
+
+        FileSystem::createFilePath(target_filename);
+        QFile* target_file = new QFile(target_filename);
+        if (!target_file->open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+            qWarning() << *this << "Cannot open file " << target_filename;
+            continue;
+        }
+
+        qInfo() << *this << ": beginning download of file " << target_filename;
+
+        Downloader* downloader = new Downloader();
+        downloader->setUrl(Settings::getValue(settings_prefix + "/url").toUrl());
+        downloader->targetFile = target_file;
+        connect(downloader, &Downloader::progressChanged, this, &GraphicsPatch::onDownloaderProgressChanged);
+        connect(downloader, &Downloader::downloadFinished, this, &GraphicsPatch::onDownloaderFinished);
+        downloaders_.insert(downloader);
+        downloader->start();
+    }
+
+    if (downloaders_.empty()) {
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void GraphicsPatch::install()
+{
+    emit operationFinished(E_INSTALL, this);
+    return;
+
+    installLoadscreens();
+
+    foreach (QString db_name, QStringList({"image", "texture"})) {
+        ++elapsed_patches_to_install_;
+        QMetaObject::invokeMethod(lotro_mgr_, "installPatch", Qt::QueuedConnection,
+                                  Q_ARG(QString, getPatchName() + "_" + db_name),
+                                  Q_ARG(QString, Settings::getValue("PatchDatabases/" + db_name + "/path").toString())
+                                  );
+    }
+}
+
+void GraphicsPatch::activate()
+{
+    emit operationFinished(E_ACTIVATE, this);
+    return;
+
+    if (Settings::getValue("Components/loadscreens").toBool()) {
+        enableLoadscreens();
+    } else {
+        disableLoadscreens();
+    }
+
+    QString operation_name;
+
+    operation_name = Settings::getValue("Components/maps").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                                  Q_ARG(QString, getPatchName() + "_maps"),
+                                  Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_MAPS_COMMON)
+                                  );
+
+    operation_name = Settings::getValue("Components/textures").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                                  Q_ARG(QString, getPatchName() + "_textures"),
+                                  Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_TEXTURES_COMMON)
+                                  );
+}
+
+void GraphicsPatch::onDownloaderProgressChanged(Downloader *, Downloader::Status)
+{
+    Downloader::Status all_downloads_status;
+    foreach (Downloader* downloader, downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    emit progressChanged(OperationProgress(all_downloads_status), this);
+}
+
+void GraphicsPatch::onDownloaderFinished(Downloader *ptr)
+{
+    ptr->targetFile->close();
+    ptr->targetFile->deleteLater();
+
+    Downloader::Status all_downloads_status;
+    for (const Downloader* downloader : downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    if (!all_downloads_status.running) {
+        for (Downloader* downloader : downloaders_) {
+            downloader->deleteLater();
+        }
+        downloaders_.clear();
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void GraphicsPatch::onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return; // This means, that message from LotroManager is addressed to another patchset
+    }
+
+    if (operation_name.contains("installPatch")) {
+        if (!successful) {
+            qCritical() << *this << "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.";
+            emit errorOccured(E_INSTALL, this, "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_INSTALL, this);
+        }
+    }
+
+    if (operation_name.contains("enableCategory") || operation_name.contains("disableCategory")) {
+        if (!successful) {
+            qCritical() << *this << "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.";
+            emit errorOccured(E_ACTIVATE, this, "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_ACTIVATE, this);
+        }
+    }
+}
+
+void GraphicsPatch::onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return;
+    }
+
+    is_being_patched_by_lotro_dat_manager_ = true;
+}
+
+void GraphicsPatch::onLotroDatManagerStatusChanged(LotroDatManager::Status status)
+{
+    if (!is_being_patched_by_lotro_dat_manager_) {
+        return;
+    }
+
+    emit progressChanged(OperationProgress(status), this);
+}
+
+void GraphicsPatch::installLoadscreens()
+{
+    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
+
+    const QStringList loadscreens_filenames = {
+        locale_prefix == "English" ? "lotro_ad_pregame.jpg" : "lotro_ad_pregame_" + locale_prefix + ".jpg",
+        "lotro_generic_teleport_screen_01.jpg",
+        "lotro_generic_teleport_screen_02.jpg",
+        "lotro_generic_teleport_screen_03.jpg",
+        "lotro_generic_teleport_screen_04.jpg",
+        "lotro_generic_teleport_screen_05.jpg",
+        "lotro_generic_teleport_screen_06.jpg",
+        "lotro_generic_teleport_screen_07.jpg",
+        "lotro_generic_teleport_screen_08.jpg",
+        "lotro_generic_teleport_screen_09.jpg",
+        "lotro_generic_teleport_screen_10.jpg"
+    };
+
+    OperationProgress progress;
+    progress.install_finished_parts = 0;
+    progress.install_total_parts = loadscreens_filenames.size() * 2;
+
+    LOTRO_DAT::Database database;
+    if (!database.InitDatabase(Settings::getValue("PatchDatabases/Loadscreen/path").toString().toStdString())) {
+        qCritical() << *this << "database.InitDatabase() of " << Settings::getValue("PatchDatabases/Loadscreen/path").toString() << " FAILED!";
+        return;
+    }
+
+    LOTRO_DAT::SubfileData data;
+
+    QString logo_path = Settings::getValue("Lotro/game_path").toString() + "/raw/" + (locale_prefix == "English" ? "en" : locale_prefix) + "/logo/";
+
+    for (size_t i = 0; i < qMin(size_t(loadscreens_filenames.size()), database.CountRows()); ++i) {
+        QFile::remove(logo_path + loadscreens_filenames[i] + "_ru");
+
+        if (!data.binary_data.WriteToFile((logo_path + loadscreens_filenames[i] + "_ru").toStdString())) {
+            qWarning() << patch_name_ << "Cannot write to file " << logo_path + loadscreens_filenames[i];
+        }
+
+        progress.install_finished_parts++;
+        emit progressChanged(progress, this);
+    }
+
+    for (int i = 0; i < loadscreens_filenames.size(); ++i) {
+        QFile::copy(logo_path + loadscreens_filenames[i], logo_path + loadscreens_filenames[i] + "_orig"); // Will not copy if _orig file already exists
+        progress.install_finished_parts++;
+        emit progressChanged(progress, this);
+    }
+}
+
+void GraphicsPatch::enableLoadscreens()
+{
+    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
+    QString logo_path = Settings::getValue("Lotro/game_path").toString() + "/raw/" + (locale_prefix == "English" ? "en" : locale_prefix) + "/logo/";
+
+    const QStringList loadscreens_filenames = {
+        locale_prefix == "English" ? "lotro_ad_pregame.jpg" : "lotro_ad_pregame_" + locale_prefix + ".jpg",
+        "lotro_generic_teleport_screen_01.jpg",
+        "lotro_generic_teleport_screen_02.jpg",
+        "lotro_generic_teleport_screen_03.jpg",
+        "lotro_generic_teleport_screen_04.jpg",
+        "lotro_generic_teleport_screen_05.jpg",
+        "lotro_generic_teleport_screen_06.jpg",
+        "lotro_generic_teleport_screen_07.jpg",
+        "lotro_generic_teleport_screen_08.jpg",
+        "lotro_generic_teleport_screen_09.jpg",
+        "lotro_generic_teleport_screen_10.jpg"
+    };
+
+    OperationProgress progress;
+    progress.install_total_parts = loadscreens_filenames.size();
+
+    for (int i = 0; i < loadscreens_filenames.size(); ++i) {
+        QFile::remove(logo_path + loadscreens_filenames[i]);
+        QFile::copy(logo_path + loadscreens_filenames[i] + "_ru", logo_path + loadscreens_filenames[i]);
+
+        progress.install_finished_parts++;
+        emit progressChanged(progress, this);
+    }
+}
+
+void GraphicsPatch::disableLoadscreens()
+{
+    QString locale_prefix = Settings::getValue("Lotro/original_locale").toString();
+    QString logo_path = Settings::getValue("Lotro/game_path").toString() + "/raw/" + (locale_prefix == "English" ? "en" : locale_prefix) + "/logo/";
+
+    const QStringList loadscreens_filenames = {
+        locale_prefix == "English" ? "lotro_ad_pregame.jpg" : "lotro_ad_pregame_" + locale_prefix + ".jpg",
+        "lotro_generic_teleport_screen_01.jpg",
+        "lotro_generic_teleport_screen_02.jpg",
+        "lotro_generic_teleport_screen_03.jpg",
+        "lotro_generic_teleport_screen_04.jpg",
+        "lotro_generic_teleport_screen_05.jpg",
+        "lotro_generic_teleport_screen_06.jpg",
+        "lotro_generic_teleport_screen_07.jpg",
+        "lotro_generic_teleport_screen_08.jpg",
+        "lotro_generic_teleport_screen_09.jpg",
+        "lotro_generic_teleport_screen_10.jpg"
+    };
+
+    OperationProgress progress;
+    progress.install_total_parts = loadscreens_filenames.size();
+
+    for (int i = 0; i < loadscreens_filenames.size(); ++i) {
+        QFile::remove(logo_path + loadscreens_filenames[i]);
+        QFile::copy(logo_path + loadscreens_filenames[i] + "_orig", logo_path + loadscreens_filenames[i]);
+
+        progress.install_finished_parts++;
+        emit progressChanged(progress, this);
+    }
+}

+ 42 - 0
src/Legacy/models/patch/graphicspatch.h

@@ -0,0 +1,42 @@
+#ifndef IMAGESPATCH_H
+#define IMAGESPATCH_H
+
+#include <QObject>
+
+#include "models/patch/patch.h"
+#include "models/lotrodatmanager.h"
+
+class GraphicsPatch : public Patch
+{
+    Q_OBJECT
+
+public:
+    GraphicsPatch(LotroDatManager* mgr, QObject* parent = nullptr);
+
+private slots:
+    virtual void checkForUpdates() override;
+    virtual void download() override;
+    virtual void install() override;
+    virtual void activate() override;
+
+private slots:
+    void onDownloaderProgressChanged(Downloader* ptr, Downloader::Status status);
+    void onDownloaderFinished(Downloader* ptr);
+
+    void onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args);
+    void onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful);
+    void onLotroDatManagerStatusChanged(LotroDatManager::Status status);
+
+private:
+    const QStringList databases_names = {
+        "image",
+        "texture",
+        "loadscreen",
+    };
+
+    void installLoadscreens();
+    void enableLoadscreens();
+    void disableLoadscreens();
+};
+
+#endif // IMAGESPATCH_H

+ 203 - 0
src/Legacy/models/patch/patch.cpp

@@ -0,0 +1,203 @@
+#include "patch.h"
+#include "models/settings.h"
+#include "models/filesystem.h"
+#include "models/lotrodatmanager.h"
+
+#include <QUrlQuery>
+
+Patch::Patch(QString patch_name, LotroDatManager* mgr, QObject *parent)
+    : QObject(parent)
+    , lotro_mgr_(mgr)
+    , patch_name_(patch_name)
+{
+    qRegisterMetaType<Operation>("Operation");
+    qRegisterMetaType<QList<Patch::Operation>>("QList<Patch::Operation>");
+    qRegisterMetaType<OperationProgress>("OperationProgress");
+
+
+    connect(this, &Patch::operationStarted, this, &Patch::onOperationStarted);
+    connect(this, &Patch::operationFinished, this, &Patch::onOperationFinished);
+}
+
+bool Patch::needDownloadDatabase(QString db_name)
+{
+    QString patch_filename = Settings::getValue("PatchDatabases/" + db_name + "/path").toString();
+    QString current_file_hash = FileSystem::fileHash(patch_filename);
+    QString required_file_hash = Settings::getValue("PatchDatabases/" + db_name + "/hashsum").toString();
+    return isDatabaseDownloadEnabled(db_name) && (current_file_hash != required_file_hash);
+}
+
+bool Patch::isDatabaseDownloadEnabled(QString db_name)
+{
+    if (db_name == "text") {
+        return Settings::getValue("Components/texts_main").toBool() ||
+               Settings::getValue("Components/texts_items").toBool() ||
+               Settings::getValue("Components/texts_emotes").toBool();
+    }
+
+    if (db_name == "font") {
+        return Settings::getValue("Components/fonts").toBool();
+    }
+
+    if (db_name == "image") {
+        return Settings::getValue("Components/maps").toBool();
+    }
+
+    if (db_name == "texture") {
+        return Settings::getValue("Components/logos").toBool();
+    }
+
+    if (db_name == "loadscreen") {
+        return Settings::getValue("Components/loadscreens").toBool();
+    }
+
+    if (db_name == "sound") {
+        return Settings::getValue("Components/sounds").toBool();
+    }
+
+    if (db_name == "video") {
+        return Settings::getValue("Components/video").toBool();
+    }
+
+    return false;
+}
+
+void Patch::execOperation(Patch::Operation operation)
+{
+    switch (operation) {
+    case E_CHECKFORUPDATES:
+        checkForUpdates();
+        return;
+    case E_DOWNLOAD:
+        download();
+        return;
+    case E_INSTALL:
+        install();
+        return;
+    case E_ACTIVATE:
+        activate();
+        return;
+    default:
+        qCritical() << "Unknown operation on patchset " << patch_name_ << "!";
+    }
+}
+
+QString Patch::getPatchName() const
+{
+    return patch_name_;
+}
+
+void Patch::enqueue(const Patch::Operation &operation)
+{
+    qDebug() << "Enqueuing operation " << operation << " in patchset" << *this;
+    operations_queue_.append(operation);
+    if (!running_operation_) {
+        processQueue();
+    }
+}
+
+void Patch::enqueue(const QList<Patch::Operation> &operations)
+{
+    qDebug() << "Enqueuing operations " << operations << " in patchset" << *this;
+    operations_queue_.append(operations);
+    if (!running_operation_) {
+        processQueue();
+    }
+}
+
+void Patch::processQueue()
+{
+    if (operations_queue_.empty() || running_operation_) {
+        return;
+    }
+
+    Operation current_operation = operations_queue_.dequeue();
+    qDebug() << "Processing patch queue: " << *this << ", starting operation " << current_operation;
+    emit operationStarted(current_operation, this);
+    execOperation(current_operation);
+}
+
+void Patch::onOperationStarted(Patch::Operation, Patch*)
+{
+    running_operation_ = true;
+}
+
+void Patch::onOperationFinished(Patch::Operation, Patch*)
+{
+    running_operation_ = false;
+    processQueue();
+}
+
+Patch::OperationProgress::OperationProgress(Downloader::Status status)
+{
+    download_finished_bytes = status.downloaded_bytes;
+    download_total_bytes = status.total_bytes;
+    download_speed = status.current_speed;
+    download_elapsed_time = status.elapsed_time;
+}
+
+Patch::OperationProgress::OperationProgress(LotroDatManager::Status status) {
+    install_finished_parts = status.finished_parts;
+    install_total_parts = status.total_parts;
+}
+
+Patch::OperationProgress Patch::OperationProgress::operator+(const Patch::OperationProgress &other) const
+{
+    Patch::OperationProgress result;
+
+    result.download_elapsed_time = download_elapsed_time + other.download_elapsed_time;
+    result.download_finished_bytes = download_finished_bytes + other.download_finished_bytes;
+    result.download_speed = download_speed + other.download_speed;
+    result.download_total_bytes = download_total_bytes + other.download_total_bytes;
+
+    result.install_finished_parts = install_finished_parts + other.install_finished_parts;
+    result.install_total_parts = install_total_parts + other.install_total_parts;
+
+    return result;
+}
+
+double Patch::OperationProgress::getDownloadPercent() const
+{
+    return double(download_finished_bytes) * 100.0/ double(download_total_bytes);
+}
+
+double Patch::OperationProgress::getInstallPercent() const
+{
+    return double(install_finished_parts) * 100.0 / double(install_total_parts);
+}
+
+QDebug operator<<(QDebug dbg, const Patch::Operation& operation) {
+    switch (operation) {
+    case Patch::Operation::E_CHECKFORUPDATES:
+        dbg << "Patch::Operation::E_CHECKFORUPDATES";
+        return dbg;
+    case Patch::Operation::E_DOWNLOAD:
+        dbg << "Patch::Operation::E_DOWNLOAD";
+        return dbg;
+    case Patch::Operation::E_INSTALL:
+        dbg << "Patch::Operation::E_INSTALL";
+        return dbg;
+    case Patch::Operation::E_ACTIVATE:
+        dbg << "Patch::Operation::E_ACTIVATE";
+        return dbg;
+    default:
+        dbg << "{Unknown operation!}";
+    }
+    return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const Patch::OperationProgress& operation_progress) {
+    dbg.nospace().noquote()
+        << "Downloaded: " << Downloader::getSizeFormatted(operation_progress.download_finished_bytes)
+        << " of " << Downloader::getSizeFormatted(operation_progress.download_total_bytes)
+        << " with speed " << Downloader::getSpeedFormatted(operation_progress.download_speed)
+        << " Elapsed time " << Downloader::getElapsedTimeFormatted(operation_progress.download_elapsed_time)
+        << " Installed: " << operation_progress.install_finished_parts << " of " << operation_progress.install_total_parts << ".";
+    return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const Patch& patch) {
+    dbg.nospace().noquote() << "Patch::" << patch.getPatchName();
+    return dbg;
+}
+

+ 97 - 0
src/Legacy/models/patch/patch.h

@@ -0,0 +1,97 @@
+#ifndef LEGACYPATCH_H
+#define LEGACYPATCH_H
+
+#include <QObject>
+#include <QQueue>
+#include <QTimer>
+
+#include "models/downloader.h"
+#include "models/filesystem.h"
+#include "models/lotrodatmanager.h"
+#include "models/settings.h"
+
+class Patch : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum Operation {
+        E_CHECKFORUPDATES,
+        E_DOWNLOAD,
+        E_INSTALL,
+        E_ACTIVATE
+    };
+
+    struct OperationProgress {
+
+        OperationProgress() = default;
+        OperationProgress(Downloader::Status status);
+        OperationProgress(LotroDatManager::Status status);
+
+        OperationProgress operator+(const Patch::OperationProgress& other) const;
+
+        double getDownloadPercent() const;
+        double getInstallPercent() const;
+
+    public:
+        quint64 download_finished_bytes = 0;
+        quint64 download_total_bytes = 0;
+        quint64 download_speed = 0;
+        quint64 download_elapsed_time = 0;
+
+        quint64 install_finished_parts = 0;
+        quint64 install_total_parts = 0;
+    };
+
+public:
+    explicit Patch(QString patch_name, LotroDatManager* mgr, QObject* parent);
+
+    QString getPatchName() const;
+
+public slots:
+    void enqueue(const Patch::Operation& operation);
+
+    void enqueue(const QList<Patch::Operation>& operations);
+
+    void processQueue();
+
+private slots: // WARNING: THESE CAN BE NON-BLOCKING OPERATIONS! Use them in chain only via operationFinished signal handlers.
+    virtual void checkForUpdates() = 0; // Checks for updates. Updates patches info in Settings.
+    virtual void download() = 0;        // Downloads patch contents, prepared for installation.
+    virtual void install() = 0;         // Installs patch into game resources (needs to be activated in order to be available in-game)
+    virtual void activate() = 0;        // Activates (or deactivates) patch components based on user preferences (patch needs to be installed)
+
+    void onOperationStarted(Operation operation, Patch* patch);
+    void onOperationFinished(Operation operation, Patch* patch);
+
+signals: // each signal brings with it pointer to class-emitter, operation name is equal to function name
+    void operationStarted(Operation operation, Patch* patch);
+    void progressChanged(OperationProgress progress, Patch* patch);
+    void operationFinished(Operation operation, Patch* patch);
+    void errorOccured(Operation operation, Patch* patch, QString msg);
+
+protected:
+    bool needDownloadDatabase(QString db_name); // Checks if database needs to be downloaded (by checksum comparing)
+
+    static bool isDatabaseDownloadEnabled(QString db_name); // Checks if database is checked for installation by user (via Settings)
+
+    void execOperation(Operation operation); // Starts operation execution by its "name"
+
+protected:
+    bool running_operation_ = false;
+    LotroDatManager* lotro_mgr_;
+    QString patch_name_;
+    QSet<Downloader*> downloaders_;
+    QQueue<Operation> operations_queue_;
+    bool is_being_patched_by_lotro_dat_manager_ = false;
+
+    quint32 elapsed_patches_to_install_ = 0;
+};
+
+QDebug operator<<(QDebug dbg, const Patch::Operation& operation);
+
+QDebug operator<<(QDebug dbg, const Patch::OperationProgress& operation_progress);
+
+QDebug operator<<(QDebug dbg, const Patch& patch);
+
+#endif // LEGACYPATCH_H

+ 211 - 0
src/Legacy/models/patch/soundspatch.cpp

@@ -0,0 +1,211 @@
+#include "soundspatch.h"
+#include "LotroDat/LotroDat.h"
+#include "LotroDat/Database.h"
+
+#include <QUrlQuery>
+#include <QSet>
+#include <QThread>
+
+SoundsPatch::SoundsPatch(LotroDatManager *mgr, QObject *parent) : Patch("SoundsPatch", mgr, parent)
+{
+    connect(lotro_mgr_, &LotroDatManager::operationStarted, this, &SoundsPatch::onLotroDatManagerOperationStarted);
+    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &SoundsPatch::onLotroDatManagerOperationFinished);
+    connect(lotro_mgr_, &LotroDatManager::statusChanged, this, &SoundsPatch::onLotroDatManagerStatusChanged);
+}
+
+void SoundsPatch::checkForUpdates()
+{
+    QUrlQuery query; // query for building GET-request aka patch-version
+
+    foreach (QString db_name, databases_names) {
+        query.addQueryItem(db_name, "100");
+    }
+
+    QUrl target_url;
+    target_url.setUrl(Settings::getValue("Network/patch_updates_url").toString());
+    target_url.setQuery(query);
+
+    QByteArray target_array;
+    Downloader downloader;
+    downloader.setUrl(target_url);
+    downloader.targetBytearray = &target_array;
+    downloader.start();
+    downloader.waitForDownloaded();
+
+    if (target_array.isEmpty()) {
+        qWarning() << *this << "Cannot check for updates, target_array is empty!";
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    QStringList patch_info = QString(target_array).split('|');
+    if (patch_info.size() != databases_names.size()) {
+        qCritical() << __FUNCTION__ << "Incorrect patches number! Data: " << patch_info;
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    for (int i = 0; i < databases_names.size(); ++i) {
+        QStringList patch_data = patch_info[i].split(":::");
+        if (patch_data.size() != 3) {
+            qCritical() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << patch_data;
+            emit errorOccured(E_CHECKFORUPDATES, this, "");
+            emit operationFinished(E_CHECKFORUPDATES, this);
+            return;
+        }
+
+        QString patch_filename = Settings::getValue("General/PatchDownloadDir").toString() + "/" + QUrl(patch_data[0]).fileName();
+
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/url", patch_data[0]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/hashsum", patch_data[1]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/datetime", patch_data[2]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/path", patch_filename);
+    }
+
+    emit operationFinished(E_CHECKFORUPDATES, this);
+}
+
+void SoundsPatch::download()
+{
+    foreach (QString db_name, databases_names) {
+        QString settings_prefix = "PatchDatabases/" + db_name;
+
+        QString target_filename = QApplication::applicationDirPath() + "/" + Settings::getValue(settings_prefix + "/path").toString();
+        qDebug() << patch_name_ << ": Checking if file " << target_filename << " matches its hashsum";
+
+        if (FileSystem::fileHash(target_filename) == Settings::getValue(settings_prefix + "/hashsum").toString()) {
+            qInfo() << *this << ": file " << target_filename << " is up-to-date, no need to download";
+            continue;
+        }
+
+        FileSystem::createFilePath(target_filename);
+        QFile* target_file = new QFile(target_filename);
+        if (!target_file->open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+            qWarning() << *this << "Cannot open file " << target_filename;
+            continue;
+        }
+
+        qInfo() << *this << ": beginning download of file " << target_filename;
+
+        Downloader* downloader = new Downloader();
+        downloader->setUrl(Settings::getValue(settings_prefix + "/url").toUrl());
+        downloader->targetFile = target_file;
+        connect(downloader, &Downloader::progressChanged, this, &SoundsPatch::onDownloaderProgressChanged);
+        connect(downloader, &Downloader::downloadFinished, this, &SoundsPatch::onDownloaderFinished);
+        downloaders_.insert(downloader);
+        downloader->start();
+    }
+
+    if (downloaders_.empty()) {
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void SoundsPatch::install()
+{
+    emit operationFinished(E_INSTALL, this);
+    return;
+
+    foreach (QString db_name, QStringList({"sound"})) {
+        ++elapsed_patches_to_install_;
+        QMetaObject::invokeMethod(lotro_mgr_, "installPatch", Qt::QueuedConnection,
+                                  Q_ARG(QString, getPatchName() + "_" + db_name),
+                                  Q_ARG(QString, Settings::getValue("PatchDatabases/" + db_name + "/path").toString())
+                                  );
+    }
+}
+
+void SoundsPatch::activate()
+{
+    emit operationFinished(E_ACTIVATE, this);
+    return;
+
+    QString operation_name = Settings::getValue("Components/sounds").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_sounds"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_AUDIOS_COMMON)
+                              );
+}
+
+void SoundsPatch::onDownloaderProgressChanged(Downloader *, Downloader::Status)
+{
+    Downloader::Status all_downloads_status;
+    foreach (Downloader* downloader, downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    emit progressChanged(OperationProgress(all_downloads_status), this);
+}
+
+void SoundsPatch::onDownloaderFinished(Downloader *ptr)
+{
+    ptr->targetFile->close();
+    ptr->targetFile->deleteLater();
+
+    Downloader::Status all_downloads_status;
+    for (const Downloader* downloader : downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    if (!all_downloads_status.running) {
+        for (Downloader* downloader : downloaders_) {
+            downloader->deleteLater();
+        }
+        downloaders_.clear();
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void SoundsPatch::onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return; // This means, that message from LotroManager is addressed to another patchset
+    }
+
+    if (operation_name.contains("installPatch")) {
+        if (!successful) {
+            qCritical() << *this << "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.";
+            emit errorOccured(E_INSTALL, this, "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_INSTALL, this);
+        }
+    }
+
+    if (operation_name.contains("enableCategory") || operation_name.contains("disableCategory")) {
+        if (!successful) {
+            qCritical() << *this << "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.";
+            emit errorOccured(E_ACTIVATE, this, "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_ACTIVATE, this);
+        }
+    }
+}
+
+void SoundsPatch::onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return;
+    }
+
+    is_being_patched_by_lotro_dat_manager_ = true;
+}
+
+void SoundsPatch::onLotroDatManagerStatusChanged(LotroDatManager::Status status)
+{
+    if (!is_being_patched_by_lotro_dat_manager_) {
+        return;
+    }
+
+    emit progressChanged(OperationProgress(status), this);
+}

+ 35 - 0
src/Legacy/models/patch/soundspatch.h

@@ -0,0 +1,35 @@
+#ifndef SOUNDSPATCH_H
+#define SOUNDSPATCH_H
+
+#include <QObject>
+
+#include "models/patch/patch.h"
+
+class SoundsPatch : public Patch
+{
+    Q_OBJECT
+
+public:
+    SoundsPatch(LotroDatManager* mgr, QObject* parent = nullptr);
+
+private slots:
+    virtual void checkForUpdates() override;
+    virtual void download() override;
+    virtual void install() override;
+    virtual void activate() override;
+
+private slots:
+    void onDownloaderProgressChanged(Downloader* ptr, Downloader::Status status);
+    void onDownloaderFinished(Downloader* ptr);
+
+    void onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args);
+    void onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful);
+    void onLotroDatManagerStatusChanged(LotroDatManager::Status status);
+
+private:
+    const QStringList databases_names = {
+        "sound",
+    };
+};
+
+#endif // SOUNDSPATCH_H

+ 239 - 0
src/Legacy/models/patch/textspatch.cpp

@@ -0,0 +1,239 @@
+#include "textspatch.h"
+#include "LotroDat/LotroDat.h"
+#include "LotroDat/Database.h"
+
+#include <QUrlQuery>
+#include <QSet>
+#include <QThread>
+
+TextsPatch::TextsPatch(LotroDatManager *mgr, QObject *parent) : Patch("TextsPatch", mgr, parent)
+{
+    connect(lotro_mgr_, &LotroDatManager::operationStarted, this, &TextsPatch::onLotroDatManagerOperationStarted);
+    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &TextsPatch::onLotroDatManagerOperationFinished);
+    connect(lotro_mgr_, &LotroDatManager::statusChanged, this, &TextsPatch::onLotroDatManagerStatusChanged);
+}
+
+void TextsPatch::checkForUpdates()
+{
+    QUrlQuery query; // query for building GET-request aka patch-version
+
+    foreach (QString db_name, databases_names) {
+        query.addQueryItem(db_name, "100");
+    }
+
+    QUrl target_url;
+    target_url.setUrl(Settings::getValue("Network/patch_updates_url").toString());
+    target_url.setQuery(query);
+
+    QByteArray target_array;
+    Downloader downloader;
+    downloader.setUrl(target_url);
+    downloader.targetBytearray = &target_array;
+    downloader.start();
+    downloader.waitForDownloaded();
+
+    if (target_array.isEmpty()) {
+        qWarning() << *this << "Cannot check for updates, target_array is empty!";
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    QStringList patch_info = QString(target_array).split('|');
+    if (patch_info.size() != databases_names.size()) {
+        qCritical() << __FUNCTION__ << "Incorrect patches number! Data: " << patch_info;
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    for (int i = 0; i < databases_names.size(); ++i) {
+        QStringList patch_data = patch_info[i].split(":::");
+        if (patch_data.size() != 3) {
+            qCritical() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << patch_data;
+            emit errorOccured(E_CHECKFORUPDATES, this, "");
+            emit operationFinished(E_CHECKFORUPDATES, this);
+            return;
+        }
+
+        QString patch_filename = Settings::getValue("General/PatchDownloadDir").toString() + "/" + QUrl(patch_data[0]).fileName();
+
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/url", patch_data[0]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/hashsum", patch_data[1]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/datetime", patch_data[2]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/path", patch_filename);
+    }
+
+    emit operationFinished(E_CHECKFORUPDATES, this);
+}
+
+void TextsPatch::download()
+{
+    foreach (QString db_name, databases_names) {
+        QString settings_prefix = "PatchDatabases/" + db_name;
+
+        QString target_filename = QApplication::applicationDirPath() + "/" + Settings::getValue(settings_prefix + "/path").toString();
+        qDebug() << patch_name_ << ": Checking if file " << target_filename << " matches its hashsum";
+
+        if (FileSystem::fileHash(target_filename) == Settings::getValue(settings_prefix + "/hashsum").toString()) {
+            qInfo() << *this << ": file " << target_filename << " is up-to-date, no need to download";
+            continue;
+        }
+
+        FileSystem::createFilePath(target_filename);
+        QFile* target_file = new QFile(target_filename);
+        if (!target_file->open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+            qWarning() << *this << "Cannot open file " << target_filename;
+            continue;
+        }
+
+        qInfo() << *this << ": beginning download of file " << target_filename;
+
+        Downloader* downloader = new Downloader();
+        downloader->setUrl(Settings::getValue(settings_prefix + "/url").toUrl());
+        downloader->targetFile = target_file;
+        connect(downloader, &Downloader::progressChanged, this, &TextsPatch::onDownloaderProgressChanged);
+        connect(downloader, &Downloader::downloadFinished, this, &TextsPatch::onDownloaderFinished);
+        downloaders_.insert(downloader);
+        downloader->start();
+    }
+
+    if (downloaders_.empty()) {
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void TextsPatch::install()
+{
+    emit operationFinished(E_INSTALL, this);
+    return;
+
+    foreach (QString db_name, databases_names) {
+        ++elapsed_patches_to_install_;
+        QMetaObject::invokeMethod(lotro_mgr_, "installPatch", Qt::QueuedConnection,
+                                  Q_ARG(QString, getPatchName() + "_" + db_name),
+                                  Q_ARG(QString, Settings::getValue("PatchDatabases/" + db_name + "/path").toString())
+                                  );
+    }
+}
+
+void TextsPatch::activate()
+{
+    emit operationFinished(E_ACTIVATE, this);
+    return;
+
+    QString operation_name = Settings::getValue("Components/fonts").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_fonts"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_FONTS_COMMON)
+                              );
+
+    operation_name = Settings::getValue("Components/texts_main").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_texts_main"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_TEXTS_COMMON)
+                              );
+
+    operation_name = Settings::getValue("Components/texts_items").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_texts_items"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_TEXTS_ITEMS)
+                              );
+
+    operation_name = Settings::getValue("Components/texts_emotes").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_texts_emotes"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_TEXTS_EMOTES)
+                              );
+
+    operation_name = Settings::getValue("Components/videos").toBool() ? "enableCategory" : "disableCategory";
+    ++elapsed_patches_to_install_;
+    QMetaObject::invokeMethod(lotro_mgr_, operation_name.toLocal8Bit().constData(), Qt::QueuedConnection,
+                              Q_ARG(QString, getPatchName() + "_texts_videos_refs"),
+                              Q_ARG(LotroDatManager::Category, LotroDatManager::Category::E_TEXTS_VIDEOS_REFS)
+                              );
+}
+
+void TextsPatch::onDownloaderProgressChanged(Downloader *, Downloader::Status)
+{
+    Downloader::Status all_downloads_status;
+    foreach (Downloader* downloader, downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    emit progressChanged(OperationProgress(all_downloads_status), this);
+}
+
+void TextsPatch::onDownloaderFinished(Downloader *ptr)
+{
+    ptr->targetFile->close();
+    ptr->targetFile->deleteLater();
+
+    Downloader::Status all_downloads_status;
+    for (const Downloader* downloader : downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    if (!all_downloads_status.running) {
+        for (Downloader* downloader : downloaders_) {
+            downloader->deleteLater();
+        }
+        downloaders_.clear();
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void TextsPatch::onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return; // This means, that message from LotroManager is addressed to another patchset
+    }
+
+    if (operation_name.contains("installPatch")) {
+        if (!successful) {
+            qCritical() << *this << "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.";
+            emit errorOccured(E_INSTALL, this, "Database " + args[0].toString() + " (" + args[1].toString() + ") was not installed due to Legacy core error.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_INSTALL, this);
+        }
+    }
+
+    if (operation_name.contains("enableCategory") || operation_name.contains("disableCategory")) {
+        if (!successful) {
+            qCritical() << *this << "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.";
+            emit errorOccured(E_ACTIVATE, this, "Error in patch " + args[0].toString() + ": activating category " + QString::number(args[1].toInt()) + " failed.");
+        }
+
+        --elapsed_patches_to_install_;
+        if (!elapsed_patches_to_install_) {
+            is_being_patched_by_lotro_dat_manager_ = false;
+            emit operationFinished(E_ACTIVATE, this);
+        }
+    }
+}
+
+void TextsPatch::onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args)
+{
+    if (args.size() == 0 || !args[0].toString().contains(getPatchName())) {
+        return;
+    }
+
+    is_being_patched_by_lotro_dat_manager_ = true;
+}
+
+void TextsPatch::onLotroDatManagerStatusChanged(LotroDatManager::Status status)
+{
+    if (!is_being_patched_by_lotro_dat_manager_) {
+        return;
+    }
+
+    emit progressChanged(OperationProgress(status), this);
+}

+ 35 - 0
src/Legacy/models/patch/textspatch.h

@@ -0,0 +1,35 @@
+#ifndef TEXTPATCH_H
+#define TEXTPATCH_H
+
+#include <QObject>
+
+#include "models/patch/patch.h"
+
+class TextsPatch : public Patch
+{
+    Q_OBJECT
+
+public:
+    TextsPatch(LotroDatManager* mgr, QObject* parent = nullptr);
+
+private slots:
+    virtual void checkForUpdates() override;
+    virtual void download() override;
+    virtual void install() override;
+    virtual void activate() override;
+
+private slots:
+    void onDownloaderProgressChanged(Downloader* ptr, Downloader::Status status);
+    void onDownloaderFinished(Downloader* ptr);
+
+    void onLotroDatManagerOperationStarted(QString operation_name, QVector<QVariant> args);
+    void onLotroDatManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool successful);
+    void onLotroDatManagerStatusChanged(LotroDatManager::Status status);
+
+private:
+    const QStringList databases_names = {
+        "text",
+        "font"
+    };
+};
+#endif // TEXTPATCH_H

+ 142 - 0
src/Legacy/models/patch/videospatch.cpp

@@ -0,0 +1,142 @@
+#include "videospatch.h"
+#include "LotroDat/LotroDat.h"
+#include "LotroDat/Database.h"
+
+#include <QUrlQuery>
+#include <QSet>
+#include <QThread>
+
+VideosPatch::VideosPatch(LotroDatManager *mgr, QObject *parent) : Patch("VideosPatch", mgr, parent)
+{
+}
+
+void VideosPatch::checkForUpdates()
+{
+    QUrlQuery query; // query for building GET-request aka patch-version
+
+    foreach (QString db_name, databases_names) {
+        query.addQueryItem(db_name, "100");
+    }
+
+    QUrl target_url;
+    target_url.setUrl(Settings::getValue("Network/patch_updates_url").toString());
+    target_url.setQuery(query);
+
+    QByteArray target_array;
+    Downloader downloader;
+    downloader.setUrl(target_url);
+    downloader.targetBytearray = &target_array;
+    downloader.start();
+    downloader.waitForDownloaded();
+
+    if (target_array.isEmpty()) {
+        qWarning() << *this << "Cannot check for updates, target_array is empty!";
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    QStringList patch_info = QString(target_array).split('|');
+    if (patch_info.size() != databases_names.size()) {
+        qCritical() << __FUNCTION__ << "Incorrect patches number! Data: " << patch_info;
+        emit errorOccured(E_CHECKFORUPDATES, this, "");
+        emit operationFinished(E_CHECKFORUPDATES, this);
+        return;
+    }
+
+    for (int i = 0; i < databases_names.size(); ++i) {
+        QStringList patch_data = patch_info[i].split(":::");
+        if (patch_data.size() != 3) {
+            qCritical() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << patch_data;
+            emit errorOccured(E_CHECKFORUPDATES, this, "");
+            emit operationFinished(E_CHECKFORUPDATES, this);
+            return;
+        }
+
+        QString patch_filename = Settings::getValue("General/PatchDownloadDir").toString() + "/" + QUrl(patch_data[0]).fileName();
+
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/url", patch_data[0]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/hashsum", patch_data[1]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/datetime", patch_data[2]);
+        Settings::setValue("PatchDatabases/" + databases_names[i] + "/path", patch_filename);
+    }
+
+    emit operationFinished(E_CHECKFORUPDATES, this);
+}
+
+void VideosPatch::download()
+{
+    foreach (QString db_name, databases_names) {
+        QString settings_prefix = "PatchDatabases/" + db_name;
+
+        QString target_filename = QApplication::applicationDirPath() + "/" + Settings::getValue(settings_prefix + "/path").toString();
+        qDebug() << patch_name_ << ": Checking if file " << target_filename << " matches its hashsum";
+
+        if (FileSystem::fileHash(target_filename) == Settings::getValue(settings_prefix + "/hashsum").toString()) {
+            qInfo() << *this << ": file " << target_filename << " is up-to-date, no need to download";
+            continue;
+        }
+
+        FileSystem::createFilePath(target_filename);
+        QFile* target_file = new QFile(target_filename);
+        if (!target_file->open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+            qWarning() << *this << "Cannot open file " << target_filename;
+            continue;
+        }
+
+        qInfo() << *this << ": beginning download of file " << target_filename;
+
+        Downloader* downloader = new Downloader();
+        downloader->setUrl(Settings::getValue(settings_prefix + "/url").toUrl());
+        downloader->targetFile = target_file;
+        connect(downloader, &Downloader::progressChanged, this, &VideosPatch::onDownloaderProgressChanged);
+        connect(downloader, &Downloader::downloadFinished, this, &VideosPatch::onDownloaderFinished);
+        downloaders_.insert(downloader);
+        downloader->start();
+    }
+
+    if (downloaders_.empty()) {
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}
+
+void VideosPatch::install()
+{
+    QThread::sleep(3);
+    emit operationFinished(E_INSTALL, this);
+}
+
+void VideosPatch::activate()
+{
+    QThread::sleep(3);
+    emit operationFinished(E_ACTIVATE, this);
+}
+
+void VideosPatch::onDownloaderProgressChanged(Downloader *, Downloader::Status)
+{
+    Downloader::Status all_downloads_status;
+    foreach (Downloader* downloader, downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    emit progressChanged(OperationProgress(all_downloads_status), this);
+}
+
+void VideosPatch::onDownloaderFinished(Downloader *ptr)
+{
+    ptr->targetFile->close();
+    ptr->targetFile->deleteLater();
+
+    Downloader::Status all_downloads_status;
+    for (const Downloader* downloader : downloaders_) {
+        all_downloads_status = all_downloads_status + downloader->getDownloadStatus();
+    }
+
+    if (!all_downloads_status.running) {
+        for (Downloader* downloader : downloaders_) {
+            downloader->deleteLater();
+        }
+        downloaders_.clear();
+        emit operationFinished(E_DOWNLOAD, this);
+    }
+}

+ 31 - 0
src/Legacy/models/patch/videospatch.h

@@ -0,0 +1,31 @@
+#ifndef VIDEOSPATCH_H
+#define VIDEOSPATCH_H
+
+#include <QObject>
+
+#include "models/patch/patch.h"
+
+class VideosPatch : public Patch
+{
+    Q_OBJECT
+
+public:
+    VideosPatch(LotroDatManager* mgr, QObject* parent = nullptr);
+
+private slots:
+    virtual void checkForUpdates() override;
+    virtual void download() override;
+    virtual void install() override;
+    virtual void activate() override;
+
+private slots:
+    void onDownloaderProgressChanged(Downloader* ptr, Downloader::Status status);
+    void onDownloaderFinished(Downloader* ptr);
+
+private:
+    const QStringList databases_names = {
+        "video"
+    };
+};
+
+#endif // VIDEOSPATCH_H

+ 0 - 299
src/Legacy/models/patchdownloader.cpp

@@ -1,299 +0,0 @@
-#include "patchdownloader.h"
-#include "models/filesystem.h"
-#include "models/settings.h"
-#include <QApplication>
-#include <QDir>
-#include <QStringList>
-#include <QUrlQuery>
-#include <QVariant>
-#include <QDebug>
-
-Q_DECLARE_METATYPE(QList<PatchDownloadData>)
-
-PatchDownloader::PatchDownloader(QObject *parent) : QObject(parent), patch_download_dir(QApplication::applicationDirPath() + "/data")
-{
-    qRegisterMetaType<QList<PatchDownloadData>>();
-    active_downloads_number = 0;
-
-    connect(&update_check_timer, &QTimer::timeout, this, &PatchDownloader::checkForUpdates);
-    update_check_timer.setInterval(1000 * 60 * 5); // 5 minutes
-    update_check_timer.start();
-}
-
-PatchDownloader::~PatchDownloader()
-{
-    foreach (Downloader* downloader, downloads_list) {
-        downloader->waitForDownloaded();
-        downloader->deleteLater();
-    }
-}
-
-QString PatchDownloader::getPredictedDownloadSizeFormatted()
-{
-    double mbytes = 0;
-
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 650;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 80;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 120;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 3;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 4;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 1;
-    }
-    if (Settings::getValue("DatabaseDownload/sound").toString() == "Enabled") {
-        mbytes += 2100;
-    }
-
-    QString unit = "Мб";
-    if (mbytes > 1024) {
-        mbytes /= 1024;
-        unit = "Гб";
-    }
-
-    return QString::number(mbytes, 'f', 1) + " " + unit;
-}
-
-QString PatchDownloader::getDatabasePathByPatchName(QString name)
-{
-    return patch_download_dir.absolutePath() + "/" + patch_data[name].url.fileName();
-}
-
-void PatchDownloader::checkForUpdates()
-{
-    foreach (QString patch_name, all_patch_names) {
-        if (downloads_list.contains(patch_name))
-            continue;
-
-        downloads_list[patch_name] = new Downloader();
-        connect(downloads_list[patch_name], &Downloader::progressChanged, this, &PatchDownloader::onDownloaderProgressChanged);
-        connect(downloads_list[patch_name], &Downloader::downloadFinished, this, &PatchDownloader::onDownloaderCompleted);
-    }
-
-    if (active_downloads_number > 0) {
-        qDebug() << "PatchDownloader: downloads are not ready yet, passing checkForUpdates";
-        return;
-    }
-
-    emit checkForUpdatesStarted();
-    if (!updatePatchList()) {
-        emit checkForUpdatesFinished();
-        return;
-    }
-    removeOldPatchesFromDirecrory();
-    DownloadMissingPatches();
-    qDebug() << "Finished checking for updates!";
-    emit checkForUpdatesFinished();
-}
-
-void PatchDownloader::onDownloaderCompleted(Downloader *downloader_ptr)
-{
-    if (downloader_ptr->targetFile) {
-        downloader_ptr->targetFile->close();
-        downloader_ptr->targetFile->deleteLater();
-    }
-
-    QString patch_finished_download_name = "none";
-
-    foreach (QString patch_name, downloads_list.keys()) {
-        if (downloads_list[patch_name] == downloader_ptr)
-            patch_finished_download_name = patch_name;
-    }
-
-    if (patch_finished_download_name != "none") {
-        QString patch_filepath = patch_download_dir.absolutePath() + "/" + patch_data[patch_finished_download_name].url.fileName();
-        Settings::setValue("DatabaseDownloadDate/" + patch_data[patch_finished_download_name].name, QDate::currentDate().toString("dd.MM.yyyy"));
-        Settings::setValue("DatabaseApplied/" + patch_data[patch_finished_download_name].name, "False");
-        Settings::setValue("DatabasePath/" + patch_data[patch_finished_download_name].name, patch_filepath);
-
-        emit changePatchStatus(patch_finished_download_name, "Загрузка завершена");
-    }
-
-    active_downloads_number--;
-
-    if (active_downloads_number == 0) {
-        emit downloadCompleted();
-    }
-}
-
-void PatchDownloader::onDownloaderProgressChanged(Downloader*)
-{
-
-    quint64 totalSize = 0;
-    quint64 downloadedSize = 0;
-    quint64 summary_speed = 0;
-    quint64 time_elapsed = 0;
-
-    foreach (Downloader* downloader, downloads_list) {
-        totalSize += downloader->getBytesTotal();
-        downloadedSize += downloader->getBytesDownloaded();
-
-        if (downloader->getBytesTotal() != downloader->getBytesDownloaded()) {
-            summary_speed += downloader->getSpeed();
-        }
-    }
-
-    time_elapsed = (totalSize - downloadedSize) / qMax(quint64(1), summary_speed);
-
-    QList<PatchDownloadData> download_data;
-    download_data << PatchDownloadData({"general", downloadedSize, totalSize, Downloader::getSpeedFormatted(summary_speed), Downloader::getElapsedTimeFormatted(time_elapsed)});
-
-    foreach (QString patch_name, downloads_list.keys()) {
-        Downloader* patch_download = downloads_list[patch_name];
-        if (patch_download->isStarted()) {
-            download_data << PatchDownloadData({patch_name, patch_download->getBytesDownloaded(), patch_download->getBytesTotal(),
-                             Downloader::getSpeedFormatted(patch_download->getSpeed()),
-                             Downloader::getElapsedTimeFormatted(patch_download->getElapsedTime())});
-        }
-    }
-
-    emit progressChanged(download_data);
-}
-
-int PatchDownloader::versionFromPatchFilename(QString filename)
-{
-    int version = 0;
-    for (int i = filename.indexOf("_v") + 2; i < filename.indexOf("_v") + 7; i += 2) {
-        version = version * 10 + (filename.at(i).toLatin1() - '0');
-    }
-    return version;
-}
-
-bool PatchDownloader::updatePatchList()
-{
-    QUrlQuery query; // query for building GET-request aka patch-version
-
-    foreach (QString patch_name, all_patch_names) {
-        query.addQueryItem(patch_name, "100");
-    }
-
-    QUrl target_url;
-    target_url.setUrl("http://translate.lotros.ru/groupware/check_updates");
-    target_url.setQuery(query);
-
-    QByteArray target_array;
-    Downloader downloader;
-    downloader.setUrl(target_url);
-    downloader.targetBytearray = &target_array;
-    downloader.start();
-    downloader.waitForDownloaded();
-
-    if (target_array.isEmpty()) {
-        qDebug() << __FUNCTION__ << "Cannot download, target_array is empty!";
-        emit getPatchListError();
-        return false;
-    }
-
-    QStringList entry_list = QString(target_array).split('|');
-
-    if (entry_list.size() != all_patch_names.size()) {
-        qDebug() << __FUNCTION__ << "Entry list size is not equal to patch names size!" << QString(target_array);
-        emit getPatchListError();
-        return false;
-    }
-
-    for (int i = 0; i < entry_list.size(); ++i) {
-        QStringList current_patch_data = entry_list[i].split(":::");
-        if (current_patch_data.size() != 3) {
-            qDebug() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << entry_list[i];
-            emit getPatchListError();
-            return false;
-        }
-        patch_data[all_patch_names[i]] = {current_patch_data[0], current_patch_data[1], current_patch_data[2], all_patch_names[i]};
-    }
-    return true;
-}
-
-bool PatchDownloader::removeOldPatchesFromDirecrory()
-{
-    QStringList actual_hash_list;
-    foreach (Patch patch, patch_data) {
-        actual_hash_list.append(patch.md5_hash);
-    }
-
-    qDebug() << "Removing old patches. Current hash list " << actual_hash_list;
-
-    QStringList paths = patch_download_dir.entryList(QDir::Files);
-
-    foreach (QString filename, paths) {
-        QString hash = FileSystem::fileHash(patch_download_dir.absolutePath() + "/" + filename, QCryptographicHash::Md5);
-        if (!actual_hash_list.contains(hash)) {
-            qDebug() << "File " << filename << " with hash " << hash << "seems outdated, deleting!";
-            if (!QFile::remove(patch_download_dir.absolutePath() + "/" +filename)) {
-                qDebug() << __FUNCTION__ << "Unable to remove file " << filename;
-                emit removeFileError(patch_download_dir.absolutePath() + "/" + filename);
-            }
-        }
-    }
-    return true;
-}
-
-bool PatchDownloader::DownloadMissingPatches()
-{
-    QDir dir(patch_download_dir);
-    if (!dir.exists())
-        QDir().mkdir(patch_download_dir.absolutePath());
-
-    bool download_started = false;
-
-    foreach (Patch patch, patch_data) {
-        QString patch_filepath = patch_download_dir.absolutePath() + "/" + patch.url.fileName();
-
-        qDebug() << "Patch" << patch.name << "is marked as" << Settings::getValue("DatabaseDownload/" + patch.name).toString();
-
-        if (Settings::getValue("DatabaseDownload/" + patch.name).toString() != "Enabled") {
-            emit changePatchStatus(patch.name, "Не выбраны для установки");
-            continue;
-        }
-
-        if (FileSystem::fileExists(patch_filepath)) {
-            if (FileSystem::fileHash(patch_filepath, QCryptographicHash::Md5) == patch.md5_hash) {
-                Settings::setValue("DatabasePath/" + patch.name, patch_filepath);
-                QString download_date = Settings::getValue("DatabaseDownloadDate/" + patch.name).toString();
-                emit changePatchStatus(patch.name, "Актуальная версия (" + download_date + ")");
-                continue;
-            }
-
-            Settings::setValue("DatabasePath/" + patch.name, "none");
-
-            if (!QFile::remove(patch_filepath)) {
-                qDebug() << __FUNCTION__ << "Unable to remove file " << patch_filepath;
-                emit changePatchStatus(patch.name, "Ошибка обновления 0x1");
-                emit removeFileError(patch_filepath);
-                continue;
-            }
-        }
-
-        emit changePatchStatus(patch.name, "Ожидает скачивания");
-
-        if (!download_started) {
-            download_started = true;
-            qDebug() << "Started downloads of PatchDownloader!";
-            emit downloadStarted();
-        }
-
-        qDebug() << "Starting download of file " << patch_filepath << " from url " << patch.url;
-
-        downloads_list[patch.name]->setUrl(patch.url);
-        downloads_list[patch.name]->targetFile = new QFile(patch_filepath, downloads_list[patch.name]);
-        downloads_list[patch.name]->targetFile->open(QIODevice::ReadWrite);
-        downloads_list[patch.name]->start();
-        active_downloads_number++;
-    }
-
-    foreach (Patch patch, patch_data) {
-        downloads_list[patch.name]->waitForDownloaded();
-    }
-
-    qDebug() << "Finished downloading patches!";
-    return true;
-}

+ 0 - 79
src/Legacy/models/patchdownloader.h

@@ -1,79 +0,0 @@
-#ifndef PATCHDOWNLOADER_H
-#define PATCHDOWNLOADER_H
-
-#include <QObject>
-#include <QTimer>
-#include <QDate>
-#include <QDir>
-#include <QSettings>
-#include <QMap>
-#include "models/downloader.h"
-
-struct Patch {
-    QUrl url;
-    QString md5_hash;
-    QString datetime;
-    QString name;
-};
-
-struct PatchDownloadData {
-    QString patch_name;
-    quint64 bytesDownloaded;
-    quint64 bytesTotal;
-    QString download_speed_formatted;
-    QString elapsed_time_formatted;
-};
-
-class PatchDownloader : public QObject
-{
-    Q_OBJECT
-public:
-    explicit PatchDownloader(QObject *parent = nullptr);
-    ~PatchDownloader();
-    void enableAutoUpdate();
-    void disableAutoUpdate();
-    void stopAllDownloads();
-    void startAllDownloads();
-    QString getPredictedDownloadSizeFormatted();
-    QString getDatabasePathByPatchName(QString name);
-
-public slots:
-    void checkForUpdates();
-
-private slots:
-    void onDownloaderCompleted(Downloader* downloader_ptr);
-    void onDownloaderProgressChanged(Downloader* downloader_ptr);
-
-signals:
-    void checkForUpdatesStarted();
-    void checkForUpdatesFinished();
-    void getPatchListError();
-    void removeFileError(QString filename);
-    void downloadNewFilesError();
-
-    void downloadCompleted();
-    void downloadStarted();
-    void progressChanged(QList<PatchDownloadData>);
-
-    void changePatchStatus(QString patch_name, QString status);
-    void patchDateChanged(QString patch_name, QDate patch_date);
-
-private:
-    int versionFromPatchFilename(QString filename);
-    bool updatePatchList();
-    bool removeOldPatchesFromDirecrory();
-    bool DownloadMissingPatches();
-
-private:
-    quint8 active_downloads_number = 0;
-
-    const QDir patch_download_dir;
-    const QStringList all_patch_names = {"sound", "text", "image", "loadscreen", "texture", "font", "video"};
-
-    QTimer update_check_timer;
-
-    QMap<QString, Downloader*> downloads_list;
-    QMap<QString, Patch> patch_data;
-};
-
-#endif // PATCHDOWNLOADER_H

+ 168 - 0
src/Legacy/models/patchlist.cpp

@@ -0,0 +1,168 @@
+#include "patchlist.h"
+
+#include <QDebug>
+
+PatchList::PatchList(LotroDatManager *mgr, QObject *parent) : QObject(parent)
+{
+    lotro_mgr_ = mgr;
+
+    qInfo() << "PatchList: Initialising patchsets";
+    texts_patch_ = new TextsPatch(mgr);
+    graphics_patch_ = new GraphicsPatch(mgr);
+    sounds_patch_ = new SoundsPatch(mgr);
+    videos_patch_ = new VideosPatch(mgr);
+
+    qInfo() << "PatchList: Patchsets were initialized, initializing threads";
+    texts_patch_thread_ = new QThread(this);
+    graphics_patch_thread_ = new QThread(this);
+    sounds_patch_thread_ = new QThread(this);
+    videos_patch_thread_ = new QThread(this);
+
+    texts_patch_->moveToThread(texts_patch_thread_);
+    graphics_patch_->moveToThread(graphics_patch_thread_);
+    sounds_patch_->moveToThread(sounds_patch_thread_);
+    videos_patch_->moveToThread(videos_patch_thread_);
+
+    connect(texts_patch_, &TextsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
+    connect(graphics_patch_, &GraphicsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
+    connect(sounds_patch_, &SoundsPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
+    connect(videos_patch_, &VideosPatch::operationStarted, this, &PatchList::onPatchOperationStarted, Qt::QueuedConnection);
+
+    connect(texts_patch_, &TextsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
+    connect(graphics_patch_, &GraphicsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
+    connect(sounds_patch_, &SoundsPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
+    connect(videos_patch_, &VideosPatch::operationFinished, this, &PatchList::onPatchOperationFinished, Qt::QueuedConnection);
+
+    connect(texts_patch_, &TextsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
+    connect(graphics_patch_, &GraphicsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
+    connect(sounds_patch_, &SoundsPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
+    connect(videos_patch_, &VideosPatch::progressChanged, this, &PatchList::onPatchOperationProgressChanged, Qt::QueuedConnection);
+
+    connect(lotro_mgr_, &LotroDatManager::operationFinished, this, &PatchList::onLotroManagerOperationFinished, Qt::QueuedConnection);
+
+    qInfo() << "PatchList: Patchset threads initialized, starting workers";
+    texts_patch_thread_->start();
+    graphics_patch_thread_->start();
+    sounds_patch_thread_->start();
+    videos_patch_thread_->start();
+}
+
+PatchList::~PatchList()
+{
+    qDebug() << "PatchList: finishing work";
+    texts_patch_thread_->quit();
+    graphics_patch_thread_->quit();
+    sounds_patch_thread_->quit();
+    videos_patch_thread_->quit();
+
+    if (!texts_patch_thread_->wait(500)) {
+        qDebug() << "PatchList: ERROR, TEXTS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
+        texts_patch_thread_->terminate();
+        texts_patch_thread_->wait();
+    }
+
+    if (!graphics_patch_thread_->wait(500)) {
+        qDebug() << "PatchList: ERROR, GRAPHICS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
+        graphics_patch_thread_->terminate();
+        graphics_patch_thread_->wait();
+    }
+
+    if (!sounds_patch_thread_->wait(500)) {
+        qDebug() << "PatchList: ERROR, SOUNDS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
+        sounds_patch_thread_->terminate();
+        sounds_patch_thread_->wait();
+    }
+
+    if (!videos_patch_thread_->wait(500)) {
+        qDebug() << "PatchList: ERROR, VIDEOS PATCHSET DIDNT STOP, FORCEFULLY TERMINATING!";
+        videos_patch_thread_->terminate();
+        videos_patch_thread_->wait();
+    }
+
+    qDebug() << "Patchlist: all jobs stopped, destroying";
+
+    delete texts_patch_;
+    delete graphics_patch_;
+    delete sounds_patch_;
+    delete videos_patch_;
+}
+
+QList<Patch *> PatchList::getPatchList()
+{
+    return {texts_patch_, graphics_patch_, sounds_patch_, videos_patch_};
+}
+
+void PatchList::onPatchOperationProgressChanged(Patch::OperationProgress operation_progress, Patch *patch)
+{
+    patch_operations_status_[patch] = operation_progress;
+
+    Patch::OperationProgress total_status;
+    for (const Patch::OperationProgress &st : patch_operations_status_) {
+        total_status = total_status + st;
+    }
+    emit progressChanged(total_status);
+}
+
+void PatchList::onLotroManagerOperationFinished(QString operation_name, QVector<QVariant>, bool result)
+{
+    if (operation_name == "initializeManager") {
+        --active_operations_num_;
+
+        if (active_operations_num_ == 0) {
+            emit patchOperationsFinished();
+        }
+
+        if (result) {
+            startAutoUpdate();
+            update();
+        } else {
+            qCritical() << "DatManager initialisation error!!!";
+        }
+    }
+}
+
+void PatchList::startAutoUpdate()
+{
+    auto_updates_enabled_ = true;
+    patch_update_timer.start();
+}
+
+void PatchList::initialize() {
+    ++active_operations_num_;
+    emit patchOperationsStarted();
+    QMetaObject::invokeMethod(lotro_mgr_, &LotroDatManager::initializeManager, Qt::QueuedConnection);
+}
+
+void PatchList::onPatchOperationStarted(Patch::Operation, Patch*)
+{
+    if (active_operations_num_ == 0) {
+        emit patchOperationsStarted();
+    }
+    ++active_operations_num_;
+}
+
+void PatchList::onPatchOperationFinished(Patch::Operation, Patch*)
+{
+    --active_operations_num_;
+    if (active_operations_num_ == 0) {
+        emit patchOperationsFinished();
+    }
+}
+
+void PatchList::update()
+{
+    if (active_operations_num_ > 0) {
+        qWarning() << "Tried to start patch update chain, while there are already some operations on them!";
+        return;
+    }
+    qInfo() << "PatchList: Starting update chain!";
+    for (Patch* patch : getPatchList()) {
+        QMetaObject::invokeMethod(patch, "enqueue",
+                                  Q_ARG(QList<Patch::Operation>,
+                                        QList<Patch::Operation>({Patch::E_CHECKFORUPDATES,
+                                                                 Patch::E_DOWNLOAD,
+                                                                 Patch::E_INSTALL,
+                                                                 Patch::E_ACTIVATE})));
+    }
+}
+

+ 68 - 0
src/Legacy/models/patchlist.h

@@ -0,0 +1,68 @@
+#ifndef PATCHLIST_H
+#define PATCHLIST_H
+
+#include <QObject>
+#include <QTimer>
+#include <QThread>
+#include <QList>
+
+#include "models/patch/patch.h"
+#include "models/patch/textspatch.h"
+#include "models/patch/graphicspatch.h"
+#include "models/patch/soundspatch.h"
+#include "models/patch/videospatch.h"
+
+class PatchList : public QObject
+{
+    Q_OBJECT
+public:
+    explicit PatchList(LotroDatManager* mgr, QObject *parent = nullptr);
+
+    ~PatchList();
+
+    QList<Patch *> getPatchList();
+
+signals:
+    void progressChanged(Patch::OperationProgress total_status);
+    void patchOperationsStarted();
+    void patchOperationsFinished();
+
+public slots:
+    void startAutoUpdate();
+
+    void update();
+
+    void initialize();
+
+private slots:
+    void onPatchOperationStarted(Patch::Operation operation, Patch* patch);
+    void onPatchOperationFinished(Patch::Operation operation, Patch* patch);
+    void onPatchOperationProgressChanged(Patch::OperationProgress operation_progress, Patch* patch);
+
+private slots:
+    void onLotroManagerOperationFinished(QString operation_name, QVector<QVariant> args, bool result);
+
+private:
+    LotroDatManager *lotro_mgr_ = nullptr;
+    bool lotro_mgr_initialised_ = false;
+    bool operations_running_ = false;
+
+    TextsPatch *texts_patch_ = nullptr;
+    GraphicsPatch *graphics_patch_ = nullptr;
+    SoundsPatch *sounds_patch_ = nullptr;
+    VideosPatch *videos_patch_ = nullptr;
+
+    QThread *texts_patch_thread_;
+    QThread *graphics_patch_thread_ = nullptr;
+    QThread *sounds_patch_thread_ = nullptr;
+    QThread *videos_patch_thread_ = nullptr;
+
+    QMap<Patch*, Patch::OperationProgress> patch_operations_status_;
+
+    quint32 active_operations_num_ = 0;
+    bool auto_updates_enabled_ = false;
+
+    QTimer patch_update_timer;    
+};
+
+#endif // PATCHLIST_H

+ 31 - 45
src/Legacy/models/settings.h

@@ -10,7 +10,8 @@ static QMap<QString, QVariant> defaults = {
     // General info
     {"General/UI_scale", "100"},
     {"General/CurrentInitStage", "0"},
-    {"General/MicroUpdates", "Disabled"},
+    {"General/MicroUpdates", false},
+    {"General/PatchDownloadDir", "data"},
 
     // Lotro Manager
     {"Lotro/game_path", "none"},
@@ -19,50 +20,35 @@ static QMap<QString, QVariant> defaults = {
     {"Lotro/no_splash_screen", "True"},
 
     // Databases download settings
-    {"DatabaseDownload/sound", "Disabled"},
-    {"DatabaseDownload/text", "Disabled"},
-    {"DatabaseDownload/image", "Disabled"},
-    {"DatabaseDownload/loadscreen", "Disabled"},
-    {"DatabaseDownload/texture", "Disabled"},
-    {"DatabaseDownload/font", "Disabled"},
-    {"DatabaseDownload/video", "Disabled"},
-
-    // Databases applied status
-    {"DatabaseApplied/sound", "False"},
-    {"DatabaseApplied/text", "False"},
-    {"DatabaseApplied/image", "False"},
-    {"DatabaseApplied/loadscreen", "False"},
-    {"DatabaseApplied/texture", "False"},
-    {"DatabaseApplied/font", "False"},
-    {"DatabaseApplied/video", "False"},
-
-    // Databases downloaded paths
-    {"DatabasePath/sound", "none"},
-    {"DatabasePath/text", "none"},
-    {"DatabasePath/image", "none"},
-    {"DatabasePath/loadscreen", "none"},
-    {"DatabasePath/texture", "none"},
-    {"DatabasePath/font", "none"},
-    {"DatabasePath/video", "none"},
-
-    // Databases download date
-    {"DatabaseDownloadDate/sound", "none"},
-    {"DatabaseDownloadDate/text", "none"},
-    {"DatabaseDownloadDate/image", "none"},
-    {"DatabaseDownloadDate/loadscreen", "none"},
-    {"DatabaseDownloadDate/texture", "none"},
-    {"DatabaseDownloadDate/font", "none"},
-    {"DatabaseDownloadDate/video", "none"},
+
+    {"DatabaseDownload/text", false},          // TextsPatch
+    {"DatabaseDownload/font", false},          // TextsPatch
+    {"DatabaseDownload/image", false},         // GraphicsPatch
+    {"DatabaseDownload/loadscreen", false},    // GraphicsPatch
+    {"DatabaseDownload/texture", false},       // GraphicsPatch
+    {"DatabaseDownload/sound", false},         // SoundsPatch
+    {"DatabaseDownload/video", false},         // VideosPatch
+
+    // Flags, meaning that database is fresh and needs to be installed
+
+    {"DatabaseUpdated/text", false},           // TextsPatch
+    {"DatabaseUpdated/font", false},           // TextsPatch
+    {"DatabaseUpdated/image", false},          // GraphicsPatch
+    {"DatabaseUpdated/loadscreen", false},     // GraphicsPatch
+    {"DatabaseUpdated/texture", false},        // GraphicsPatch
+    {"DatabaseUpdated/sound", false},          // SoundsPatch
+    {"DatabaseUpdated/video", false},          // VideosPatch
 
     // Localisation components
-    {"Components/fonts", "Disabled"},
-    {"Components/texts_main", "Disabled"},
-    {"Components/texts_items", "Disabled"},
-    {"Components/texts_emotes", "Disabled"},
-    {"Components/maps", "Disabled"},
-    {"Components/loadscreens", "Disabled"},
-    {"Components/sounds", "Disabled"},
-    {"Components/videos", "Disabled"},
+    {"Components/fonts", false},               // TextsPatch
+    {"Components/texts_main", false},          // TextsPatch
+    {"Components/texts_items", false},         // TextsPatch
+    {"Components/texts_emotes", false},        // TextsPatch
+    {"Components/maps", false},                // ImagesPatch
+    {"Components/loadscreens", false},         // ImagesPatch
+    {"Components/textures", false},            // ImagesPatch
+    {"Components/sounds", false},              // SoundsPatch
+    {"Components/videos", false},              // VideosPatch
 
     // Network
     {"Network/site_url", "http://translate.lotros.ru/"},
@@ -73,7 +59,8 @@ static QMap<QString, QVariant> defaults = {
     {"Network/game_servers_status", "http://translate.lotros.ru/servers.txt"},
     {"Network/game_servers_message", "http://translate.lotros.ru/profmessage.txt"},
     {"Network/weekly_code_url", "http://translate.lotros.ru/coupon.txt"},
-    {"Network/news_list_url", "http://translate.lotros.ru/groupware/launcher_news/30/1"}
+    {"Network/news_list_url", "http://translate.lotros.ru/groupware/launcher_news/30/1"},
+    {"Network/patch_updates_url", "http://translate.lotros.ru/groupware/check_updates"}
 };
 
 static void setDefaultSettings()
@@ -85,7 +72,6 @@ static void setDefaultSettings()
 }
 
 static QVariant getValue(QString key) {
-    Q_ASSERT(defaults.contains(key)); // any required key should be pre-defined in defaults
     QSettings settings;
     return settings.value(key, defaults[key]);
 }

+ 13 - 4
src/Legacy/object_script.Legacy.Debug

@@ -4,9 +4,7 @@ INPUT(
 ./..\..\build\debug\Legacy\obj\main.o
 ./..\..\build\debug\Legacy\obj\downloader.o
 ./..\..\build\debug\Legacy\obj\lotrodatmanager.o
-./..\..\build\debug\Legacy\obj\patchdownloader.o
 ./..\..\build\debug\Legacy\obj\selfupdater.o
-./..\..\build\debug\Legacy\obj\internallogicmanager.o
 ./..\..\build\debug\Legacy\obj\helpwidget.o
 ./..\..\build\debug\Legacy\obj\mainwindow.o
 ./..\..\build\debug\Legacy\obj\menuentry.o
@@ -21,12 +19,17 @@ INPUT(
 ./..\..\build\debug\Legacy\obj\dialogwindow.o
 ./..\..\build\debug\Legacy\obj\qsmoothscrollarea.o
 ./..\..\build\debug\Legacy\obj\fonts.o
+./..\..\build\debug\Legacy\obj\patch.o
+./..\..\build\debug\Legacy\obj\soundspatch.o
+./..\..\build\debug\Legacy\obj\videospatch.o
+./..\..\build\debug\Legacy\obj\textspatch.o
+./..\..\build\debug\Legacy\obj\graphicspatch.o
+./..\..\build\debug\Legacy\obj\patchlist.o
+./..\..\build\debug\Legacy\obj\legacyapplication.o
 ./..\..\build\debug\Legacy\obj\legacy_plugin_import.o
 ./..\..\build\debug\Legacy\obj\moc_downloader.o
 ./..\..\build\debug\Legacy\obj\moc_lotrodatmanager.o
-./..\..\build\debug\Legacy\obj\moc_patchdownloader.o
 ./..\..\build\debug\Legacy\obj\moc_selfupdater.o
-./..\..\build\debug\Legacy\obj\moc_internallogicmanager.o
 ./..\..\build\debug\Legacy\obj\moc_helpwidget.o
 ./..\..\build\debug\Legacy\obj\moc_mainwindow.o
 ./..\..\build\debug\Legacy\obj\moc_menuentry.o
@@ -40,4 +43,10 @@ INPUT(
 ./..\..\build\debug\Legacy\obj\moc_weeklycodewidget.o
 ./..\..\build\debug\Legacy\obj\moc_dialogwindow.o
 ./..\..\build\debug\Legacy\obj\moc_qsmoothscrollarea.o
+./..\..\build\debug\Legacy\obj\moc_patch.o
+./..\..\build\debug\Legacy\obj\moc_soundspatch.o
+./..\..\build\debug\Legacy\obj\moc_videospatch.o
+./..\..\build\debug\Legacy\obj\moc_textspatch.o
+./..\..\build\debug\Legacy\obj\moc_graphicspatch.o
+./..\..\build\debug\Legacy\obj\moc_patchlist.o
 );

+ 13 - 4
src/Legacy/object_script.Legacy.Release

@@ -2,9 +2,7 @@ INPUT(
 ./..\..\build\release\Legacy\obj\main.o
 ./..\..\build\release\Legacy\obj\downloader.o
 ./..\..\build\release\Legacy\obj\lotrodatmanager.o
-./..\..\build\release\Legacy\obj\patchdownloader.o
 ./..\..\build\release\Legacy\obj\selfupdater.o
-./..\..\build\release\Legacy\obj\internallogicmanager.o
 ./..\..\build\release\Legacy\obj\helpwidget.o
 ./..\..\build\release\Legacy\obj\mainwindow.o
 ./..\..\build\release\Legacy\obj\menuentry.o
@@ -19,12 +17,17 @@ INPUT(
 ./..\..\build\release\Legacy\obj\dialogwindow.o
 ./..\..\build\release\Legacy\obj\qsmoothscrollarea.o
 ./..\..\build\release\Legacy\obj\fonts.o
+./..\..\build\release\Legacy\obj\patch.o
+./..\..\build\release\Legacy\obj\soundspatch.o
+./..\..\build\release\Legacy\obj\videospatch.o
+./..\..\build\release\Legacy\obj\textspatch.o
+./..\..\build\release\Legacy\obj\graphicspatch.o
+./..\..\build\release\Legacy\obj\patchlist.o
+./..\..\build\release\Legacy\obj\legacyapplication.o
 ./..\..\build\release\Legacy\obj\legacy_plugin_import.o
 ./..\..\build\release\Legacy\obj\moc_downloader.o
 ./..\..\build\release\Legacy\obj\moc_lotrodatmanager.o
-./..\..\build\release\Legacy\obj\moc_patchdownloader.o
 ./..\..\build\release\Legacy\obj\moc_selfupdater.o
-./..\..\build\release\Legacy\obj\moc_internallogicmanager.o
 ./..\..\build\release\Legacy\obj\moc_helpwidget.o
 ./..\..\build\release\Legacy\obj\moc_mainwindow.o
 ./..\..\build\release\Legacy\obj\moc_menuentry.o
@@ -38,4 +41,10 @@ INPUT(
 ./..\..\build\release\Legacy\obj\moc_weeklycodewidget.o
 ./..\..\build\release\Legacy\obj\moc_dialogwindow.o
 ./..\..\build\release\Legacy\obj\moc_qsmoothscrollarea.o
+./..\..\build\release\Legacy\obj\moc_patch.o
+./..\..\build\release\Legacy\obj\moc_soundspatch.o
+./..\..\build\release\Legacy\obj\moc_videospatch.o
+./..\..\build\release\Legacy\obj\moc_textspatch.o
+./..\..\build\release\Legacy\obj\moc_graphicspatch.o
+./..\..\build\release\Legacy\obj\moc_patchlist.o
 );

+ 9 - 11
src/Legacy/widgets/aboutwidget.cpp

@@ -1,19 +1,17 @@
-#include "widgets/aboutwidget.h"
 #include "ui_aboutwidget.h"
-#include "models/filesystem.h"
-#include "models/lotrodatmanager.h"
-#include "widgets/mainwindow.h"
-#include "constants.h"
+
+#include "models/patchlist.h"
 #include "models/settings.h"
+#include "constants.h"
+
+#include "widgets/aboutwidget.h"
 
-#include <QDebug>
-#include <QFileDialog>
-#include <QMessageBox>
 #include <QDesktopServices>
 
-AboutWidget::AboutWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent) :
-    QWidget(parent), lotro_manager(lotro_dat_manager),
-    ui(new Ui::AboutWidget)
+AboutWidget::AboutWidget(PatchList *legacy_patches, QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::AboutWidget)
+    , legacy_patches_(legacy_patches)
 {
     ui->setupUi(this);
 }

+ 4 - 8
src/Legacy/widgets/aboutwidget.h

@@ -2,22 +2,19 @@
 #define AboutWidget_H
 
 #include <QWidget>
-#include <QTimer>
-#include <QSettings>
 
 namespace Ui {
 class AboutWidget;
 }
 
-class PatchDownloader;
-class LotroDatManager;
+class PatchList;
 
 class AboutWidget : public QWidget
 {
     Q_OBJECT
 
 public:
-    explicit AboutWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent = 0);
+    explicit AboutWidget(PatchList* legacy_patches, QWidget *parent = 0);
     ~AboutWidget();
 
 public slots:
@@ -36,10 +33,9 @@ private slots:
     void on_goto_donate_clicked();
 
 private:
-    PatchDownloader* patch_updater;
-    LotroDatManager* lotro_manager;
-
     Ui::AboutWidget *ui;
+
+    PatchList *legacy_patches_;
 };
 
 #endif // AboutWidget_H

+ 1 - 1
src/Legacy/widgets/aboutwidget.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>1000</width>
-    <height>538</height>
+    <height>530</height>
    </rect>
   </property>
   <property name="windowTitle">

+ 4 - 7
src/Legacy/widgets/chooseversiondialog.cpp

@@ -1,21 +1,18 @@
 #include "chooseversiondialog.h"
 #include "ui_chooseversiondialog.h"
 
-#include "models/lotrodatmanager.h"
-#include "models/patchdownloader.h"
 #include "models/settings.h"
 #include "widgets/mainwindow.h"
 #include "constants.h"
 
-ChooseVersionDialog::ChooseVersionDialog(PatchDownloader *patch_downloader, LotroDatManager *lotro_dat_manager, QWidget *parent) :
-    QWidget(parent), lotro_manager(lotro_dat_manager),
+ChooseVersionDialog::ChooseVersionDialog(QWidget *parent) :
+    QWidget(parent),
     ui(new Ui::ChooseVersionDialog)
 {
 //    setWindowModality(Qt::WindowModal);
     ui->setupUi(this);
     ui->wait_widget->hide();
     ui->cancel_widget->show();
-
 }
 
 ChooseVersionDialog::~ChooseVersionDialog()
@@ -44,14 +41,14 @@ void ChooseVersionDialog::on_start_patched_clicked()
 {
     ui->wait_widget->show();
     ui->cancel_widget->hide();
-    QMetaObject::invokeMethod(lotro_manager, "StartGame", Qt::QueuedConnection, Q_ARG(LOTRO_DAT::DatLocaleManager::LOCALE, LOTRO_DAT::DatLocaleManager::PATCHED));
+//    QMetaObject::invokeMethod(lotro_manager, "StartGame", Qt::QueuedConnection, Q_ARG(LOTRO_DAT::DatLocaleManager::LOCALE, LOTRO_DAT::DatLocaleManager::PATCHED));
 }
 
 void ChooseVersionDialog::on_start_original_clicked()
 {
     ui->wait_widget->show();
     ui->cancel_widget->hide();
-    QMetaObject::invokeMethod(lotro_manager, "StartGame", Qt::QueuedConnection, Q_ARG(LOTRO_DAT::DatLocaleManager::LOCALE, LOTRO_DAT::DatLocaleManager::ORIGINAL));
+//    QMetaObject::invokeMethod(lotro_manager, "StartGame", Qt::QueuedConnection, Q_ARG(LOTRO_DAT::DatLocaleManager::LOCALE, LOTRO_DAT::DatLocaleManager::ORIGINAL));
 }
 
 void ChooseVersionDialog::on_cancel_clicked()

+ 1 - 4
src/Legacy/widgets/chooseversiondialog.h

@@ -17,7 +17,7 @@ class ChooseVersionDialog : public QWidget
     Q_OBJECT
 
 public:
-    explicit ChooseVersionDialog(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent = 0);
+    explicit ChooseVersionDialog(QWidget *parent = 0);
     ~ChooseVersionDialog();
 
 public slots:
@@ -41,9 +41,6 @@ signals:
     void cancelled();
 
 private:
-    PatchDownloader* patch_updater;
-    LotroDatManager* lotro_manager;
-
     Ui::ChooseVersionDialog *ui;
     QPoint dragPosition;
 };

+ 9 - 4
src/Legacy/widgets/helpwidget.cpp

@@ -1,11 +1,16 @@
-#include "widgets/HelpWidget.h"
 #include "ui_HelpWidget.h"
+
+#include "models/patchlist.h"
+
+#include "widgets/helpwidget.h"
+
 #include <QDebug>
 #include <QScrollBar>
 
-HelpWidget::HelpWidget(PatchDownloader*, LotroDatManager*, QWidget *parent) :
-    QWidget(parent),
-    ui(new Ui::HelpWidget)
+HelpWidget::HelpWidget(PatchList* legacy_patches, QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::HelpWidget)
+    , legacy_patches_(legacy_patches)
 {
     ui->setupUi(this);
     currently_active_entry = ui->menuentry_1;

+ 5 - 6
src/Legacy/widgets/helpwidget.h

@@ -11,15 +11,14 @@ namespace Ui {
 class HelpWidget;
 }
 
-class PatchDownloader;
-class LotroDatManager;
+class PatchList;
 
 class HelpWidget : public QWidget
 {
     Q_OBJECT
 
 public:
-    explicit HelpWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent = 0);
+    explicit HelpWidget(PatchList *legacy_patches, QWidget *parent = 0);
 
     ~HelpWidget();
 
@@ -48,14 +47,14 @@ private slots:
     void on_menuentry_5_clicked();
 
 private:
-    PatchDownloader* patch_updater;
-    LotroDatManager* lotro_manager;
+    Ui::HelpWidget *ui;
+
+    PatchList *legacy_patches_;
 
     QPushButton* currently_active_entry = nullptr;
     QPropertyAnimation* menuHoverWidgetAnimation = nullptr;
     QPropertyAnimation* scrollAreaContentsAnimation = nullptr;
 
-    Ui::HelpWidget *ui;
 };
 
 #endif // HelpWidget_H

+ 151 - 157
src/Legacy/widgets/mainwindow.cpp

@@ -2,11 +2,9 @@
 #include "ui_mainwindow.h"
 #include "constants.h"
 
-#include "models/patchdownloader.h"
-#include "models/lotrodatmanager.h"
-
 #include "widgets/chooseversiondialog.h"
 #include "widgets/dialogwindow.h"
+#include "models/patchlist.h"
 
 #include <QBitmap>
 #include <QPainter>
@@ -23,91 +21,84 @@
 #include <QNetworkAccessManager>
 
 #include <ui_statuswidget.h>
+
 namespace Ui {
 class StatusWidget;
 }
 
-
-MainWindow::MainWindow(QWidget *parent) :
-    QMainWindow(parent, Qt::Window | Qt::FramelessWindowHint),
-    ui(new Ui::MainWindow), menuHoverWidget(nullptr), menuHoverWidgetAnimation(nullptr)
+MainWindow::MainWindow(PatchList *legacy_patches, QWidget *parent)
+    : QMainWindow(parent, Qt::Window | Qt::FramelessWindowHint)
+    , legacy_patches_(legacy_patches)
+    , ui(new Ui::MainWindow)
+    , current_active_entry_(nullptr)
+    , current_hovered_entry_(nullptr)
+    , status_widget_(nullptr)
+    , settings_widget_(nullptr)
+    , help_widget_(nullptr)
+    , about_widget_(nullptr)
+    , choose_locale_dialog_(nullptr)
+    , dialog_window_(nullptr)
+    , menu_hover_widget_(nullptr)
+    , menu_hover_widget_animation_(nullptr)
 {
     constructFonts();
     ui->setupUi(this);
 
-    currently_active_entry = ui->menuentry_1;
-    currently_hover_entry = nullptr;
-
-    qDebug() << "Creating patch downloader instance & thread";
-    lotro_functions_thread = new QThread();
-    patch_updater = new PatchDownloader();
-    lotro_manager = new LotroDatManager();
+    current_active_entry_ = ui->menuentry_1;
+    current_hovered_entry_ = nullptr;
 
-//    connect(patch_updater, &PatchDownloader::checkForUpdatesStarted, this, &MainWindow::onPatchDownloaderStarted, Qt::BlockingQueuedConnection);
-//    connect(patch_updater, &PatchDownloader::checkForUpdatesFinished, this, &MainWindow::onPatchDownloaderFinished, Qt::BlockingQueuedConnection);
-//    connect(lotro_manager, &LotroDatManager::processStarted, this, &MainWindow::onLotroManagerStarted, Qt::BlockingQueuedConnection);
-//    connect(lotro_manager, &LotroDatManager::processFinished, this, &MainWindow::onLotroManagerFinished, Qt::BlockingQueuedConnection);
-//    connect(lotro_manager, &LotroDatManager::caughtError, this, &MainWindow::onLotroManagerErrorOccured, Qt::BlockingQueuedConnection);
+    qDebug() << __FUNCTION__ << "Initialising main frame...";
+    status_widget_ = new StatusWidget(legacy_patches_, this);
+    settings_widget_ = new SettingsWidget(legacy_patches_, this);
+    help_widget_ = new HelpWidget(legacy_patches_, this);
+    about_widget_ = new AboutWidget(legacy_patches_, this);
 
-    connect(lotro_functions_thread, &QThread::finished, patch_updater, &QObject::deleteLater, Qt::QueuedConnection);
-    connect(lotro_functions_thread, &QThread::finished, lotro_manager, &QObject::deleteLater, Qt::QueuedConnection);
-    patch_updater->moveToThread(lotro_functions_thread);
-    lotro_manager->moveToThread(lotro_functions_thread);
-    lotro_functions_thread->start();
+    ui->content_layout->addWidget(status_widget_);
+    ui->content_layout->addWidget(settings_widget_);
+    ui->content_layout->addWidget(help_widget_);
+    ui->content_layout->addWidget(about_widget_);
 
-    qDebug() << "Initialising lotro manager...";
-    QMetaObject::invokeMethod(lotro_manager, "InitialiseManager", Qt::QueuedConnection);
-
-    status_widget = new StatusWidget(patch_updater, lotro_manager, this);
-    settings_widget = new SettingsWidget(patch_updater, lotro_manager, this);
-    help_widget = new HelpWidget(patch_updater, lotro_manager, this);
-    about_widget = new AboutWidget(patch_updater, lotro_manager, this);
+    hideAllContentWidgets();
+    status_widget_->show();
 
-    choose_locale_dialog = new ChooseVersionDialog(patch_updater, lotro_manager, this);
-    choose_locale_dialog->resize(size());
-    choose_locale_dialog->hide();
-    connect(choose_locale_dialog, &ChooseVersionDialog::cancelled, this, &MainWindow::hideChooseVersionDialog);
+    qDebug() << __FUNCTION__ << "Initialising additional frames...";
 
-    dialog_window = new DialogWindow(this);
-    dialog_window->resize(size());
+    choose_locale_dialog_ = new ChooseVersionDialog(this);
+    choose_locale_dialog_->resize(size());
+    choose_locale_dialog_->hide();
+    connect(choose_locale_dialog_, &ChooseVersionDialog::cancelled, choose_locale_dialog_, &ChooseVersionDialog::hide);
 
-    ui->content_layout->addWidget(status_widget);
-    ui->content_layout->addWidget(settings_widget);
-    ui->content_layout->addWidget(help_widget);
-    ui->content_layout->addWidget(about_widget);
+    dialog_window_ = new DialogWindow(this);
+    dialog_window_->resize(size());
 
-    hideAllContentWidgets();
-    status_widget->show();
-
-    qDebug() << "Making background";
+    qDebug() << __FUNCTION__ << "Making background";
 
     qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
-    current_bg_id = qrand() % MAX_PIXMAP_ID + 1;
-    QPixmap background(":/backgrounds/bg" + QString::number(current_bg_id) + ".png");
+    current_bg_id_ = qrand() % MAX_PIXMAP_ID + 1;
+    QPixmap background(":/backgrounds/bg" + QString::number(current_bg_id_) + ".png");
     setupWindowBackgroundAndMask(background);
 
     setupMenuHoverWidget();
 
-    qDebug() << "Initialising background updates";
-    background_update_timer.setInterval(30 * 1000);
-    connect(&background_update_timer, &QTimer::timeout, this, &MainWindow::randomChangeBackground);
-    background_update_timer.start();
+    qDebug() << __FUNCTION__ << "Initialising background updates";
+    background_update_timer_.setInterval(30 * 1000);
+    connect(&background_update_timer_, &QTimer::timeout, this, &MainWindow::randomChangeBackground);
+    background_update_timer_.start();
 
-    qDebug() << "Initialising main window connections";
+    qDebug() << __FUNCTION__ << "Initialising main window connections";
     makeConnections();
 
-    qDebug() << "Installing event filters to clickable objects";
+    qDebug() << __FUNCTION__ << "Installing event filters to clickable objects";
     setEventFilterRecursive(this);
 
-    qDebug() << "Finishing main frame initialisation";
+    qDebug() << __FUNCTION__ << "Finished main frame initialisation";
     show();
-    QMetaObject::invokeMethod(patch_updater, "checkForUpdates");
 }
 
 void MainWindow::mousePressEvent(QMouseEvent *event)
 {
     if (event->button() == Qt::LeftButton) {
-        dragPosition = event->globalPos() - frameGeometry().topLeft();
+        drag_position_ = event->globalPos() - frameGeometry().topLeft();
         event->accept();
     }
 }
@@ -115,7 +106,7 @@ void MainWindow::mousePressEvent(QMouseEvent *event)
 void MainWindow::mouseMoveEvent(QMouseEvent *event)
 {
     if (event->buttons() & Qt::LeftButton) {
-        move(event->globalPos() - dragPosition);
+        move(event->globalPos() - drag_position_);
         event->accept();
     }
 }
@@ -138,18 +129,18 @@ void MainWindow::resizeEvent(QResizeEvent * event)
     ui->menu_widget->resize(width * 650 / default_window_width, height * 53 / default_window_height);
 
     ui->content_area->move(0, height * 110 / default_window_height);
-    ui->content_area->resize(width * 1000 / default_window_width, height * 538 / default_window_height);
-    setupWindowBackgroundAndMask(current_bg);
-    choose_locale_dialog->resize(event->size());
+    ui->content_area->resize(width * 1000 / default_window_width, height * 530 / default_window_height);
+    setupWindowBackgroundAndMask(current_bg_);
+    choose_locale_dialog_->resize(event->size());
 
     ui->closeButton->setMinimumSize(width * 20 / default_window_width, height * 20 / default_window_height);
     ui->minimizeButton->setMinimumSize(width * 20 / default_window_width, height * 20 / default_window_height);
 
-    choose_locale_dialog->move({0, 0});
-    choose_locale_dialog->resize(event->size());
+    choose_locale_dialog_->move({0, 0});
+    choose_locale_dialog_->resize(event->size());
 
-    dialog_window->move({0, 0});
-    dialog_window->resize(size());
+    dialog_window_->move({0, 0});
+    dialog_window_->resize(size());
     updateFontSizes();
 }
 
@@ -158,18 +149,17 @@ void MainWindow::randomChangeBackground()
     if (!qApp)
         return;
 
-    qDebug() << "Starting background update";
-
+    qDebug() << __FUNCTION__ << "Starting background update";
 
     int next_pixmap_id = qrand() % MAX_PIXMAP_ID + 1;
-    while (next_pixmap_id == current_bg_id) {
+    while (next_pixmap_id == current_bg_id_) {
         next_pixmap_id = qrand() % MAX_PIXMAP_ID + 1;
     }
-    qDebug() << "Next background id = " << next_pixmap_id;
+    qDebug() << __FUNCTION__ << "Next background id = " << next_pixmap_id;
 
-    QPixmap *cur_bg = new QPixmap(current_bg);
+    QPixmap *cur_bg = new QPixmap(current_bg_);
     QPixmap *new_bg= new QPixmap(":/backgrounds/bg" + QString::number(next_pixmap_id) + ".png");
-    current_bg_id = next_pixmap_id;
+    current_bg_id_ = next_pixmap_id;
 
     QtConcurrent::run([cur_bg, new_bg, this](){
         const int iterations_num = 50;
@@ -189,7 +179,7 @@ void MainWindow::randomChangeBackground()
         }
         delete cur_bg;
         delete new_bg;
-        qDebug() << "Background update finished";
+        qDebug() << __FUNCTION__ << "Background update finished";
     });
 }
 
@@ -198,122 +188,127 @@ MainWindow::~MainWindow()
     delete ui;
 }
 
+void MainWindow::showMessageDialog(QObject* emitter, QString message, QString ok_button_text, QString cancel_button_text)
+{
+    QMessageBox msg_box;
+    msg_box.setText(message);
+    msg_box.setStandardButtons(QMessageBox::NoButton);
+
+    QPushButton* ok_button = nullptr;
+    QPushButton* cancel_button = nullptr;
+
+    if (!ok_button_text.isEmpty()) {
+        ok_button = new QPushButton(&msg_box);
+        ok_button->setText(ok_button_text);
+        msg_box.addButton(ok_button, QMessageBox::AcceptRole);
+    }
+
+    if (!cancel_button_text.isEmpty()) {
+        cancel_button = new QPushButton(&msg_box);
+        cancel_button->setText(cancel_button_text);
+        msg_box.addButton(cancel_button, QMessageBox::RejectRole);
+    }
+
+    int result = msg_box.exec();
+    emit messageDialogFinished(emitter, result);
+}
+
 void MainWindow::on_menuentry_1_clicked()
 {
-    currently_active_entry = ui->menuentry_1;
+    current_active_entry_ = ui->menuentry_1;
     hideAllContentWidgets();
-    status_widget->show();
+    status_widget_->show();
 }
 
 void MainWindow::on_menuentry_2_clicked()
 {
-    currently_active_entry = ui->menuentry_2;
+    current_active_entry_ = ui->menuentry_2;
     hideAllContentWidgets();
-    settings_widget->show();
+    settings_widget_->show();
 }
 
 void MainWindow::on_menuentry_3_clicked()
 {
-    currently_active_entry = ui->menuentry_3;
+    current_active_entry_ = ui->menuentry_3;
     hideAllContentWidgets();
-    help_widget->show();
+    help_widget_->show();
 }
 
 void MainWindow::on_menuentry_4_clicked()
 {
-    currently_active_entry = ui->menuentry_4;
+    current_active_entry_ = ui->menuentry_4;
     hideAllContentWidgets();
-    about_widget->show();
+    about_widget_->show();
 }
 
 void MainWindow::onHoverMenuentry(MenuEntry *hovered_entry)
 {
-    if (currently_hover_entry != hovered_entry) {
-        currently_hover_entry = hovered_entry;
+    if (current_hovered_entry_ != hovered_entry) {
+        current_hovered_entry_ = hovered_entry;
         moveMenuHoverWidget(hovered_entry);
     }
 }
 
-
-void MainWindow::onPatchDownloaderStarted() {
-    qDebug() << "Patch downloader started!";
-}
-
-void MainWindow::onPatchDownloaderFinished() {
-    qDebug() << "Patch downloader finished!";
-
-//    if (lotro_manager->Initialised()) {
-//        QMetaObject::invokeMethod(lotro_manager, "InstallUpdates", Qt::QueuedConnection);
-//    }
-    return;
-}
-
-void MainWindow::onLotroManagerStarted(QString operation, QVector<QVariant> data) {
-    qDebug() << "LotroManager: Started operation " << operation << " with parameters " << data;
-    return;
-}
-
-void MainWindow::onLotroManagerFinished(QString operation, QVector<QVariant> data) {
-    qDebug() << "LotroManager: Finished operation " << operation << " with parameters " << data;
-//    if (operation == "")
-    return;
-}
-
-void MainWindow::onLotroManagerErrorOccured(QString, QVector<QVariant>) {
-
-}
-
-
 void MainWindow::setupWindowBackgroundAndMask(QPixmap background)
 {
-    if (!qApp)
-        return;
-    current_bg = background;
-    QPixmap scaled_bg = current_bg.scaled(width(), height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-    current_mask = scaled_bg.mask();
-    setMask(current_mask);
+    current_bg_ = background;
+    QPixmap scaled_bg = current_bg_.scaled(width(), height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+    current_mask_ = scaled_bg.mask();
+    setMask(current_mask_);
 
     QPalette palette;
     palette.setBrush(QPalette::Window, scaled_bg);
     setPalette(palette);
 }
 
+void MainWindow::onPatchOperationsStarted()
+{
+    show_warning_on_close = true;
+}
+
+void MainWindow::onPatchOperationsFinished()
+{
+    show_warning_on_close = false;
+}
+
 void MainWindow::makeConnections()
 {
+    connect(legacy_patches_, &PatchList::patchOperationsStarted, this, &MainWindow::onPatchOperationsStarted);
+    connect(legacy_patches_, &PatchList::patchOperationsFinished, this, &MainWindow::onPatchOperationsFinished);
 }
 
 void MainWindow::setupMenuHoverWidget()
 {
-    menuHoverWidget = new QWidget(ui->menu_widget);
-    menuHoverWidget->setStyleSheet("background-color: rgba(55, 37, 31, 230);");
-    menuHoverWidget->resize(0, 0);
+    menu_hover_widget_ = new QWidget(ui->menu_widget);
+    menu_hover_widget_->setStyleSheet("background-color: rgba(55, 37, 31, 230);");
+    menu_hover_widget_->resize(0, 0);
 
     connect(ui->menuentry_1, &MenuEntry::entryIsHovered, this, &MainWindow::onHoverMenuentry);
     connect(ui->menuentry_2, &MenuEntry::entryIsHovered, this, &MainWindow::onHoverMenuentry);
     connect(ui->menuentry_3, &MenuEntry::entryIsHovered, this, &MainWindow::onHoverMenuentry);
     connect(ui->menuentry_4, &MenuEntry::entryIsHovered, this, &MainWindow::onHoverMenuentry);
 
-    currently_active_entry = ui->menuentry_1;
-    menu_hover_checker_timer.setInterval(500);
-    connect(&menu_hover_checker_timer, &QTimer::timeout, this, &MainWindow::checkMenuIsHovered);
-    menu_hover_checker_timer.start();
+    current_active_entry_ = ui->menuentry_1;
+    menu_hover_checker_timer_.setInterval(500);
+    connect(&menu_hover_checker_timer_, &QTimer::timeout, this, &MainWindow::checkMenuIsHovered);
+    menu_hover_checker_timer_.start();
 }
 
 void MainWindow::moveMenuHoverWidget(MenuEntry *target)
 {
-    if (menuHoverWidget->size() == QSize(0, 0)) {
-        menuHoverWidget->resize(target->size() + QSize(10, 0));
-        menuHoverWidget->move(target->pos() + QPoint(-5, 0));
+    if (menu_hover_widget_->size() == QSize(0, 0)) {
+        menu_hover_widget_->resize(target->size() + QSize(10, 0));
+        menu_hover_widget_->move(target->pos() + QPoint(-5, 0));
     } else {
-        if (menuHoverWidgetAnimation == nullptr)
-            menuHoverWidgetAnimation = new QPropertyAnimation(menuHoverWidget, "geometry");
+        if (menu_hover_widget_animation_ == nullptr)
+            menu_hover_widget_animation_ = new QPropertyAnimation(menu_hover_widget_, "geometry");
         else
-            menuHoverWidgetAnimation->stop();
+            menu_hover_widget_animation_->stop();
 
-        menuHoverWidgetAnimation->setDuration(200);
-        menuHoverWidgetAnimation->setStartValue(QRect(menuHoverWidget->pos(), menuHoverWidget->size()));
-        menuHoverWidgetAnimation->setEndValue(QRect(target->pos() + QPoint(-5, 0), target->size() + QSize(10, 0)));
-        menuHoverWidgetAnimation->start();
+        menu_hover_widget_animation_->setDuration(200);
+        menu_hover_widget_animation_->setStartValue(QRect(menu_hover_widget_->pos(), menu_hover_widget_->size()));
+        menu_hover_widget_animation_->setEndValue(QRect(target->pos() + QPoint(-5, 0), target->size() + QSize(10, 0)));
+        menu_hover_widget_animation_->start();
     }
 
     ui->menuentry_1->raise();
@@ -328,32 +323,17 @@ void MainWindow::checkMenuIsHovered()
     QWidget *hovered = qApp->widgetAt(pos);
     if (!hovered || hovered->objectName().size() < 4 ||
             (hovered->objectName().left(9) != "menuentry" && hovered->objectName() != "menu_widget")) {
-        moveMenuHoverWidget(currently_active_entry);
-        currently_hover_entry = nullptr;
+        moveMenuHoverWidget(current_active_entry_);
+        current_hovered_entry_ = nullptr;
     }
 }
 
 void MainWindow::hideAllContentWidgets()
 {
-    status_widget->hide();
-    settings_widget->hide();
-    help_widget->hide();
-    about_widget->hide();
-}
-
-void MainWindow::showChooseVersionDialog()
-{
-//    QGraphicsBlurEffect *effect = new QGraphicsBlurEffect();
-//    effect->setBlurRadius(10);
-//    effect->setBlurHints(QGraphicsBlurEffect::QualityHint);
-//    ui->content_area->setGraphicsEffect(effect);
-    choose_locale_dialog->show();
-}
-
-void MainWindow::hideChooseVersionDialog()
-{
-//    ui->content_area->setGraphicsEffect(nullptr);
-    choose_locale_dialog->hide();
+    status_widget_->hide();
+    settings_widget_->hide();
+    help_widget_->hide();
+    about_widget_->hide();
 }
 
 void MainWindow::updateFontSizes()
@@ -366,8 +346,22 @@ void MainWindow::updateFontSizes()
 
 void MainWindow::on_closeButton_clicked()
 {
-    hide();
-    qApp->quit();
+    close();
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+    if (show_warning_on_close) {
+        auto result = QMessageBox::question(this, "Подтвердите действие", "Внимание! В настоящий момент выполняются процессы установки/обновления.\n"
+                                                            "Выход из приложения может привести к ошибкам и повреждению файлов игры.\n\n"
+                                                            "Вы уверены, что хотите прервать работу Наследия?");
+        if (result == QMessageBox::Yes) {
+            qApp->quit();
+        }
+    } else {
+        qApp->quit();
+    }
+    event->ignore();
 }
 
 void MainWindow::on_minimizeButton_clicked()

+ 35 - 33
src/Legacy/widgets/mainwindow.h

@@ -8,6 +8,9 @@
 #include <QBitmap>
 #include <QGraphicsBlurEffect>
 #include <QFont>
+#include <QCloseEvent>
+#include <QMutex>
+#include <QMutexLocker>
 
 #include "statuswidget.h"
 #include "settingswidget.h"
@@ -20,24 +23,28 @@ class MainWindow;
 }
 
 class MenuEntry;
-class LotroDatManager;
-// class NetworkUpdater;
-class PatchDownloader;
 class DialogWindow;
+class PatchList;
 
 class MainWindow : public QMainWindow
 {
     Q_OBJECT
 
 public:
-    explicit MainWindow(QWidget *parent = 0);
+    explicit MainWindow(PatchList *legacy_patches, QWidget *parent = 0);
     ~MainWindow();
+    int getLastDialogResult();
+
+protected:
+    virtual void closeEvent(QCloseEvent *event) override;
 
 public slots:
-    void showChooseVersionDialog();
-    void hideChooseVersionDialog();
+    void showMessageDialog(QObject* emitter, QString message, QString ok_button_text = "Ок", QString cancel_button_text = "Отмена");
     void updateFontSizes();
 
+signals:
+    void messageDialogFinished(QObject* emitter, int result);
+
 protected:
     void mouseMoveEvent(QMouseEvent *event) override;
     void mousePressEvent(QMouseEvent *event) override;
@@ -63,12 +70,9 @@ private slots:
 
     void setupWindowBackgroundAndMask(QPixmap background);
 
-    void onPatchDownloaderStarted();
-    void onPatchDownloaderFinished();
+    void onPatchOperationsStarted();
 
-    void onLotroManagerStarted(QString, QVector<QVariant>);
-    void onLotroManagerFinished(QString, QVector<QVariant>);
-    void onLotroManagerErrorOccured(QString, QVector<QVariant>);
+    void onPatchOperationsFinished();
 
 private:
     void makeConnections();
@@ -84,37 +88,35 @@ private:
     bool buttonsMouseMoveEventFilter(QObject *obj, QEvent *event);
 
     void setEventFilterRecursive(QObject* widget);
-private:    
-    MenuEntry* currently_active_entry;
-    MenuEntry* currently_hover_entry;
 
-    PatchDownloader *patch_updater;
-//    QThread *patch_updater_thread;
+private:
+    bool show_warning_on_close = false;
 
-    LotroDatManager *lotro_manager;
-    QThread *lotro_functions_thread;
-//    QThread *lotro_manager_thread;
+    PatchList *legacy_patches_;
 
     Ui::MainWindow *ui;
 
-    StatusWidget *status_widget;
-    SettingsWidget *settings_widget;
-    HelpWidget *help_widget;
-    AboutWidget *about_widget;
+    MenuEntry* current_active_entry_;
+    MenuEntry* current_hovered_entry_;
+
+    StatusWidget *status_widget_;
+    SettingsWidget *settings_widget_;
+    HelpWidget *help_widget_;
+    AboutWidget *about_widget_;
 
-    ChooseVersionDialog *choose_locale_dialog;
-    DialogWindow *dialog_window;
+    ChooseVersionDialog *choose_locale_dialog_;
+    DialogWindow *dialog_window_;
 
-    QWidget* menuHoverWidget;
-    QPropertyAnimation* menuHoverWidgetAnimation;
-    QTimer menu_hover_checker_timer;
+    QWidget* menu_hover_widget_;
+    QPropertyAnimation* menu_hover_widget_animation_;
+    QTimer menu_hover_checker_timer_;
 
-    QPoint dragPosition;
+    QPoint drag_position_;
 
-    int current_bg_id;
-    QPixmap current_bg;
-    QBitmap current_mask;
-    QTimer background_update_timer;
+    int current_bg_id_;
+    QPixmap current_bg_;
+    QBitmap current_mask_;
+    QTimer background_update_timer_;
 
     const int MAX_PIXMAP_ID = 21;
 };

+ 1 - 1
src/Legacy/widgets/mainwindow.ui

@@ -47,7 +47,7 @@
       <x>0</x>
       <y>110</y>
       <width>1000</width>
-      <height>538</height>
+      <height>530</height>
      </rect>
     </property>
     <property name="sizePolicy">

+ 10 - 34
src/Legacy/widgets/settingswidget.cpp

@@ -1,10 +1,11 @@
-#include "widgets/settingswidget.h"
-#include "widgets/mainwindow.h"
 #include "ui_settingswidget.h"
-#include "models/filesystem.h"
-#include "models/lotrodatmanager.h"
-#include "models/patchdownloader.h"
+
 #include "models/settings.h"
+#include "models/patchlist.h"
+
+#include "widgets/settingswidget.h"
+#include "widgets/mainwindow.h"
+
 #include "constants.h"
 
 #include <QDebug>
@@ -12,9 +13,10 @@
 #include <QFileDialog>
 #include <QMetaObject>
 
-SettingsWidget::SettingsWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent) :
-    QWidget(parent), patch_updater(patch_downloader), lotro_manager(lotro_dat_manager),
-    ui(new Ui::SettingsWidget)
+SettingsWidget::SettingsWidget(PatchList *legacy_patches, QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::SettingsWidget)
+    , legacy_patches_(legacy_patches)
 {
     ui->setupUi(this);
     for (int i = 1; i <= 4; ++i)
@@ -32,11 +34,6 @@ SettingsWidget::SettingsWidget(PatchDownloader* patch_downloader, LotroDatManage
     ui->lotro_base_language_combobox->installEventFilter(combobox_scrolling_disabler);
     ui->interface_scale_combobox->installEventFilter(combobox_scrolling_disabler);
 //    ui->content_scroll_area->verticalScrollBar()->installEventFilter(scroller);
-
-    connect(patch_downloader, &PatchDownloader::checkForUpdatesStarted, this, &SettingsWidget::handleCheckForUpdatesStarted, Qt::QueuedConnection);
-    connect(patch_downloader, &PatchDownloader::checkForUpdatesFinished, this, &SettingsWidget::handleCheckForUpdatesFinished, Qt::QueuedConnection);
-    connect(lotro_manager, &LotroDatManager::processStarted, this, &SettingsWidget::handleLotroManagerStarted);
-    connect(lotro_manager, &LotroDatManager::processFinished, this, &SettingsWidget::handleLotroManagerFinished);
 }
 
 SettingsWidget::~SettingsWidget()
@@ -144,25 +141,6 @@ void SettingsWidget::resizeEvent(QResizeEvent *event)
     ui->apply_button_entry_2->setMinimumHeight(double(55) * coefficient);
 }
 
-void SettingsWidget::handleLotroManagerStarted(QString process_name, QVector<QVariant> data)
-{
-}
-
-void SettingsWidget::handleLotroManagerFinished(QString process_name, QVector<QVariant> data)
-{
-//    if (process_name == "")
-}
-
-void SettingsWidget::handleCheckForUpdatesStarted()
-{
-
-}
-
-void SettingsWidget::handleCheckForUpdatesFinished()
-{
-
-}
-
 void SettingsWidget::on_interface_scale_combobox_currentIndexChanged(const QString &arg1)
 {
     MainWindow* window = qobject_cast<MainWindow*>(qApp->activeWindow());
@@ -196,7 +174,6 @@ void SettingsWidget::on_change_folder_button_clicked()
 
     Settings::setValue("Lotro/game_path", game_folder);
     ui->game_folder_path->setText(game_folder);
-    QMetaObject::invokeMethod(lotro_manager, "InitialiseManager", Qt::QueuedConnection);
 }
 
 void SettingsWidget::on_lotro_base_language_combobox_currentIndexChanged(int index)
@@ -214,5 +191,4 @@ void SettingsWidget::on_lotro_base_language_combobox_currentIndexChanged(int ind
         return;
 
     Settings::setValue("Lotro/original_locale", value);
-    QMetaObject::invokeMethod(lotro_manager, "InitialiseManager", Qt::QueuedConnection);
 }

+ 5 - 15
src/Legacy/widgets/settingswidget.h

@@ -16,8 +16,7 @@ namespace Ui {
 class SettingsWidget;
 }
 
-class PatchDownloader;
-class LotroDatManager;
+class PatchList;
 
 namespace SettingsWidgetPrivate {
 
@@ -102,7 +101,7 @@ class SettingsWidget : public QWidget
 
 public:
 
-    explicit SettingsWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent = 0);
+    explicit SettingsWidget(PatchList *legacy_patches, QWidget *parent = 0);
 
     ~SettingsWidget();
 
@@ -121,15 +120,6 @@ protected:
     void resizeEvent(QResizeEvent *event) override;
 
 private slots:
-
-    void handleLotroManagerStarted(QString, QVector<QVariant>);
-
-    void handleLotroManagerFinished(QString, QVector<QVariant>);
-
-    void handleCheckForUpdatesStarted();
-
-    void handleCheckForUpdatesFinished();
-
     void on_interface_scale_combobox_currentIndexChanged(const QString &arg1);
 
     void on_change_folder_button_clicked();
@@ -137,10 +127,10 @@ private slots:
     void on_lotro_base_language_combobox_currentIndexChanged(int index);
 
 private:
-    PatchDownloader* patch_updater;
-    LotroDatManager* lotro_manager;
-
     Ui::SettingsWidget *ui;
+
+    PatchList *legacy_patches_;
+
     SettingsWidgetPrivate::ComboboxScrollingDisabler* combobox_scrolling_disabler;
     SettingsWidgetPrivate::SettingsTabsScroller* scroller;
 };

+ 199 - 110
src/Legacy/widgets/statuswidget.cpp

@@ -1,8 +1,10 @@
 #include "statuswidget.h"
 #include "ui_statuswidget.h"
+
+#include "models/patchlist.h"
+
 #include "widgets/mainwindow.h"
-#include "models/patchdownloader.h"
-#include "models/lotrodatmanager.h"
+
 #include "constants.h"
 
 #include <QDesktopServices>
@@ -10,18 +12,14 @@
 #include <QDebug>
 #include <QMessageBox>
 
-StatusWidget::StatusWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent) :
-    QWidget(parent), patch_updater(patch_downloader), lotro_manager(lotro_dat_manager),
-    ui(new Ui::StatusWidget)
+StatusWidget::StatusWidget(PatchList *legacy_patches, QWidget *parent)
+    : QWidget(parent)
+    , ui(new Ui::StatusWidget)
+    , legacy_patches_(legacy_patches)
 {
 
     ui->setupUi(this);
-    last_progress_update_time.start();
-
-    connect(patch_updater, &PatchDownloader::checkForUpdatesStarted, this, &StatusWidget::onPatchDownloaderStarted, Qt::QueuedConnection);
-    connect(patch_updater, &PatchDownloader::progressChanged, this, &StatusWidget::onPatchDownloaderProgressChanged, Qt::QueuedConnection);
-    connect(patch_updater, &PatchDownloader::checkForUpdatesFinished, this, &StatusWidget::onPatchDownloaderFinished, Qt::QueuedConnection);
-    connect(patch_updater, &PatchDownloader::changePatchStatus, this, &StatusWidget::updatePatchStatus);
+    last_statusbar_update_time_.start();
 
     connect(ui->weekly_code_widget, &WeeklyCodeWidget::showCompletedTooltip, this, &StatusWidget::setToolTipToWeeklyCodeComplete);
     connect(ui->weekly_code_widget, &WeeklyCodeWidget::showHelpTooltip, this, &StatusWidget::setToolTipToWeeklyCodeHelp);
@@ -65,6 +63,16 @@ StatusWidget::StatusWidget(PatchDownloader* patch_downloader, LotroDatManager* l
 
     active_tooltip = nullptr;
     resetToolTip();
+
+    connect(legacy_patches_, &PatchList::patchOperationsStarted, this, &StatusWidget::onPatchTotalOperationsStarted);
+    connect(legacy_patches_, &PatchList::patchOperationsFinished, this, &StatusWidget::onPatchTotalOperationsFinished);
+    connect(legacy_patches_, &PatchList::progressChanged, this, &StatusWidget::onPatchTotalProgressChanged);
+
+    foreach (Patch* patch, legacy_patches_->getPatchList()) {
+        connect(patch, &Patch::progressChanged, this, &StatusWidget::onPatchProgressChanged);
+        connect(patch, &Patch::operationStarted, this, &StatusWidget::onPatchOperationStarted);
+        connect(patch, &Patch::operationFinished, this, &StatusWidget::onPatchOperationFinished);
+    }
 }
 
 StatusWidget::~StatusWidget()
@@ -74,27 +82,18 @@ StatusWidget::~StatusWidget()
 
 void StatusWidget::updateFontsSizes()
 {
-    QFont pt10_font = QFont(ui->news_label->font());
-    pt10_font.setPixelSize(pixels_in_10_pt);
-    QFont pt9_font = QFont(ui->progress_label->font());
-    pt9_font.setPixelSize(pixels_in_9_pt);
-    QFont pt11_font = QFont(ui->game_button->font());
-    pt11_font.setPixelSize(pixels_in_11_pt);
-    QFont pt8_font = QFont(ui->images_label->font());
-    pt8_font.setPixelSize(pixels_in_8_pt);
-
     ui->progress_label->setFont(crimson_10pt);
     ui->game_button->setFont(trajan_11pt);
     ui->news_label->setFont(trajan_10pt);
 
-    ui->images_label->setFont(crimson_11pt);
-    ui->images_status->setFont(crimson_11pt);
-    ui->sounds_label->setFont(crimson_11pt);
-    ui->sounds_status->setFont(crimson_11pt);
-    ui->texts_label->setFont(crimson_11pt);
-    ui->texts_status->setFont(crimson_11pt);
-    ui->videos_label->setFont(crimson_11pt);
-    ui->videos_status->setFont(crimson_11pt);
+    ui->graphicspatch_label->setFont(crimson_11pt);
+    ui->graphicspatch_status->setFont(crimson_11pt);
+    ui->soundspatch_label->setFont(crimson_11pt);
+    ui->soundspatch_status->setFont(crimson_11pt);
+    ui->textspatch_label->setFont(crimson_11pt);
+    ui->textspatch_status->setFont(crimson_11pt);
+    ui->videospatch_label->setFont(crimson_11pt);
+    ui->videospatch_status->setFont(crimson_11pt);
 
     ui->news_tooltip->setFont(garamond_11pt);
     ui->weekly_code_tooltip_1->setFont(garamond_11pt);
@@ -102,93 +101,41 @@ void StatusWidget::updateFontsSizes()
     ui->server_status_tooltip->setFont(garamond_11pt);
 }
 
-void StatusWidget::resizeEvent(QResizeEvent *event)
+void StatusWidget::resizeEvent(QResizeEvent *)
 {
     double coefficient = window_width / default_window_width;
-    ui->game_button->move(QPoint(820, 460) * coefficient);
-    ui->game_button->resize(QSize(150, 60) * coefficient);
-    ui->progressBar->move(QPoint(320, 480) * coefficient);
-    ui->progressBar->resize(QSize(470, 40) * coefficient);
-    ui->progress_label->move(QPoint(330, 390) * coefficient);
-    ui->progress_label->resize(QSize(380, 90) * coefficient);
-    ui->news_label->move(QPoint(45, 33)* coefficient);
-    ui->news_label->resize(QSize(180, 21) * coefficient);
-    ui->news_scroll_area->move(QPoint(40, 75) * coefficient);
-    ui->news_scroll_area->resize(QSize(240, 440) * coefficient);
-    ui->server_status_widget->move(QPoint(820, 90) * coefficient);
-    ui->server_status_widget->resize(QSize(155, 320) * coefficient);
-    ui->weekly_code_widget->move(QPoint(810, 13) * coefficient);
-    ui->weekly_code_widget->resize(QSize(173, 57) * coefficient);
-    ui->galadriel_widget->move(QPoint(320, 20) * coefficient);
-    ui->galadriel_widget->resize(QSize(531, 461) * coefficient);
-
-    ui->news_tooltip->move(QPoint(38, 13) * coefficient);
-    ui->news_tooltip->resize(QSize(365, 114) * coefficient);
-    ui->weekly_code_tooltip_1->move(QPoint(38, 13) * coefficient);
-    ui->weekly_code_tooltip_1->resize(QSize(365, 114) * coefficient);
-    ui->weekly_code_tooltip_2->move(QPoint(38, 13) * coefficient);
-    ui->weekly_code_tooltip_2->resize(QSize(365, 114) * coefficient);
-    ui->server_status_tooltip->move(QPoint(38, 13) * coefficient);
-    ui->server_status_tooltip->resize(QSize(365, 114) * coefficient);
-
-    ui->patches_status->move(QPoint(38, 13) * coefficient);
-    ui->patches_status->resize(QSize(365, 114) * coefficient);
+//    ui->game_button->move(QPoint(820, 460) * coefficient);
+//    ui->game_button->resize(QSize(150, 60) * coefficient);
+//    ui->progressBar->move(QPoint(320, 480) * coefficient);
+//    ui->progressBar->resize(QSize(470, 40) * coefficient);
+//    ui->progress_label->move(QPoint(330, 390) * coefficient);
+//    ui->progress_label->resize(QSize(380, 90) * coefficient);
+//    ui->news_label->move(QPoint(45, 33)* coefficient);
+//    ui->news_label->resize(QSize(180, 21) * coefficient);
+//    ui->news_scroll_area->move(QPoint(40, 75) * coefficient);
+//    ui->news_scroll_area->resize(QSize(240, 440) * coefficient);
+//    ui->server_status_widget->move(QPoint(820, 90) * coefficient);
+//    ui->server_status_widget->resize(QSize(155, 320) * coefficient);
+//    ui->weekly_code_widget->move(QPoint(810, 13) * coefficient);
+//    ui->weekly_code_widget->resize(QSize(173, 57) * coefficient);
+//    ui->galadriel_widget->move(QPoint(320, 20) * coefficient);
+//    ui->galadriel_widget->resize(QSize(531, 461) * coefficient);
+
+//    ui->news_tooltip->move(QPoint(38, 13) * coefficient);
+//    ui->news_tooltip->resize(QSize(365, 114) * coefficient);
+//    ui->weekly_code_tooltip_1->move(QPoint(38, 13) * coefficient);
+//    ui->weekly_code_tooltip_1->resize(QSize(365, 114) * coefficient);
+//    ui->weekly_code_tooltip_2->move(QPoint(38, 13) * coefficient);
+//    ui->weekly_code_tooltip_2->resize(QSize(365, 114) * coefficient);
+//    ui->server_status_tooltip->move(QPoint(38, 13) * coefficient);
+//    ui->server_status_tooltip->resize(QSize(365, 114) * coefficient);
+
+//    ui->patches_status->move(QPoint(38, 13) * coefficient);
+//    ui->patches_status->resize(QSize(385, 114) * coefficient);
 
     updateFontsSizes();
 }
 
-void StatusWidget::onPatchDownloaderStarted()
-{
-    qDebug() << "Status widget received DownloadStarted signal!";
-    ui->game_button->setDisabled(true);
-    ui->progress_label->setText("Проверка обновлений...");
-    ui->progressBar->setValue(0);
-}
-
-void StatusWidget::onPatchDownloaderFinished()
-{
-    ui->progress_label->setText("Все патчи загружены!");
-    ui->progressBar->setValue(100);
-    ui->game_button->setDisabled(false);
-}
-
-void StatusWidget::onPatchDownloaderProgressChanged(QList<PatchDownloadData> patches_data)
-{
-    if (last_progress_update_time.elapsed() < 200) {
-        return;
-    } else {
-        last_progress_update_time.restart();
-    }
-
-    foreach (PatchDownloadData data, patches_data) {
-        if (data.patch_name == "general") {
-            ui->progress_label->setText("Скачивание переводов " + data.download_speed_formatted + ".\nЗагружено "
-                                       + Downloader::getSizeFormatted(data.bytesDownloaded) + " из " + Downloader::getSizeFormatted(data.bytesTotal)
-                                       + " (" + QString::number(double(data.bytesDownloaded) * 100.0 / double(data.bytesTotal), 'f', 1) + "%) "
-                                       + "\nОставшееся время: " + data.elapsed_time_formatted);
-            ui->progressBar->setValue(data.bytesDownloaded * 100 / data.bytesTotal);
-        } else {
-            updatePatchStatus(data.patch_name, "Скачивание " + QString::number(double(data.bytesDownloaded) * 100.0 / double(data.bytesTotal), 'f', 1) + "% (" + data.download_speed_formatted + ")");
-        }
-    }
-}
-
-void StatusWidget::updatePatchStatus(QString patch_name, QString status)
-{
-    QString status_label_name = patch_name + "s_status";
-    QLabel* status_label = findChild<QLabel*>(status_label_name);
-    if (!status_label)
-        return;
-
-    status_label->setText(status);
-}
-
-void StatusWidget::on_game_button_clicked()
-{
-    MainWindow* window = qobject_cast<MainWindow*>(parentWidget()->parentWidget()->parentWidget());
-    window->showChooseVersionDialog();
-}
-
 void StatusWidget::setToolTipToWeeklyCodeHelp()
 {
     fadeBetweenToolTips(ui->weekly_code_tooltip_1);
@@ -231,6 +178,148 @@ void StatusWidget::fadeBetweenToolTips(QWidget* next_tooltip)
     if (next_active_tooltip_anim->state() == QAbstractAnimation::Stopped)
         next_active_tooltip_anim->start();
 
-
     active_tooltip = next_tooltip;
 }
+
+void StatusWidget::onPatchTotalOperationsStarted()
+{
+    all_patch_operations_finished_ = false;
+    ui->game_button->setEnabled(false);
+}
+
+void StatusWidget::onPatchTotalOperationsFinished()
+{
+    all_patch_operations_finished_ = true;
+    ui->game_button->setEnabled(true);
+
+    for (Patch* patch : legacy_patches_->getPatchList()) {
+        QString label_name = patch->getPatchName().toLower() + "_status";
+        QLabel *label = findChild<QLabel*>(label_name);
+        if (!label) {
+            return;
+        }
+
+        QString install_date;
+        if (!(install_date = Settings::getValue("UpdateDates/" + patch->getPatchName()).toString()).isEmpty()) {
+            label->setText("Патч версии от " + install_date);
+        } else {
+            label->setText("Все операции с патчем завершены.");
+        }
+    }
+
+    ui->progress_label->setText("");
+}
+
+void StatusWidget::onPatchOperationStarted(Patch::Operation operation, Patch *patch)
+{
+    QString label_name = patch->getPatchName().toLower() + "_status";
+    QLabel *label = findChild<QLabel*>(label_name);
+    patch_operations[patch] = operation;
+    if (!label) {
+        return;
+    }
+
+    switch (operation) {
+    case Patch::E_CHECKFORUPDATES:
+        label->setText("Проверка наличия обновлений...");
+        break;
+    case Patch::E_DOWNLOAD:
+        label->setText("Подготовка к загрузке данных...");
+        break;
+    case Patch::E_INSTALL:
+        label->setText("Ожидание начала установки...");
+        break;
+    case Patch::E_ACTIVATE:
+        label->setText("Ожидание начала активации...");
+    default:
+        break;
+    }
+}
+
+void StatusWidget::onPatchOperationFinished(Patch::Operation operation, Patch *patch)
+{
+    if (all_patch_operations_finished_) {
+        return;
+    }
+
+    QString label_name = patch->getPatchName().toLower() + "_status";
+    QLabel *label = findChild<QLabel*>(label_name);
+
+    if (!label) {
+        return;
+    }
+
+    switch (operation) {
+    case Patch::E_CHECKFORUPDATES:
+        label->setText("Проверка обновлений завершена");
+        break;
+    case Patch::E_DOWNLOAD:
+        label->setText("Загрузка данных завершена");
+        break;
+    case Patch::E_INSTALL:
+        label->setText("Установка патча завершена");
+        break;
+    case Patch::E_ACTIVATE:
+        label->setText("Активация патча завершена");
+    default:
+        break;
+    }
+}
+
+void StatusWidget::onPatchTotalProgressChanged(Patch::OperationProgress operation_progress)
+{
+    updateStatusBar(operation_progress);
+}
+
+void StatusWidget::onPatchProgressChanged(Patch::OperationProgress progress, Patch *patch)
+{
+    QString label_name = patch->getPatchName().toLower() + "_status";
+    QLabel *label = findChild<QLabel*>(label_name);
+    if (!label) {
+        return;
+    }
+
+    switch (patch_operations[patch]) {
+    case Patch::E_CHECKFORUPDATES:
+        label->setText("Проверка наличия обновлений...");
+        break;
+    case Patch::E_DOWNLOAD:
+        label->setText("Загрузка... " + QString::number(progress.getDownloadPercent(), 'f', 1)
+                       + "% (" + Downloader::getSizeFormatted(progress.download_finished_bytes)
+                       + "/" + Downloader::getSizeFormatted(progress.download_total_bytes) + ")");
+        break;
+    case Patch::E_INSTALL:
+        label->setText("Установка... " + QString::number(progress.getInstallPercent(), 'f', 2)
+                       + "% (" + QString::number(progress.install_finished_parts) + "/" + QString::number(progress.install_total_parts) + ")");
+        break;
+    case Patch::E_ACTIVATE:
+        label->setText("Активация... " + QString::number(progress.getInstallPercent(), 'f', 2)
+                       + "% (" + QString::number(progress.install_finished_parts) + "/" + QString::number(progress.install_total_parts) + ")");
+    default:
+        break;
+    }
+}
+
+void StatusWidget::updateStatusBar(Patch::OperationProgress progress)
+{
+    if (last_statusbar_update_time_.elapsed() > 500) {
+        QString text = "Выполнение операций...";
+        if (progress.download_total_bytes != 0) {
+            text += "\nЗагрузка данных: " + QString::number(progress.getDownloadPercent(), 'f', 1) + "% ("
+                    + Downloader::getSizeFormatted(progress.download_finished_bytes) + "/"
+                    + Downloader::getSizeFormatted(progress.download_total_bytes) + ", "
+                    + Downloader::getSpeedFormatted(progress.download_speed) + ")\n"
+                    + "До конца загрузки: " + Downloader::getElapsedTimeFormatted(progress.download_elapsed_time);
+        }
+
+        if (progress.install_total_parts != 0) {
+            text += "\nПрименение патчей: " + QString::number(progress.getInstallPercent()) + "% "
+                    + "(часть " + QString::number(progress.install_finished_parts + 1) + " из " + QString::number(progress.install_total_parts);
+        }
+
+        ui->progress_label->setText(text);
+        last_statusbar_update_time_.restart();
+    }
+}
+
+

+ 19 - 16
src/Legacy/widgets/statuswidget.h

@@ -3,24 +3,23 @@
 
 #include <QWidget>
 #include <QSettings>
-#include <QPropertyAnimation>
+#include <QTime>
 #include <QMap>
+#include <QPropertyAnimation>
 #include <QGraphicsOpacityEffect>
 
-#include "models/patchdownloader.h"
+#include "models/patchlist.h"
 
 namespace Ui {
 class StatusWidget;
 }
 
-class LotroDatManager;
-
 class StatusWidget : public QWidget
 {
     Q_OBJECT
 
 public:
-    explicit StatusWidget(PatchDownloader* patch_downloader, LotroDatManager* lotro_dat_manager, QWidget *parent = 0);
+    explicit StatusWidget(PatchList *legacy_patches, QWidget *parent = 0);
     ~StatusWidget();
 
 public slots:
@@ -30,14 +29,6 @@ protected:
     void resizeEvent(QResizeEvent *event) override;
 
 private slots:
-    void onPatchDownloaderStarted();
-    void onPatchDownloaderFinished();
-    void onPatchDownloaderProgressChanged(QList<PatchDownloadData>);
-
-    void updatePatchStatus(QString patch_name, QString status);
-
-    void on_game_button_clicked();
-
     void setToolTipToWeeklyCodeHelp();
     void setToolTipToWeeklyCodeComplete();
     void setToolTipToNewsHelp();
@@ -46,19 +37,31 @@ private slots:
 
     void fadeBetweenToolTips(QWidget* next_tooltip);
 
+    void onPatchTotalOperationsStarted();
+    void onPatchTotalOperationsFinished();
+
+    void onPatchOperationStarted(Patch::Operation operation, Patch* patch);
+    void onPatchOperationFinished(Patch::Operation operation, Patch* patch);
 
+    void onPatchTotalProgressChanged(Patch::OperationProgress operation_progress);
+    void onPatchProgressChanged(Patch::OperationProgress progress, Patch* patch);
+
+private:
+    void updateStatusBar(Patch::OperationProgress progress);
 
 private:
     Ui::StatusWidget *ui;
 
-    PatchDownloader* patch_updater;
-    LotroDatManager* lotro_manager;
+    PatchList *legacy_patches_;
+
+    bool all_patch_operations_finished_ = false;
 
     QMap<QString, QGraphicsOpacityEffect*> tooltip_effects;
     QMap<QString, QPropertyAnimation*> tooltip_animations;
+    QMap<Patch*, Patch::Operation> patch_operations;
     QWidget* active_tooltip;
 
-    QTime last_progress_update_time;
+    QTime last_statusbar_update_time_;
 };
 
 #endif // STATUSWIDGET_H

+ 91 - 146
src/Legacy/widgets/statuswidget.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>1000</width>
-    <height>538</height>
+    <height>530</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -50,7 +50,7 @@ color: white;
      <x>45</x>
      <y>75</y>
      <width>240</width>
-     <height>440</height>
+     <height>453</height>
     </rect>
    </property>
    <property name="styleSheet">
@@ -112,7 +112,7 @@ QScrollBar:vertical {
       <x>0</x>
       <y>0</y>
       <width>240</width>
-      <height>440</height>
+      <height>453</height>
      </rect>
     </property>
    </widget>
@@ -121,7 +121,7 @@ QScrollBar:vertical {
    <property name="geometry">
     <rect>
      <x>820</x>
-     <y>90</y>
+     <y>120</y>
      <width>155</width>
      <height>320</height>
     </rect>
@@ -130,10 +130,10 @@ QScrollBar:vertical {
   <widget class="WeeklyCodeWidget" name="weekly_code_widget" native="true">
    <property name="geometry">
     <rect>
-     <x>810</x>
-     <y>13</y>
-     <width>173</width>
-     <height>57</height>
+     <x>780</x>
+     <y>30</y>
+     <width>201</width>
+     <height>61</height>
     </rect>
    </property>
    <property name="autoFillBackground">
@@ -146,10 +146,10 @@ QScrollBar:vertical {
   <widget class="QWidget" name="galadriel_widget" native="true">
    <property name="geometry">
     <rect>
-     <x>320</x>
-     <y>20</y>
-     <width>531</width>
-     <height>461</height>
+     <x>310</x>
+     <y>36</y>
+     <width>541</width>
+     <height>491</height>
     </rect>
    </property>
    <property name="styleSheet">
@@ -160,9 +160,9 @@ border-image: url(:/characters/galadriel_with_text.png);
    <widget class="QLabel" name="weekly_code_tooltip_1">
     <property name="geometry">
      <rect>
-      <x>38</x>
-      <y>13</y>
-      <width>365</width>
+      <x>29</x>
+      <y>17</y>
+      <width>396</width>
       <height>114</height>
      </rect>
     </property>
@@ -189,9 +189,9 @@ border-image: url(:/characters/galadriel_with_text.png);
    <widget class="QLabel" name="news_tooltip">
     <property name="geometry">
      <rect>
-      <x>38</x>
-      <y>13</y>
-      <width>365</width>
+      <x>29</x>
+      <y>17</y>
+      <width>396</width>
       <height>114</height>
      </rect>
     </property>
@@ -218,9 +218,9 @@ border-image: url(:/characters/galadriel_with_text.png);
    <widget class="QLabel" name="weekly_code_tooltip_2">
     <property name="geometry">
      <rect>
-      <x>38</x>
-      <y>13</y>
-      <width>365</width>
+      <x>29</x>
+      <y>17</y>
+      <width>396</width>
       <height>114</height>
      </rect>
     </property>
@@ -247,9 +247,9 @@ border-image: url(:/characters/galadriel_with_text.png);
    <widget class="QWidget" name="patches_status" native="true">
     <property name="geometry">
      <rect>
-      <x>38</x>
-      <y>13</y>
-      <width>365</width>
+      <x>29</x>
+      <y>17</y>
+      <width>396</width>
       <height>114</height>
      </rect>
     </property>
@@ -272,21 +272,8 @@ border-image: url(:/characters/galadriel_with_text.png);
      <property name="verticalSpacing">
       <number>5</number>
      </property>
-     <item row="0" column="1" rowspan="4">
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>40</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-     <item row="0" column="0">
-      <widget class="QLabel" name="texts_label">
+     <item row="0" column="1">
+      <widget class="QLabel" name="textspatch_status">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -300,12 +287,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>Тексты:</string>
+        <string>поиск и проверка обновлений...</string>
        </property>
       </widget>
      </item>
-     <item row="0" column="2">
-      <widget class="QLabel" name="texts_status">
+     <item row="1" column="0">
+      <widget class="QLabel" name="graphicspatch_label">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -319,12 +306,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>получение информации...</string>
+        <string>Картинки:</string>
        </property>
       </widget>
      </item>
-     <item row="1" column="0">
-      <widget class="QLabel" name="images_label">
+     <item row="0" column="0">
+      <widget class="QLabel" name="textspatch_label">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -338,12 +325,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>Картинки:</string>
+        <string>Тексты:</string>
        </property>
       </widget>
      </item>
-     <item row="1" column="2">
-      <widget class="QLabel" name="images_status">
+     <item row="2" column="1">
+      <widget class="QLabel" name="soundspatch_status">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -361,8 +348,8 @@ border-image: url(:/characters/galadriel_with_text.png);
        </property>
       </widget>
      </item>
-     <item row="2" column="2">
-      <widget class="QLabel" name="sounds_status">
+     <item row="2" column="0">
+      <widget class="QLabel" name="soundspatch_label">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -376,12 +363,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>получение информации...</string>
+        <string>Озвучка:</string>
        </property>
       </widget>
      </item>
-     <item row="2" column="0">
-      <widget class="QLabel" name="sounds_label">
+     <item row="1" column="1">
+      <widget class="QLabel" name="graphicspatch_status">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -395,12 +382,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>Озвучка:</string>
+        <string>получение информации...</string>
        </property>
       </widget>
      </item>
-     <item row="3" column="0">
-      <widget class="QLabel" name="videos_label">
+     <item row="3" column="1">
+      <widget class="QLabel" name="videospatch_status">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -414,12 +401,12 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>Ролики:</string>
+        <string>получение информации...</string>
        </property>
       </widget>
      </item>
-     <item row="3" column="2">
-      <widget class="QLabel" name="videos_status">
+     <item row="3" column="0">
+      <widget class="QLabel" name="videospatch_label">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
          <horstretch>0</horstretch>
@@ -433,18 +420,18 @@ border-image: url(:/characters/galadriel_with_text.png);
         </font>
        </property>
        <property name="text">
-        <string>получение информации...</string>
+        <string>Ролики:</string>
        </property>
       </widget>
      </item>
-     <item row="0" column="3" rowspan="4">
+     <item row="0" column="2" rowspan="4">
       <spacer name="horizontalSpacer_2">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
-         <width>40</width>
+         <width>0</width>
          <height>20</height>
         </size>
        </property>
@@ -455,9 +442,9 @@ border-image: url(:/characters/galadriel_with_text.png);
    <widget class="QLabel" name="server_status_tooltip">
     <property name="geometry">
      <rect>
-      <x>38</x>
-      <y>13</y>
-      <width>365</width>
+      <x>29</x>
+      <y>17</y>
+      <width>396</width>
       <height>114</height>
      </rect>
     </property>
@@ -481,6 +468,45 @@ border-image: url(:/characters/galadriel_with_text.png);
      <bool>true</bool>
     </property>
    </widget>
+   <widget class="QLabel" name="progress_label">
+    <property name="geometry">
+     <rect>
+      <x>0</x>
+      <y>400</y>
+      <width>381</width>
+      <height>90</height>
+     </rect>
+    </property>
+    <property name="sizePolicy">
+     <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+      <horstretch>0</horstretch>
+      <verstretch>0</verstretch>
+     </sizepolicy>
+    </property>
+    <property name="font">
+     <font>
+      <family>Trajan Pro 3</family>
+      <pointsize>9</pointsize>
+      <weight>50</weight>
+      <bold>false</bold>
+     </font>
+    </property>
+    <property name="text">
+     <string/>
+    </property>
+    <property name="alignment">
+     <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+    </property>
+    <property name="wordWrap">
+     <bool>false</bool>
+    </property>
+   </widget>
+   <zorder>weekly_code_tooltip_1</zorder>
+   <zorder>news_tooltip</zorder>
+   <zorder>weekly_code_tooltip_2</zorder>
+   <zorder>server_status_tooltip</zorder>
+   <zorder>patches_status</zorder>
+   <zorder>progress_label</zorder>
   </widget>
   <widget class="QPushButton" name="game_button">
    <property name="geometry">
@@ -547,91 +573,10 @@ QPushButton#game_button:disabled {
     <bool>true</bool>
    </property>
   </widget>
-  <widget class="QLabel" name="progress_label">
-   <property name="geometry">
-    <rect>
-     <x>330</x>
-     <y>390</y>
-     <width>380</width>
-     <height>90</height>
-    </rect>
-   </property>
-   <property name="sizePolicy">
-    <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-     <horstretch>0</horstretch>
-     <verstretch>0</verstretch>
-    </sizepolicy>
-   </property>
-   <property name="font">
-    <font>
-     <family>Trajan Pro 3</family>
-     <pointsize>9</pointsize>
-     <weight>50</weight>
-     <bold>false</bold>
-    </font>
-   </property>
-   <property name="text">
-    <string>Все операции выполнены</string>
-   </property>
-   <property name="alignment">
-    <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
-   </property>
-   <property name="wordWrap">
-    <bool>true</bool>
-   </property>
-  </widget>
-  <widget class="QProgressBar" name="progressBar">
-   <property name="geometry">
-    <rect>
-     <x>320</x>
-     <y>480</y>
-     <width>470</width>
-     <height>40</height>
-    </rect>
-   </property>
-   <property name="minimumSize">
-    <size>
-     <width>0</width>
-     <height>40</height>
-    </size>
-   </property>
-   <property name="maximumSize">
-    <size>
-     <width>16777215</width>
-     <height>40</height>
-    </size>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">QProgressBar {
-    border: 0px solid grey;
-	border-image: url(:/per.png);
-}
-
-QProgressBar::chunk {
-background-image: url(:/letters.png);
-margin-left:8px;
-margin-right:8px;
-margin-top:7px;
-margin-bottom:7px;
-}
-</string>
-   </property>
-   <property name="maximum">
-    <number>100</number>
-   </property>
-   <property name="value">
-    <number>0</number>
-   </property>
-   <property name="textVisible">
-    <bool>false</bool>
-   </property>
-  </widget>
   <zorder>galadriel_widget</zorder>
   <zorder>news_label</zorder>
   <zorder>news_scroll_area</zorder>
   <zorder>game_button</zorder>
-  <zorder>progress_label</zorder>
-  <zorder>progressBar</zorder>
   <zorder>server_status_widget</zorder>
   <zorder>weekly_code_widget</zorder>
  </widget>