diff --git a/README.md b/README.md index 1b184e8..678bc6e 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,6 @@ Make sure the SD card is properly recognized and shows up as a disk. * The `System Data` group contains important data that is necessary for your imported games to run. You should definitely import the contents there, if they do not exist yet. 1. After you've finished your selection, click `OK`. You should now see a progress dialog; wait a while until your contents are imported. * The time will depend on how big your contents are, as well as your CPU processing power and (mainly) disk I/O speeds. - * The progress is only updated every time a content has been *fully* imported. If you see it stall for a long time, it just means that a very large content is being processed. Just be more patient and do not worry. You can also check the disk usage in `Task Manager`. ### What to do next @@ -66,7 +65,6 @@ If you have any game cartidges, and would like to dump them as well, visit [this * UI improvements * Better error messages * Beautiful icons - * Better progress indicator * Bug fixes * Clear all the `TODO`s in the code * Wireless transfer (probably FTP?) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d21a5eb..bb82d78 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(common STATIC string_util.cpp string_util.h swap.h + thread.h ) target_link_libraries(common PUBLIC fmt) diff --git a/src/common/thread.h b/src/common/thread.h new file mode 100644 index 0000000..4855341 --- /dev/null +++ b/src/common/thread.h @@ -0,0 +1,95 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +namespace Common { + +class Event { +public: + void Set() { + std::lock_guard lk{mutex}; + if (!is_set) { + is_set = true; + condvar.notify_one(); + } + } + + void Wait() { + std::unique_lock lk{mutex}; + condvar.wait(lk, [&] { return is_set; }); + is_set = false; + } + + template + bool WaitFor(const std::chrono::duration& time) { + std::unique_lock lk(mutex); + if (!condvar.wait_for(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + + template + bool WaitUntil(const std::chrono::time_point& time) { + std::unique_lock lk{mutex}; + if (!condvar.wait_until(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + + void Reset() { + std::unique_lock lk{mutex}; + // no other action required, since wait loops on the predicate and any lingering signal will + // get cleared on the first iteration + is_set = false; + } + +private: + bool is_set = false; + std::condition_variable condvar; + std::mutex mutex; +}; + +class Barrier { +public: + explicit Barrier(std::size_t count_) : count(count_) {} + + /// Blocks until all "count" threads have called Sync() + void Sync() { + std::unique_lock lk{mutex}; + const std::size_t current_generation = generation; + + if (++waiting == count) { + generation++; + waiting = 0; + condvar.notify_all(); + } else { + condvar.wait(lk, + [this, current_generation] { return current_generation != generation; }); + } + } + + std::size_t Generation() const { + std::unique_lock lk(mutex); + return generation; + } + +private: + std::condition_variable condvar; + mutable std::mutex mutex; + std::size_t count; + std::size_t waiting = 0; + std::size_t generation = 0; // Incremented once each time the barrier is used +}; + +} // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 37463f4..b8620eb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,6 +11,8 @@ add_library(core STATIC key/arithmetic128.h key/key.cpp key/key.h + quick_decryptor.cpp + quick_decryptor.h ) target_link_libraries(core PRIVATE common cryptopp) diff --git a/src/core/decryptor.cpp b/src/core/decryptor.cpp index 45bb953..6d38838 100644 --- a/src/core/decryptor.cpp +++ b/src/core/decryptor.cpp @@ -17,7 +17,9 @@ namespace Core { -SDMCDecryptor::SDMCDecryptor(const std::string& root_folder_) : root_folder(root_folder_) { +SDMCDecryptor::SDMCDecryptor(const std::string& root_folder_) + : root_folder(root_folder_), quick_decryptor(root_folder) { + ASSERT_MSG(Key::IsNormalKeyAvailable(Key::SDKey), "SD Key must be available in order to decrypt"); @@ -47,28 +49,13 @@ std::array GetFileCTR(const std::string& path) { } } // namespace -bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, - const std::string& destination) const { - auto ctr = GetFileCTR(source); - auto key = Key::GetNormalKey(Key::SDKey); - CryptoPP::CTR_Mode::Decryption aes; - aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); +bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, const std::string& destination, + const QuickDecryptor::ProgressCallback& callback) { + return quick_decryptor.DecryptAndWriteFile(source, destination, callback); +} - if (!FileUtil::CreateFullPath(destination)) { - return false; - } - - std::string absolute_source = root_folder + source; - try { - CryptoPP::FileSource(absolute_source.c_str(), true, - new CryptoPP::StreamTransformationFilter( - aes, new CryptoPP::FileSink(destination.c_str(), true)), - true); - } catch (CryptoPP::Exception& e) { - LOG_ERROR(Core, "Error decrypting and writing file: {}", e.what()); - return false; - } - return true; +void SDMCDecryptor::Abort() { + quick_decryptor.Abort(); } std::vector SDMCDecryptor::DecryptFile(const std::string& source) const { diff --git a/src/core/decryptor.h b/src/core/decryptor.h index c4dc91f..788639d 100644 --- a/src/core/decryptor.h +++ b/src/core/decryptor.h @@ -7,6 +7,7 @@ #include #include #include "common/common_types.h" +#include "core/quick_decryptor.h" namespace Core { @@ -22,11 +23,17 @@ public: /** * Decrypts a file from the SD card and writes it into another file. + * This function blocks, but can be aborted with the Abort() function (would return false) + * * @param source Path to the file relative to the root folder, starting with "/". * @param destination Path to the destination file. * @return true on success, false otherwise */ - bool DecryptAndWriteFile(const std::string& source, const std::string& destination) const; + bool DecryptAndWriteFile(const std::string& source, const std::string& destination, + const QuickDecryptor::ProgressCallback& callback = [](std::size_t, + std::size_t) {}); + + void Abort(); /** * Decrypts a file and reads it into a vector. @@ -36,6 +43,7 @@ public: private: std::string root_folder; + QuickDecryptor quick_decryptor; }; } // namespace Core diff --git a/src/core/importer.cpp b/src/core/importer.cpp index da5e849..ebaf66f 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -53,29 +53,34 @@ bool SDMCImporter::IsGood() const { return is_good; } -bool SDMCImporter::ImportContent(const ContentSpecifier& specifier) { +void SDMCImporter::Abort() { + decryptor->Abort(); +} + +bool SDMCImporter::ImportContent(const ContentSpecifier& specifier, + const ProgressCallback& callback) { switch (specifier.type) { case ContentType::Application: case ContentType::Update: case ContentType::DLC: - return ImportTitle(specifier.id); + return ImportTitle(specifier.id, callback); case ContentType::Savegame: - return ImportSavegame(specifier.id); + return ImportSavegame(specifier.id, callback); case ContentType::Extdata: - return ImportExtdata(specifier.id); + return ImportExtdata(specifier.id, callback); case ContentType::Sysdata: - return ImportSysdata(specifier.id); + return ImportSysdata(specifier.id, callback); default: UNREACHABLE(); } } -bool SDMCImporter::ImportTitle(u64 id) { +bool SDMCImporter::ImportTitle(u64 id, const ProgressCallback& callback) { const auto path = fmt::format("title/{:08x}/{:08x}/content/", (id >> 32), (id & 0xFFFFFFFF)); return FileUtil::ForeachDirectoryEntry( nullptr, config.sdmc_path + path, - [this, &path](u64* /*num_entries_out*/, const std::string& directory, - const std::string& virtual_name) { + [this, &path, callback](u64* /*num_entries_out*/, const std::string& directory, + const std::string& virtual_name) { if (FileUtil::IsDirectory(directory + virtual_name)) { return true; } @@ -84,11 +89,12 @@ bool SDMCImporter::ImportTitle(u64 id) { FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo " "3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + - path + virtual_name); + path + virtual_name, + callback); }); } -bool SDMCImporter::ImportSavegame(u64 id) { +bool SDMCImporter::ImportSavegame(u64 id, [[maybe_unused]] const ProgressCallback& callback) { const auto path = fmt::format("title/{:08x}/{:08x}/data/", (id >> 32), (id & 0xFFFFFFFF)); DataContainer container(decryptor->DecryptFile(fmt::format("/{}00000001.sav", path))); @@ -106,7 +112,7 @@ bool SDMCImporter::ImportSavegame(u64 id) { "Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + path); } -bool SDMCImporter::ImportExtdata(u64 id) { +bool SDMCImporter::ImportExtdata(u64 id, [[maybe_unused]] const ProgressCallback& callback) { const auto path = fmt::format("extdata/{:08x}/{:08x}/", (id >> 32), (id & 0xFFFFFFFF)); SDExtdata extdata("/" + path, *decryptor); if (!extdata.IsGood()) { @@ -118,7 +124,7 @@ bool SDMCImporter::ImportExtdata(u64 id) { "Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + path); } -bool SDMCImporter::ImportSysdata(u64 id) { +bool SDMCImporter::ImportSysdata(u64 id, [[maybe_unused]] const ProgressCallback& callback) { switch (id) { case 0: { // boot9.bin const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9; @@ -330,6 +336,74 @@ void SDMCImporter::ListSysdata(std::vector& out) const { } while (0); } +void SDMCImporter::DeleteContent(const ContentSpecifier& specifier) { + switch (specifier.type) { + case ContentType::Application: + case ContentType::Update: + case ContentType::DLC: + return DeleteTitle(specifier.id); + case ContentType::Savegame: + return DeleteSavegame(specifier.id); + case ContentType::Extdata: + return DeleteExtdata(specifier.id); + case ContentType::Sysdata: + return DeleteSysdata(specifier.id); + default: + UNREACHABLE(); + } +} + +void SDMCImporter::DeleteTitle(u64 id) const { + FileUtil::DeleteDirRecursively(fmt::format( + "{}Nintendo " + "3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/{:08x}/{:08x}/" + "content/", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), (id >> 32), (id & 0xFFFFFFFF))); +} + +void SDMCImporter::DeleteSavegame(u64 id) const { + FileUtil::DeleteDirRecursively(fmt::format( + "{}Nintendo " + "3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/{:08x}/{:08x}/" + "data/", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), (id >> 32), (id & 0xFFFFFFFF))); +} + +void SDMCImporter::DeleteExtdata(u64 id) const { + FileUtil::DeleteDirRecursively(fmt::format( + "{}Nintendo " + "3DS/00000000000000000000000000000000/00000000000000000000000000000000/extdata/{:08x}/" + "{:08x}/", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), (id >> 32), (id & 0xFFFFFFFF))); +} + +void SDMCImporter::DeleteSysdata(u64 id) const { + switch (id) { + case 0: { // boot9.bin + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9); + } + case 1: { // safe mode firm + const bool is_new_3ds = FileUtil::Exists(config.safe_mode_firm_path + "new/"); + const auto target_path = + fmt::format("{}00000000000000000000000000000000/title/00040138/{}/", + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), + (is_new_3ds ? "20000003" : "00000003")); + FileUtil::DeleteDirRecursively(target_path); + } + case 2: { // seed db + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB); + } + case 3: { // secret sector + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SECRET_SECTOR); + } + case 4: { // aes_keys.txt + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS); + } + default: + UNREACHABLE_MSG("Unexpected sysdata id {}", id); + } +} + std::vector LoadPresetConfig(std::string mount_point) { if (mount_point.back() != '/' && mount_point.back() != '\\') { mount_point += '/'; diff --git a/src/core/importer.h b/src/core/importer.h index f48c2b5..d83ea92 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -61,6 +61,9 @@ struct Config { class SDMCImporter { public: + /// (current_size, total_size) + using ProgressCallback = std::function; + /** * Initializes the importer. * @param root_folder Path to the "Nintendo 3DS//" folder. @@ -70,10 +73,23 @@ public: ~SDMCImporter(); /** - * Dumps a specific content by its specifier. + * Aborts a specific content by its specifier. + * Blocks, but can be aborted on another thread if needed. * @return true on success, false otherwise */ - bool ImportContent(const ContentSpecifier& specifier); + bool ImportContent(const ContentSpecifier& specifier, + const ProgressCallback& callback = [](std::size_t, std::size_t) {}); + + /** + * Deletes/Cleans up a content. Used for deleting contents that have + * not been fully imported. + */ + void DeleteContent(const ContentSpecifier& specifier); + + /** + * Aborts current importing. + */ + void Abort(); /** * Gets a list of dumpable content specifiers. @@ -87,13 +103,17 @@ public: private: bool Init(); - bool ImportTitle(u64 id); - bool ImportSavegame(u64 id); - bool ImportExtdata(u64 id); - bool ImportSysdata(u64 id); + bool ImportTitle(u64 id, const ProgressCallback& callback); + bool ImportSavegame(u64 id, const ProgressCallback& callback); + bool ImportExtdata(u64 id, const ProgressCallback& callback); + bool ImportSysdata(u64 id, const ProgressCallback& callback); void ListTitle(std::vector& out) const; void ListExtdata(std::vector& out) const; void ListSysdata(std::vector& out) const; + void DeleteTitle(u64 id) const; + void DeleteSavegame(u64 id) const; + void DeleteExtdata(u64 id) const; + void DeleteSysdata(u64 id) const; bool is_good{}; Config config; diff --git a/src/core/quick_decryptor.cpp b/src/core/quick_decryptor.cpp new file mode 100644 index 0000000..ba4ebbe --- /dev/null +++ b/src/core/quick_decryptor.cpp @@ -0,0 +1,204 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/key/key.h" +#include "core/quick_decryptor.h" + +namespace Core { + +QuickDecryptor::QuickDecryptor(const std::string& root_folder_) : root_folder(root_folder_) { + ASSERT_MSG(Key::IsNormalKeyAvailable(Key::SDKey), + "SD Key must be available in order to decrypt"); + + if (root_folder.back() == '/' || root_folder.back() == '\\') { + // Remove '/' or '\' character at the end as we will add them back when combining path + root_folder.erase(root_folder.size() - 1); + } +} + +QuickDecryptor::~QuickDecryptor() = default; + +namespace { +std::array GetFileCTR(const std::string& path) { + auto path_utf16 = Common::UTF8ToUTF16(path); + std::vector path_data(path_utf16.size() * 2 + 2, 0); // Add the '\0' character + std::memcpy(path_data.data(), path_utf16.data(), path_utf16.size() * 2); + + CryptoPP::SHA256 sha; + std::array hash; + sha.CalculateDigest(hash.data(), path_data.data(), path_data.size()); + + std::array ctr; + for (int i = 0; i < 16; i++) { + ctr[i] = hash[i] ^ hash[16 + i]; + } + return ctr; +} +} // namespace + +bool QuickDecryptor::DecryptAndWriteFile(const std::string& source_, + const std::string& destination_, + const ProgressCallback& callback_) { + if (is_running) { + LOG_ERROR(Core, "Decryptor is running"); + return false; + } + + if (!FileUtil::CreateFullPath(destination_)) { + LOG_ERROR(Core, "Could not create path {}", destination_); + return false; + } + + for (auto& event : data_read_event) { + event.Reset(); + } + for (auto& event : data_decrypted_event) { + event.Reset(); + } + for (auto& event : data_written_event) { + event.Reset(); + } + completion_event.Reset(); + + source = source_; + destination = destination_; + callback = callback_; + + total_size = FileUtil::GetSize(root_folder + source); + if (total_size == 0) { + LOG_ERROR(Core, "Could not open file {}", root_folder + source); + return false; + } + + is_good = is_running = true; + + read_thread = std::make_unique(&QuickDecryptor::DataReadLoop, this); + decrypt_thread = std::make_unique(&QuickDecryptor::DataDecryptLoop, this); + write_thread = std::make_unique(&QuickDecryptor::DataWriteLoop, this); + + completion_event.Wait(); + is_running = false; + + read_thread->join(); + decrypt_thread->join(); + write_thread->join(); + + bool ret = is_good; + is_good = true; + return ret; +} + +void QuickDecryptor::DataReadLoop() { + std::size_t current_buffer = 0; + bool is_first_run = true; + + FileUtil::IOFile file(root_folder + source, "rb"); + if (!file) { + is_good = false; + completion_event.Set(); + return; + } + + std::size_t file_size = total_size; + + while (is_running && file_size > 0) { + if (is_first_run) { + if (current_buffer == buffers.size() - 1) { + is_first_run = false; + } + } else { + data_written_event[current_buffer].Wait(); + } + + const auto bytes_to_read = std::min(BufferSize, file_size); + if (file.ReadBytes(buffers[current_buffer].data(), bytes_to_read) != bytes_to_read) { + is_good = false; + completion_event.Set(); + return; + } + file_size -= bytes_to_read; + + data_read_event[current_buffer].Set(); + current_buffer = (current_buffer + 1) % buffers.size(); + } +} + +void QuickDecryptor::DataDecryptLoop() { + auto ctr = GetFileCTR(source); + auto key = Key::GetNormalKey(Key::SDKey); + CryptoPP::CTR_Mode::Decryption aes; + aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); + + std::size_t current_buffer = 0; + std::size_t file_size = total_size; + + while (is_running && file_size > 0) { + data_read_event[current_buffer].Wait(); + + const auto bytes_to_process = std::min(BufferSize, file_size); + aes.ProcessData(buffers[current_buffer].data(), buffers[current_buffer].data(), + bytes_to_process); + + file_size -= bytes_to_process; + + data_decrypted_event[current_buffer].Set(); + current_buffer = (current_buffer + 1) % buffers.size(); + } +} + +void QuickDecryptor::DataWriteLoop() { + std::size_t current_buffer = 0; + + FileUtil::IOFile file(destination, "wb"); + if (!file) { + is_good = false; + completion_event.Set(); + return; + } + + std::size_t file_size = total_size; + std::size_t iteration = 0; + /// The number of iterations each progress report covers. 32 * 16K = 512K + constexpr std::size_t ProgressReportFreq = 32; + + while (is_running && file_size > 0) { + iteration++; + if (iteration % ProgressReportFreq == 0) { + callback(iteration * BufferSize, total_size); + } + + data_decrypted_event[current_buffer].Wait(); + + const auto bytes_to_write = std::min(BufferSize, file_size); + if (file.WriteBytes(buffers[current_buffer].data(), bytes_to_write) != bytes_to_write) { + is_good = false; + completion_event.Set(); + return; + } + file_size -= bytes_to_write; + + data_written_event[current_buffer].Set(); + current_buffer = (current_buffer + 1) % buffers.size(); + } + + completion_event.Set(); +} + +void QuickDecryptor::Abort() { + if (is_running.exchange(false)) { + is_good = false; + completion_event.Set(); + } +} + +} // namespace Core diff --git a/src/core/quick_decryptor.h b/src/core/quick_decryptor.h new file mode 100644 index 0000000..a645c19 --- /dev/null +++ b/src/core/quick_decryptor.h @@ -0,0 +1,62 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/thread.h" + +namespace Core { + +class QuickDecryptor { +public: + /// (current_size, total_size) + using ProgressCallback = std::function; + + /** + * Initializes the decryptor. + * @param root_folder Path to the "Nintendo 3DS//" folder. + */ + explicit QuickDecryptor(const std::string& root_folder); + + ~QuickDecryptor(); + + bool DecryptAndWriteFile(const std::string& source, const std::string& destination, + const ProgressCallback& callback = [](std::size_t, std::size_t) {}); + + void DataReadLoop(); + void DataDecryptLoop(); + void DataWriteLoop(); + + void Abort(); + +private: + static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB + + std::string root_folder; + std::string source; + std::string destination; + std::size_t total_size{}; + + std::array, 3> buffers; + std::array data_read_event; + std::array data_decrypted_event; + std::array data_written_event; + + std::unique_ptr read_thread; + std::unique_ptr decrypt_thread; + std::unique_ptr write_thread; + + ProgressCallback callback; + + Common::Event completion_event; + bool is_good{true}; + std::atomic_bool is_running{false}; +}; + +} // namespace Core diff --git a/src/frontend/import_dialog.cpp b/src/frontend/import_dialog.cpp index 00f156d..175daa1 100644 --- a/src/frontend/import_dialog.cpp +++ b/src/frontend/import_dialog.cpp @@ -266,18 +266,32 @@ void ImportDialog::StartImporting() { auto* dialog = new QProgressDialog(tr("Initializing..."), tr("Cancel"), 0, static_cast(total_size / multiplier), this); dialog->setWindowModality(Qt::WindowModal); + dialog->setMinimumDuration(0); dialog->setValue(0); auto* job = new ImportJob(this, importer, std::move(to_import)); - connect(job, &ImportJob::ProgressUpdated, this, - [dialog, multiplier, total_count](u64 size_imported, u64 count, - Core::ContentSpecifier next_content) { + connect(job, &ImportJob::NextContent, this, + [this, dialog, multiplier, total_count](u64 size_imported, u64 count, + Core::ContentSpecifier next_content) { dialog->setValue(static_cast(size_imported / multiplier)); - dialog->setLabelText(tr("Importing %1 (%2/%3)...") - .arg(GetContentName(next_content)) + dialog->setLabelText(tr("(%1/%2) Importing %3...") .arg(count) - .arg(total_count)); + .arg(total_count) + .arg(GetContentName(next_content))); + current_content = next_content; + current_count = count; + }); + connect(job, &ImportJob::ProgressUpdated, this, + [this, dialog, multiplier, total_count](u64 total_size_imported, + u64 current_size_imported) { + dialog->setValue(static_cast(total_size_imported / multiplier)); + dialog->setLabelText(tr("(%1/%2) Importing %3 (%4/%5)...") + .arg(current_count) + .arg(total_count) + .arg(GetContentName(current_content)) + .arg(ReadableByteSize(current_size_imported)) + .arg(ReadableByteSize(current_content.maximum_size))); }); connect(job, &ImportJob::ErrorOccured, this, [this, dialog](Core::ContentSpecifier current_content) { diff --git a/src/frontend/import_dialog.h b/src/frontend/import_dialog.h index 9282736..ed47435 100644 --- a/src/frontend/import_dialog.h +++ b/src/frontend/import_dialog.h @@ -41,4 +41,8 @@ private: // HACK: To tell whether the checkbox state change is a programmatic trigger // TODO: Is there a more elegant way of doing the same? bool program_trigger = false; + + // TODO: Why this won't work as locals? + Core::ContentSpecifier current_content = {}; + u64 current_count = 0; }; diff --git a/src/frontend/import_job.cpp b/src/frontend/import_job.cpp index 05cee32..836daa6 100644 --- a/src/frontend/import_job.cpp +++ b/src/frontend/import_job.cpp @@ -15,10 +15,17 @@ ImportJob::~ImportJob() = default; void ImportJob::run() { u64 size_imported = 0, count = 0; for (const auto& content : contents) { - emit ProgressUpdated(size_imported, count + 1, content); - if (!importer.ImportContent(content)) { - emit ErrorOccured(content); - return; + emit NextContent(size_imported, count + 1, content); + const auto callback = [this, size_imported](std::size_t current_size, + std::size_t /*total_size*/) { + emit ProgressUpdated(size_imported + current_size, current_size); + }; + if (!importer.ImportContent(content, callback)) { + importer.DeleteContent(content); + if (!cancelled) { + emit ErrorOccured(content); + return; + } } count++; size_imported += content.maximum_size; @@ -32,4 +39,5 @@ void ImportJob::run() { void ImportJob::Cancel() { cancelled.store(true); + importer.Abort(); } diff --git a/src/frontend/import_job.h b/src/frontend/import_job.h index aac1c03..17b7423 100644 --- a/src/frontend/import_job.h +++ b/src/frontend/import_job.h @@ -20,7 +20,17 @@ public: void Cancel(); signals: - void ProgressUpdated(u64 size_imported, u64 count, Core::ContentSpecifier next_content); + /** + * Called when progress is updated on the current content. + * @param total_size_imported Total imported size taking all previous contents into + * consideration. + * @param current_size_imported Imported size of the current content. + */ + void ProgressUpdated(u64 total_size_imported, u64 current_size_imported); + + /// Dumping of a content has been finished, go on to the next. Called at start as well. + void NextContent(u64 size_imported, u64 count, Core::ContentSpecifier next_content); + void Completed(); void ErrorOccured(Core::ContentSpecifier current_content);