Browse Source

Merge branch 'f/LotroDat-6-DatStatus-module-enchancement' of LotRO_Legacy/LotroDat into dev

Ivan Arkhipov 5 years ago
parent
commit
8b230179d4

+ 74 - 11
include/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_;
     };
 }
 

+ 12 - 13
src/DatFile.cpp

@@ -29,21 +29,20 @@ namespace LOTRO_DAT {
         el::Loggers::addFlag(el::LoggingFlag::LogDetailedCrashReason);
         el::Loggers::addFlag(el::LoggingFlag::ImmediateFlush);
         el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
-        el::Loggers::addFlag(el::LoggingFlag::LogDetailedCrashReason);
         
         defaultConf.setToDefault();
         defaultConf.setGlobally(el::ConfigurationType::Format, "%datetime %level : %msg (function: %func)");
 
-
         defaultConf.setGlobally(el::ConfigurationType::ToFile, "true");
         defaultConf.setGlobally(el::ConfigurationType::Filename, "dat_library.log");
         defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "false");
-        defaultConf.setGlobally(el::ConfigurationType::PerformanceTracking, "true");
-        defaultConf.setGlobally(el::ConfigurationType::MaxLogFileSize, "‭20971520‬"); // 20MB
+        defaultConf.setGlobally(el::ConfigurationType::MaxLogFileSize, "15728640"); // 15MB
 
         #ifndef NDEBUG
         defaultConf.set(el::Level::Debug, el::ConfigurationType::Enabled, "true");
         defaultConf.set(el::Level::Debug, el::ConfigurationType::Filename, "dat_library_debug.log");
+        #elif NDEBUG
+        defaultConf.set(el::Level::Debug, el::ConfigurationType::Enabled, "false");
         #endif
         
         el::Loggers::reconfigureAllLoggers(defaultConf);
@@ -107,27 +106,27 @@ namespace LOTRO_DAT {
         auto operation = io_->Init(filename);
         if (operation.result != SUCCESS) {
             Deinitialize();
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to internal IO error");
         }
 
         operation = fileSystem_->Init();
         if (operation.result != SUCCESS) {
             Deinitialize();
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to filesystem parsing error");
         }
 
         operation = localeManager_->Init();
         if (operation.result != SUCCESS) {
             Deinitialize();
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to locale manager initialisation error");
         }
 
         initialized_ = true;
 
-        status_->ClearAll();
+        status_->SetDefaultStatus();
         return DatOperationResult<>();
     }
 
@@ -151,7 +150,7 @@ namespace LOTRO_DAT {
         localeManager_->PrintInformaion(out);
 
         fclose(out);
-        status_->ClearAll();
+        status_->SetDefaultStatus();
         return DatOperationResult<>(SUCCESS);
     }
 
@@ -160,24 +159,24 @@ namespace LOTRO_DAT {
 
         auto operation = localeManager_->DeInit();
         if (operation.result != SUCCESS) {
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
         }
 
         operation = fileSystem_->DeInit();
         if (operation.result != SUCCESS) {
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
         }
 
         operation = io_->DeInit();
         if (operation.result != SUCCESS) {
-            status_->ClearAll();
+            status_->SetDefaultStatus();
             return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
         }
 
         initialized_ = false;
-        status_->ClearAll();
+        status_->SetDefaultStatus();
         return DatOperationResult<>();
     }
 

+ 10 - 8
src/DatSubsystems/DatBackupManager.cpp

@@ -23,7 +23,7 @@ namespace LOTRO_DAT {
 
     bool DatBackupManager::CheckIfBackupAvailable(const std::string &backup_datname) {
         DatFile file;
-        bool result = file.Initialise(backup_datname, 0).result == SUCCESS && file.Initialized();
+        bool result = (file.Initialise(backup_datname, 0).result == SUCCESS && file.Initialized());
         file.Deinitialize();
         return result;
     }
@@ -43,10 +43,9 @@ namespace LOTRO_DAT {
             return DatOperationResult<>(ERROR, "CREATEBACKUP: cannot open file " + backup_datname);
 
         dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_CREATING);
-        dat->GetStatusModule().SetPercentage(0);
         dat->GetStatusModule().SetDebugMessage("Creating backup " + backup_datname + " from " + dat->GetIO().GetFilename().value);
         auto operation = CopyDatFile(*dat, file);
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
 
         fclose(file);
 
@@ -78,10 +77,10 @@ namespace LOTRO_DAT {
             return DatOperationResult<>(ERROR, "RESTOREFROMBACKUP: cannot open file " + backup_datname);
 
         dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_RESTORING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
         dat->GetStatusModule().SetDebugMessage("Restoring file " + dat->GetIO().GetFilename().value +  " from backup " + backup_datname);
         auto operation = CopyDatFile(backup_file, file);
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
 
         fclose(file);
         if (operation.result == ERROR)
@@ -101,13 +100,14 @@ namespace LOTRO_DAT {
 
     DatOperationResult<> DatBackupManager::RemoveBackup(const std::string &backup_datname) {
         dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_REMOVING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
+        dat->GetStatusModule().SetTotalParts(1);
         dat->GetStatusModule().SetDebugMessage("Removing backup file " + backup_datname);
 
         if (remove(backup_datname.c_str()) != 0)
             LOG(INFO) << "Removed backup file " << backup_datname;
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<>(SUCCESS);
     }
 
@@ -125,10 +125,12 @@ namespace LOTRO_DAT {
                 source.GetIO().GetActualDatSize().value / COPY_BLOCK_SIZE + (source.GetIO().GetActualDatSize().value % COPY_BLOCK_SIZE != 0);
         long long newfile_size = 0;
         unsigned elapsed_size = unsigned(source.GetIO().GetActualDatSize().value - newfile_size);
+        
+        dat->GetStatusModule().SetTotalParts(source.GetIO().GetActualDatSize().value);
 
         BinaryData data(COPY_BLOCK_SIZE);
         for (unsigned i = 0; i < parts_count; i++) {
-            dat->GetStatusModule().SetPercentage(i * 100 / (unsigned)parts_count);
+            dat->GetStatusModule().SetFinishedParts(i * COPY_BLOCK_SIZE);
 
             auto operation = source.GetIO().ReadData(data, std::min(COPY_BLOCK_SIZE, elapsed_size), newfile_size);
             if (operation.result == ERROR)

+ 21 - 16
src/DatSubsystems/DatExporter.cpp

@@ -29,13 +29,17 @@ namespace LOTRO_DAT {
         int iterated_files = 0;
 
         dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
+        dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());
+
         dat->GetStatusModule().SetDebugMessage("Starting extracting files of type " + std::to_string(type) + ", this may take long time, please be patient!");
         LOG(INFO) << "Extracting files by type " + std::to_string(type) + "to database...";
 
         auto operation = dat->GetFileSystem().PerformOperationOnAllFiles([&iterated_files, &success_exported, this, type, &output_directory_path](std::shared_ptr<SubFile>& file) -> void {
             iterated_files++;
-            dat->GetStatusModule().SetPercentage(iterated_files * 100u / dat->GetFileSystem().GetInitialisedFilesNumber());
+            dat->GetStatusModule().SetFinishedParts(iterated_files);
+            dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());
+
             if ((file->FileType() & type) == NO_TYPE)
                 return;
 
@@ -57,7 +61,7 @@ namespace LOTRO_DAT {
         });
 
         LOG(INFO) << "Extracting files: successfully exported " << success_exported << " files";
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<int>(success_exported, SUCCESS);
     }
 
@@ -72,19 +76,20 @@ namespace LOTRO_DAT {
 
     DatOperationResult<> DatExporter::ExtractFileById(long long file_id, std::string output_filename) {
         dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
+        dat->GetStatusModule().SetTotalParts(1);
         dat->GetStatusModule().SetDebugMessage("Extracting file " + std::to_string(file_id) + " to file " + output_filename);
 
         auto operation_GetFile = dat->GetFileSystem().GetFile(file_id);
         if (operation_GetFile.result == ERROR) {
-            dat->GetStatusModule().ClearAll();
+            dat->GetStatusModule().SetDefaultStatus();
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: File not found! id = " + std::to_string(file_id));
         }
         std::shared_ptr<SubFile> file = operation_GetFile.value;
 
         auto operation_GetFileData = dat->GetFileSystem().GetFileData(*file, 8);
         if (operation_GetFileData.result == ERROR) {
-            dat->GetStatusModule().ClearAll();
+            dat->GetStatusModule().SetDefaultStatus();
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: Can't get data for id = " + std::to_string(file_id));
         }
 
@@ -97,7 +102,7 @@ namespace LOTRO_DAT {
         else
             result = export_data.binary_data.WriteToFile(output_filename + file->Extension());
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         if (!result)
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: Cannot write to file" + output_filename + file->Extension());
 
@@ -121,7 +126,8 @@ namespace LOTRO_DAT {
             return DatOperationResult<int>(0, ERROR, "EXTRACTALLBYTYPETODB: database is nullptr");
 
         dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
+        dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());
         dat->GetStatusModule().SetDebugMessage("Initialising extraction of files by type " + std::to_string(type) + "to database... This may take long time, please be patient!\"");
         LOG(INFO) << "Extracting files by type " + std::to_string(type) + "to database...";
 
@@ -130,7 +136,7 @@ namespace LOTRO_DAT {
 
         auto operation = dat->GetFileSystem().PerformOperationOnAllFiles([&success_exported, &iterated_files, this, type, db](std::shared_ptr<SubFile>& file) {
             iterated_files++;
-            dat->GetStatusModule().SetPercentage(iterated_files * 100u / dat->GetFileSystem().GetInitialisedFilesNumber());
+            dat->GetStatusModule().SetFinishedParts(iterated_files);
 
             if ((file->FileType() & type) == NO_TYPE)
                 return;
@@ -146,12 +152,10 @@ namespace LOTRO_DAT {
         });
 
         LOG(INFO) << "Extracting files: successfully exported " << success_exported << " files";
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<int>(success_exported, SUCCESS);
     }
 
-
-
     /*!
      * \author Ivan Arkhipov
      * \date 21.10.2018
@@ -165,19 +169,20 @@ namespace LOTRO_DAT {
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: database is nullptr");
 
         dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
-        dat->GetStatusModule().SetPercentage(50);
+        dat->GetStatusModule().SetFinishedParts(0);
+        dat->GetStatusModule().SetTotalParts(1);
         LOG(INFO) << "Extracting file with id = " << file_id << "to database...";
 
         auto operation_GetFile = dat->GetFileSystem().GetFile(file_id);
         if (operation_GetFile.result == ERROR) {
-            dat->GetStatusModule().ClearAll();
+            dat->GetStatusModule().SetDefaultStatus();
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: File not found! id = " + std::to_string(file_id));
         }
         std::shared_ptr<SubFile> file = operation_GetFile.value;
 
         auto operation_GetFileData = dat->GetFileSystem().GetFileData(*file, 8);
         if (operation_GetFileData.result == ERROR) {
-            dat->GetStatusModule().ClearAll();
+            dat->GetStatusModule().SetDefaultStatus();
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: Can't get data for id = " + std::to_string(file_id));
         }
 
@@ -185,7 +190,7 @@ namespace LOTRO_DAT {
 
         bool result = db->PushFile(export_data);
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         if (!result)
             return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: Cannot export file" + std::to_string(file_id));
 

+ 21 - 10
src/DatSubsystems/DatLocaleManager.cpp

@@ -188,7 +188,7 @@ namespace LOTRO_DAT {
             LOG(INFO) << "Locale is already " << locale << ", nothing to do.";
 
             if (dat->GetStatusModule().GetStatus() == DatStatus::E_COMMITING)
-                dat->GetStatusModule().ClearAll();
+                dat->GetStatusModule().SetDefaultStatus();
 
             return DatOperationResult<>(SUCCESS);
         }
@@ -197,10 +197,11 @@ namespace LOTRO_DAT {
 
         size_t files_total = dict.size();
         size_t files_proceeded = 0;
+        dat->GetStatusModule().SetTotalParts(files_total);
 
         for (const auto &file : dict) {
             ++files_proceeded;
-            dat->GetStatusModule().SetPercentage(files_proceeded * 100 / files_total);
+            dat->GetStatusModule().SetFinishedParts(files_proceeded);
 
             if (CategoryIsInactive(file.second.category) && locale == PATCHED)
                 continue;
@@ -224,7 +225,7 @@ namespace LOTRO_DAT {
         current_locale_ = locale;
 
         if (dat->GetStatusModule().GetStatus() == DatStatus::E_COMMITING)
-            dat->GetStatusModule().ClearAll();
+            dat->GetStatusModule().SetDefaultStatus();
 
         return DatOperationResult<>(SUCCESS);
     }
@@ -460,6 +461,10 @@ namespace LOTRO_DAT {
             return DatOperationResult<>(SUCCESS);
         }
 
+        dat->GetStatusModule().SetStatus(DatStatus::E_COMMITING);
+        dat->GetStatusModule().SetTotalParts(orig_dict_.size() + patch_dict_.size());
+        dat->GetStatusModule().SetFinishedParts(0);
+
         BinaryData binary_data = BinaryData(4 + 4 + 4 + 4 + 14 + 15 + 4
                                             + 4 + (32 + 4) * orig_dict_.size()
                                             + 4 + (32 + 4) * patch_dict_.size()
@@ -477,11 +482,14 @@ namespace LOTRO_DAT {
         binary_data.Append(BinaryData::FromNumber<4>(orig_dict_.size()), current_size);
         current_size += 4;
 
+        unsigned long long processed_files = 0;
+
         for (const auto &file : orig_dict_) {
             binary_data.Append(file.second.MakeHeaderData(), current_size);
             current_size += 32;
             binary_data.Append(BinaryData::FromNumber<4>(file.second.category), current_size);
             current_size += 4;
+            dat->GetStatusModule().SetFinishedParts(++processed_files);
         }
 
         binary_data.Append(BinaryData::FromNumber<4>(patch_dict_.size()), current_size);
@@ -492,6 +500,7 @@ namespace LOTRO_DAT {
             current_size += 32;
             binary_data.Append(BinaryData::FromNumber<4>(file.second.category), current_size);
             current_size += 4;
+            dat->GetStatusModule().SetFinishedParts(++processed_files);
         }
 
         binary_data.Append(BinaryData::FromNumber<4>(inactive_categories.size()), current_size);
@@ -555,6 +564,7 @@ namespace LOTRO_DAT {
             }
         }
 
+        dat->GetStatusModule().SetDefaultStatus();
         LOG(INFO) << "Locales committed successfully";
         return DatOperationResult<>(SUCCESS);
     }
@@ -569,44 +579,45 @@ namespace LOTRO_DAT {
     DatOperationResult<> DatLocaleManager::EnableCategory(long long category) {
         inactive_categories.erase(category);
         dat->GetStatusModule().SetStatus(DatStatus::E_COMMITING);
-        dat->GetStatusModule().SetPercentage(0);
-
+        
         size_t files_count = patch_dict_.size();
         size_t files_processed = 0;
+        dat->GetStatusModule().SetTotalParts(files_count);
 
         for (const auto& entry : patch_dict_) {
             SubFile file = entry.second;
             ++files_processed;
-            dat->GetStatusModule().SetPercentage(files_processed * 100 / files_count);
+            dat->GetStatusModule().SetFinishedParts(files_processed);
 
             if (file.category == category) {
                 dat->GetFileSystem().UpdateFileInfo(file);
             }
         }
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<>(SUCCESS);
     }
 
     DatOperationResult<> DatLocaleManager::DisableCategory(long long category) {
         inactive_categories.insert(category);
         dat->GetStatusModule().SetStatus(DatStatus::E_COMMITING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
 
         size_t files_count = orig_dict_.size();
         size_t files_processed = 0;
+        dat->GetStatusModule().SetTotalParts(files_count);
 
         for (const auto& entry : orig_dict_) {
             SubFile file = entry.second;
             ++files_processed;
-            dat->GetStatusModule().SetPercentage(files_processed * 100 / files_count);
+            dat->GetStatusModule().SetFinishedParts(files_processed);
 
             if (file.category == category) {
                 dat->GetFileSystem().UpdateFileInfo(file);
             }
         }
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<>(SUCCESS);
     }
 }

+ 18 - 12
src/DatSubsystems/DatPatcher.cpp

@@ -24,15 +24,17 @@ namespace LOTRO_DAT {
 
         if (single_file) {
             dat->GetStatusModule().SetStatus(DatStatus::E_PATCHING);
-            dat->GetStatusModule().SetPercentage(0);
+            dat->GetStatusModule().SetFinishedParts(0);
+            dat->GetStatusModule().SetTotalParts(1);
         }
 
         dat->GetStatusModule().SetDebugMessage("Patching file with id " + std::to_string(file_id));
 
         auto getfile_operation = dat->GetFileSystem().GetFile(file_id);
         if (getfile_operation.result == ERROR) {
-            if (single_file)
-                dat->GetStatusModule().ClearAll();
+            if (single_file) {
+                dat->GetStatusModule().SetDefaultStatus();
+            }
 
             return DatOperationResult<>(ERROR,
                                         "PATCHSUBFILEDATA: Unable to find file with id " + std::to_string(file_id));
@@ -57,8 +59,9 @@ namespace LOTRO_DAT {
         auto getdata_operation = dat->GetFileSystem().GetFileData(*file, 0);
 
         if (getdata_operation.result == ERROR) {
-            if (single_file)
-                dat->GetStatusModule().ClearAll();
+            if (single_file) {
+                dat->GetStatusModule().SetDefaultStatus();
+            }
 
             return DatOperationResult<>(ERROR,
                                         "PATCHSUBFILEDATA: can't get file data for id = " + std::to_string(file_id));
@@ -70,12 +73,14 @@ namespace LOTRO_DAT {
 
         auto result = ApplyFilePatch(file, patch_data);
 
-        if (single_file)
-            dat->GetStatusModule().ClearAll();
+        if (single_file) {
+            dat->GetStatusModule().SetDefaultStatus();
+        }
 
-        if (result.result == ERROR)
+        if (result.result == ERROR) {
             return DatOperationResult<>(ERROR,
                                         "PATCHSUBFILEDATA: applyfilepatch failed for id = " + std::to_string(file_id));
+        }
 
         return DatOperationResult<>(SUCCESS);
     }
@@ -94,7 +99,7 @@ namespace LOTRO_DAT {
             return DatOperationResult<int>(0, ERROR, "PATCHALLDATABASE: db is nullptr");
 
         dat->GetStatusModule().SetStatus(DatStatus::E_PATCHING);
-        dat->GetStatusModule().SetPercentage(0);
+        dat->GetStatusModule().SetFinishedParts(0);
         dat->GetStatusModule().SetDebugMessage("Patching database...");
 
         SubfileData data;
@@ -102,13 +107,14 @@ namespace LOTRO_DAT {
 
         int successfully_patched = 0;
         unsigned db_rows = db->CountRows();
+        dat->GetStatusModule().SetTotalParts(db_rows);
 
         for (unsigned i = 0; i < db_rows; ++i) {
             auto operation = PatchFile(data, false);
             if (operation.result == SUCCESS)
                 successfully_patched++;
-
-            dat->GetStatusModule().SetPercentage(i * 100 / db_rows);
+            
+            dat->GetStatusModule().SetFinishedParts(i);
 
             data = db->GetNextFile();
         }
@@ -116,7 +122,7 @@ namespace LOTRO_DAT {
         LOG(INFO) << "Database import success: patched " + std::to_string(successfully_patched) + " out of " +
                                                        std::to_string(db_rows) + " files";
 
-        dat->GetStatusModule().ClearAll();
+        dat->GetStatusModule().SetDefaultStatus();
         return DatOperationResult<int>(successfully_patched, SUCCESS);
     }
 

+ 67 - 18
src/DatSubsystems/DatStatus.cpp

@@ -1,42 +1,91 @@
 #include <DatSubsystems/DatStatus.h>
 #include <DatFile.h>
 
+#include <math.h>
+
 namespace LOTRO_DAT {
-    void DatStatus::SetPercentage(unsigned percent) {
-        percentage_ = std::min(100u, percent);
-    }
+    unsigned long long DatStatus::CallbackStorage::free_handler_ = 0;
 
-    unsigned DatStatus::GetPercentage() {
-        return percentage_;
+    bool equalOfftoOneDecimalPoint(double x, double y) {
+        return int(x * 10.0) == int(y * 10.0);
     }
 
-    bool DatStatus::CheckIfNotPatched() {
-        return dat->GetLocaleManager().patch_dict_.empty();
+    DatStatus::DatStatus(DatFile *datFilePtr) : dat(datFilePtr) {
     }
 
-    DatStatus::DatStatus(DatFile *datFilePtr) : dat(datFilePtr), percentage_(0), status_(E_FREE) {
+    void DatStatus::SetStatus(DatStatus::DAT_STATUS status) {
+        bool need_to_invoke_progress_callback_functions = (status_ != status);
+        status_ = status;
+        if (need_to_invoke_progress_callback_functions) {
+            InvokeCallbackFunctions();
+        }
     }
-
-    void DatStatus::ClearAll() {
-        debug_message = "";
-        status_ = E_FREE;
+        
+    void DatStatus::SetFinishedParts(unsigned long long finished_parts) {
+        finished_parts_ = finished_parts;
+        if (!equalOfftoOneDecimalPoint(GetPercentage(), percentage_)) {
+            percentage_ = GetPercentage();
+            InvokeCallbackFunctions();
+        }
     }
 
-    void DatStatus::SetStatus(DatStatus::DAT_STATUS status) {
-        status_ = status;
+    void DatStatus::SetTotalParts(unsigned long long total_parts) {
+        total_parts_ = total_parts;
+    }
 
+    void DatStatus::SetDebugMessage(const std::string &message) {
+        debug_message_ = message;
     }
 
     DatStatus::DAT_STATUS DatStatus::GetStatus() {
         return status_;
     }
 
-    void DatStatus::SetDebugMessage(const std::string &message) {
-        debug_message = message;
+    double DatStatus::GetPercentage() {
+        return total_parts_ != 0 ? double(finished_parts_) * 100.0 / double(total_parts_) : 0;
+    }
+
+    unsigned long long DatStatus::GetFinishedParts() {
+        return finished_parts_;
+    }
 
+    unsigned long long DatStatus::GetTotalParts() {
+        return total_parts_;
     }
 
     std::string DatStatus::GetDebugMessage() {
-        return debug_message;
+        return debug_message_;
+    }
+    
+    void DatStatus::SetDefaultStatus() {
+        debug_message_ = "";
+        total_parts_ = 0;
+        finished_parts_ = 0;
+        status_ = E_FREE;
+        InvokeCallbackFunctions();
+    }
+
+    bool DatStatus::CheckIfNotPatched() {
+        return dat->GetLocaleManager().patch_dict_.empty();
+    }
+
+    unsigned long long DatStatus::AddStatusChangedCallbackFunction(Callback func) {
+        CallbackStorage storage(func);
+        callback_functions_.insert(storage);
+        return storage.GetHandler();
+    }
+
+    void DatStatus::RemoveStatusChangedCallbackFunction(unsigned long long handler) {
+        callback_functions_.erase(CallbackStorage(handler));
+    }
+
+    void DatStatus::EraseAllCallbackFunctions() {
+        callback_functions_.clear();
+    }
+
+    void DatStatus::InvokeCallbackFunctions() {
+        for (const auto& func : callback_functions_) {
+            func(ProgressInfo({GetStatus(), GetPercentage(), GetFinishedParts(), GetTotalParts()})); // evaluating callback functions, transfering all needed information.
+        }
     }
-}
+} // namespace LOTRO_DAT

+ 82 - 18
src/Examples/extractor_example.cpp

@@ -5,7 +5,7 @@
 #include <ctime>
 #include <algorithm>
 
-#ifdef WIN32
+#ifdef _WIN32
 #include <direct.h>
 #define mkdir(dir, mode) _mkdir(dir)
 #endif
@@ -31,7 +31,56 @@ bool exportTexturesToDb = false;
 bool exportUnknownToDb = false;
 // There is no need to change anything else
 
+void DatStatusChangedHandler(DatStatus::ProgressInfo info) {
+    if (info.status == DatStatus::DAT_STATUS::E_FREE) {
+        std::cout << "DatStatus: operation finished" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_CREATING) {
+        std::cout << "DatStatus: creating backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_REMOVING) {
+        std::cout << "DatStatus: removing backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_RESTORING) {
+        std::cout << "DatStatus: restoring backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_COMMITING) {
+        std::cout << "DatStatus: applying locales " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_EXTRACTING) {
+        std::cout << "DatStatus: extracting data " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_GATHERING_INFO) {
+        std::cout << "DatStatus: gathering info " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_INITIALISING) {
+        std::cout << "DatStatus: initialising " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_PATCHING) {
+        std::cout << "DatStatus: applying patch " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+}
+
 int main() {
+    std::cout.precision(1);
+    std::cout << fixed;
     std::cout << "Gi1dor's LotRO .dat extractor ver. " << LOTRO_DAT_VERSION << std::endl;
 
     std::cout << "Hello! I'm a basic shell version of .dat file extractor. I can open .dat file directly, "
@@ -42,13 +91,13 @@ int main() {
     if (!in.fail()) {
         std::string filename;
         getline(in, filename);
-
         std::cout << "Using .dat file from dat_file_path.txt...\n";
         std::cout << "Opening file " << filename << std::endl;
 
         auto operation = file.Initialise(filename, 0);
-        if (operation.result == false)
+        if (operation.result == false) {
             std::cout << "Dat initialisation failed. Error message: " << operation.msg << "\n";
+        }
     }
 
     while (!file.Initialized()) {
@@ -60,12 +109,14 @@ int main() {
         std::cout << "Opening file " << filename << std::endl;
 
         auto operation = file.Initialise(filename, 0);
-        if (operation.result == false)
+        if (operation.result == false) {
             std::cout << "Dat initialisation failed. Error message: " << operation.msg << "\nTo try again enter path to .dat file\n";
+        }
     }
 
     std::cout << "Great! File initialised successfully!\n";
-    std::cout << "Gathering file information...";
+    file.GetStatusModule().AddStatusChangedCallbackFunction(&DatStatusChangedHandler);
+    std::cout << "Gathering file information...\n";
     file.GatherInformation("dat_info.log");
 
     int cmd = 0;
@@ -111,65 +162,78 @@ int main() {
 
             if (exportImagesToDb) {
                 output_db.InitDatabase(output_dir + std::string("Images.db"));
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(JPG, &output_db).value << " .jpg files to Images.db" << std::endl << std::flush;
+                int extracted_jpg_files_num = file.GetExporter().ExtractAllFilesByType(JPG, &output_db).value;
+                std::cout << "Extracted " << extracted_jpg_files_num << " .jpg files to Images.db" << std::endl << std::flush;
                 output_db.CloseDatabase();
             }
 
             if (exportSoundsToDb) {
                 output_db.InitDatabase(output_dir + std::string("Sounds.db"));
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(WAV, &output_db).value << " .wav files to Sounds.db" << std::endl << std::flush;
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(OGG, &output_db).value << " .ogg files to Sounds.db" << std::endl << std::flush;
+                int extracted_wav_files_num = file.GetExporter().ExtractAllFilesByType(WAV, &output_db).value;
+                int extracted_ogg_files_num = file.GetExporter().ExtractAllFilesByType(OGG, &output_db).value;
+                std::cout << "Extracted " << extracted_wav_files_num << " .wav files to Sounds.db" << std::endl << std::flush;
+                std::cout << "Extracted " << extracted_ogg_files_num << " .ogg files to Sounds.db" << std::endl << std::flush;
                 output_db.CloseDatabase();
             }
 
             if (exportTextsToDb) {
                 output_db.InitDatabase(output_dir + std::string("Texts.db"));
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(TEXT, &output_db).value << " text files to Texts.db" << std::endl << std::flush;
+                int extracted_text_files_num = file.GetExporter().ExtractAllFilesByType(TEXT, &output_db).value;
+                std::cout << "Extracted " << extracted_text_files_num << " text files to Texts.db" << std::endl << std::flush;
                 output_db.CloseDatabase();
             }
 
             if (exportFontsToDb) {
                 output_db.InitDatabase(output_dir + std::string("Fonts.db"));
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(FONT, &output_db).value << " font files to Fonts.db" << std::endl << std::flush;
+                int extracted_font_files_num = file.GetExporter().ExtractAllFilesByType(FONT, &output_db).value;
+                std::cout << "Extracted " << extracted_font_files_num << " font files to Fonts.db" << std::endl << std::flush;
                 output_db.CloseDatabase();
             }
 
             if (exportTexturesToDb) {
                 output_db.InitDatabase(output_dir + std::string("Textures.db"));
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(DDS, &output_db).value << " .dds files to Textures.db" << std::endl << std::flush;
+                int extracted_dds_files_num = file.GetExporter().ExtractAllFilesByType(DDS, &output_db).value;
+                std::cout << "Extracted " << extracted_dds_files_num << " .dds files to Textures.db" << std::endl << std::flush;
                 output_db.CloseDatabase();
             }
 
             if (exportImagesToFiles) {
                 mkdir((output_dir + "jpg").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(JPG, output_dir + "jpg\\").value << " .jpg files to directory" << std::endl << std::flush;
+                int extracted_jpg_files_num = file.GetExporter().ExtractAllFilesByType(JPG, output_dir + "jpg\\").value;
+                std::cout << "Extracted " << extracted_jpg_files_num << " .jpg files to directory" << std::endl << std::flush;
             }
 
             if (exportTexturesToFiles) {
                 mkdir((output_dir + "dds").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(DDS, output_dir + "dds\\").value << " .dds files to directory" << std::endl << std::flush;
+                int extracted_dds_files_num = file.GetExporter().ExtractAllFilesByType(DDS, output_dir + "dds\\").value;
+                std::cout << "Extracted " << extracted_dds_files_num << " .dds files to directory" << std::endl << std::flush;
             }
 
             if (exportSoundsToFiles) {
                 mkdir((output_dir + "wav").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(WAV, output_dir + "wav\\").value << " .wav files to directory" << std::endl << std::flush;
+                int extracted_wav_files_num = file.GetExporter().ExtractAllFilesByType(WAV, output_dir + "wav\\").value;
+                std::cout << "Extracted " << extracted_wav_files_num << " .wav files to directory" << std::endl << std::flush;
                 mkdir((output_dir + "ogg").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(OGG, output_dir + "ogg\\").value << " .ogg files to directory" << std::endl << std::flush;
+                int extracted_ogg_files_num = file.GetExporter().ExtractAllFilesByType(OGG, output_dir + "ogg\\").value;
+                std::cout << "Extracted " << extracted_ogg_files_num << " .ogg files to directory" << std::endl << std::flush;
             }
 
             if (exportFontsToFiles) {
                 mkdir((output_dir + "fonts").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(FONT, output_dir + "fonts\\").value << " font files to directory" << std::endl << std::flush;
+                int extracted_font_files_num = file.GetExporter().ExtractAllFilesByType(FONT, output_dir + "fonts\\").value;
+                std::cout << "Extracted " << extracted_font_files_num << " font files to directory" << std::endl << std::flush;
             }
 
             if (exportUnknownToFiles) {
                 mkdir((output_dir + "unknown").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(UNKNOWN, output_dir + "unknown\\").value << " unknown files to directory" << std::endl << std::flush;
+                int extracted_unknown_files_num = file.GetExporter().ExtractAllFilesByType(UNKNOWN, output_dir + "unknown\\").value;
+                std::cout << "Extracted " << extracted_unknown_files_num << " unknown files to directory" << std::endl << std::flush;
             }
 
             if (exportTextsToFiles) {
                 mkdir((output_dir + "texts").c_str(), 744);
-                std::cout << "Extracted " << file.GetExporter().ExtractAllFilesByType(TEXT, output_dir + "texts\\").value << " text files to directory" << std::endl << std::flush;
+                int extracted_text_files_num = file.GetExporter().ExtractAllFilesByType(TEXT, output_dir + "texts\\").value;
+                std::cout << "Extracted " << extracted_text_files_num << " text files to directory" << std::endl << std::flush;
             }
 
             fprintf(stdout, "Spent %f seconds on running unpacker! Thank you for your patience!\n",

+ 54 - 15
src/Examples/patcher_example.cpp

@@ -14,7 +14,56 @@
 using namespace LOTRO_DAT;
 using namespace std;
 
+void DatStatusChangedHandler(DatStatus::ProgressInfo info) {
+    if (info.status == DatStatus::DAT_STATUS::E_FREE) {
+        std::cout << "DatStatus: operation finished" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_CREATING) {
+        std::cout << "DatStatus: creating backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_REMOVING) {
+        std::cout << "DatStatus: removing backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_BACKUP_RESTORING) {
+        std::cout << "DatStatus: restoring backup " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_COMMITING) {
+        std::cout << "DatStatus: applying locales " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_EXTRACTING) {
+        std::cout << "DatStatus: extracting data " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_GATHERING_INFO) {
+        std::cout << "DatStatus: gathering info " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_INITIALISING) {
+        std::cout << "DatStatus: initialising " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+    if (info.status == DatStatus::DAT_STATUS::E_PATCHING) {
+        std::cout << "DatStatus: applying patch " << info.percentage << "% " 
+        << "(" << info.finished_parts << "/" << info.total_parts << ")" << std::endl;
+        return;
+    }
+}
+
 int main() {
+    std::cout.precision(1);
+    std::cout << fixed;
     std::cout << "Gi1dor's LotRO .dat patcher ver. " << LOTRO_DAT_VERSION << std::endl;
     freopen("patcher_errors.log", "w", stderr);
 
@@ -54,6 +103,7 @@ int main() {
     }
 
     std::cout << "Great! File initialised successfully!\n";
+    file.GetStatusModule().AddStatusChangedCallbackFunction(&DatStatusChangedHandler);
 
     while (true) {
         std::cout << "Please, choose, what should I do. I can patch datfile from database to .dat file (enter 1), "
@@ -105,21 +155,10 @@ int main() {
                 std::cout << "There are " << db.CountRows() << " files in database." << std::endl;
 
                 std::cout << "Successfully opened database! Beginning patching...\n";
+                
                 const clock_t begin_time = clock();
-                size_t all = db.CountRows();
-                size_t now = 0;
-
-                SubfileData subfile = db.GetNextFile();
-                while (!subfile.Empty()) {
-                    if (file.GetPatcher().PatchFile(subfile).result == ERROR) {
-                        fprintf(stderr, "Error! Caught exception while patching file! Passing it\n");
-                    }
-
-                    subfile = db.GetNextFile();
-                    ++now;
-                    if (now * 100 / all > (now - 1) * 100 / all)
-                        std::cout << now * 100 / all << "%\n";
-                }
+
+                file.GetPatcher().PatchAllDatabase(&db);
 
                 db.CloseDatabase();
 
@@ -180,7 +219,7 @@ int main() {
         }
 
         if (cmd == 10) {
-            std::cout << "Backup file " << (file.GetBackupManager().CheckIfBackupAvailable("clie_local_En.backup") ? "exists!" : "doesn't exist.") << std::endl;
+            std::cout << "Backup file " << (file.GetBackupManager().CheckIfBackupAvailable("cli_local_En.backup") ? "exists!" : "doesn't exist.") << std::endl;
         }
     }
     //system("pause");