#include "PrinterFileSystem.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Format/bbs_3mf.hpp" #include "libslic3r/Model.hpp" #include "slic3r/GUI/I18N.hpp" #include "../../Utils/NetworkAgent.hpp" #include "../BitmapCache.hpp" #include #include #include #include #include #include #include "nlohmann/json.hpp" #include #ifndef NDEBUG //#define PRINTER_FILE_SYSTEM_TEST #endif std::string last_system_error() { return Slic3r::decode_path(std::error_code( #ifdef _WIN32 GetLastError(), #else errno, #endif std::system_category()).message().c_str()); } wxDEFINE_EVENT(EVT_STATUS_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_MODE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_FILE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_THUMBNAIL, wxCommandEvent); wxDEFINE_EVENT(EVT_DOWNLOAD, wxCommandEvent); wxDEFINE_EVENT(EVT_FILE_CALLBACK, wxCommandEvent); static wxBitmap default_thumbnail; static std::map error_messages = { {PrinterFileSystem::ERROR_PIPE, L("Reconnecting the printer, the operation cannot be completed immediately, please try again later.")}, {PrinterFileSystem::ERROR_RES_BUSY, L("The device cannot handle more conversations. Please retry later.")}, {PrinterFileSystem::FILE_NO_EXIST, L("File does not exist.")}, {PrinterFileSystem::FILE_CHECK_ERR, L("File checksum error. Please retry.")}, {PrinterFileSystem::FILE_TYPE_ERR, L("Not supported on the current printer version.")}, {PrinterFileSystem::STORAGE_UNAVAILABLE, L("Please check if the SD card is inserted into the printer.\nIf it still cannot be read, you can try formatting the SD card.")} }; struct StaticBambuLib : BambuLib { static StaticBambuLib & get(); static int Fake_Bambu_Create(Bambu_Tunnel*, char const*) { return -2; } }; PrinterFileSystem::PrinterFileSystem() : BambuLib(StaticBambuLib::get()) { if (!default_thumbnail.IsOk()) { default_thumbnail = *Slic3r::GUI::BitmapCache().load_svg("printer_file", 0, 0); #ifdef __APPLE__ default_thumbnail = wxBitmap(default_thumbnail.ConvertToImage(), -1, 1); #endif } m_session.owner = this; #ifdef PRINTER_FILE_SYSTEM_TEST auto time = wxDateTime::Now(); wxString path = "D:\\work\\pic\\"; for (int i = 0; i < 10; ++i) { auto name = wxString::Format(L"gcode-%02d.3mf", i + 1); m_file_list.push_back({name.ToUTF8().data(), "", time.GetTicks(), 26937, i < 5 ? FF_DOWNLOAD : 0, default_thumbnail}); std::ifstream ifs((path + name).ToUTF8().data(), std::ios::binary); if (ifs) ParseThumbnail(m_file_list.back(), ifs); time.Add(wxDateSpan::Days(-1)); } m_file_list.swap(m_file_list_cache[{F_MODEL, ""}]); time = wxDateTime::Now(); for (int i = 0; i < 100; ++i) { auto name = wxString::Format(L"img-%03d.jpg", i + 1); wxImage im(path + name); m_file_list.push_back({name.ToUTF8().data(), "", time.GetTicks(), 26937, i < 20 ? FF_DOWNLOAD : 0, i > 3 ? im : default_thumbnail}); time.Add(wxDateSpan::Days(-1)); } m_file_list[0].thumbnail = default_thumbnail; m_file_list.swap(m_file_list_cache[{F_TIMELAPSE, ""}]); #endif } PrinterFileSystem::~PrinterFileSystem() { m_recv_thread.detach(); } void PrinterFileSystem::SetFileType(FileType type, std::string const &storage) { if (m_file_type == type && m_file_storage == storage) return; SelectAll(false); assert(m_file_list_cache[std::make_pair(m_file_type, m_file_storage)].empty()); m_file_list.swap(m_file_list_cache[{m_file_type, m_file_storage}]); std::swap(m_file_type, type); m_file_storage = storage; m_file_list.swap(m_file_list_cache[{m_file_type, m_file_storage}]); m_lock_start = m_lock_end = 0; BuildGroups(); UpdateGroupSelect(); SendChangedEvent(EVT_FILE_CHANGED); if (type == F_INVALID_TYPE) return; if (m_session.tunnel == nullptr) return; m_status = Status::ListSyncing; SendChangedEvent(EVT_STATUS_CHANGED, m_status); ListAllFiles(); } void PrinterFileSystem::SetGroupMode(GroupMode mode) { if (this->m_group_mode == mode) return; this->m_group_mode = mode; m_lock_start = m_lock_end = 0; UpdateGroupSelect(); SendChangedEvent(EVT_MODE_CHANGED); } size_t PrinterFileSystem::EnterSubGroup(size_t index) { if (m_group_mode == G_NONE) return index; index = m_group_mode == G_YEAR ? m_group_year[index] : m_group_month[index]; SetGroupMode((GroupMode)(m_group_mode - 1)); return index; } void PrinterFileSystem::ListAllFiles() { json req; char const * types[] {"timelapse","video", "model" }; req["type"] = types[m_file_type]; if (!m_file_storage.empty()) req["storage"] = m_file_storage; req["api_version"] = 2; req["notify"] = "DETAIL"; SendRequest(LIST_INFO, req, [this, type = m_file_type](json const& resp, FileList & list, auto) -> int { json files = resp["file_lists"]; for (auto& f : files) { std::string name = f["name"]; std::string path = f.value("path", ""); time_t time = f.value("time", 0); boost::uint64_t size = f["size"]; if (type > F_TIMELAPSE && path.empty()) // Fix old printer that always return timelapses return FILE_TYPE_ERR; File ff = {name, path, time, size, 0}; list.push_back(ff); } return 0; }, [this, type = m_file_type](int result, FileList list) { if (result != 0) { m_last_error = result; m_status = Status::Failed; m_file_list.clear(); BuildGroups(); UpdateGroupSelect(); SendChangedEvent(EVT_STATUS_CHANGED, m_status, "", result); SendChangedEvent(EVT_FILE_CHANGED); return 0; } if (type != m_file_type) return 0; m_file_list.swap(list); for (auto & file : m_file_list) file.thumbnail = default_thumbnail; std::sort(m_file_list.begin(), m_file_list.end()); auto iter1 = m_file_list.begin(); auto end1 = m_file_list.end(); auto iter2 = list.begin(); auto end2 = list.end(); while (iter1 != end1 && iter2 != end2) { if (*iter1 < *iter2) { ++iter1; } else if (*iter2 < *iter1) { ++iter2; } else { if (iter1->path == iter2->path && iter1->name == iter2->name) { iter1->thumbnail = iter2->thumbnail; iter1->flags = iter2->flags; if (!iter1->thumbnail.IsOk()) iter1->flags &= ~FF_THUMNAIL; iter1->download = iter2->download; iter1->local_path = iter2->local_path; iter1->metadata = iter2->metadata; } ++iter1; ++iter2; } } BuildGroups(); UpdateGroupSelect(); m_last_error = 0; m_status = Status::ListReady; SendChangedEvent(EVT_STATUS_CHANGED, m_status); SendChangedEvent(EVT_FILE_CHANGED); if ((m_task_flags & FF_DOWNLOAD) == 0) DownloadNextFile(); return 0; }); } void PrinterFileSystem::DeleteFiles(size_t index) { if (index == size_t(-1)) { size_t n = 0; for (size_t i = 0; i < m_file_list.size(); ++i) { auto &file = m_file_list[i]; if ((file.flags & FF_SELECT) != 0 && (file.flags & FF_DELETED) == 0) { file.flags |= FF_DELETED; ++n; } } if (n == 0) return; } else { if (index >= m_file_list.size()) return; auto &file = m_file_list[index]; if ((file.flags & FF_DELETED) != 0) return; file.flags |= FF_DELETED; } if ((m_task_flags & FF_DELETED) == 0) DeleteFilesContinue(); } struct PrinterFileSystem::Download : Progress { size_t index; std::string name; std::string path; std::string local_path; std::string error; boost::filesystem::ofstream ofs; boost::uuids::detail::md5 boost_md5; }; void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path) { if (index == (size_t) -1) { size_t n = 0; for (size_t i = 0; i < m_file_list.size(); ++i) { auto &file = m_file_list[i]; if ((file.flags & FF_SELECT) == 0) continue; if ((file.flags & FF_DOWNLOAD) != 0 && file.DownloadProgress() >= -1) continue; file.flags |= FF_DOWNLOAD; std::shared_ptr download(new Download); download->progress = -1; download->local_path = (boost::filesystem::path(path) / file.name).string(); file.download = download; ++n; } if (n == 0) return; } else { if (index >= m_file_list.size()) return; auto &file = m_file_list[index]; if ((file.flags & FF_DOWNLOAD) != 0 && file.DownloadProgress() >= -1) return; file.flags |= FF_DOWNLOAD; std::shared_ptr download(new Download); download->progress = -1; download->local_path = (boost::filesystem::path(path) / file.name).string(); file.download = download; } boost::filesystem::create_directories(path); if ((m_task_flags & FF_DOWNLOAD) == 0) DownloadNextFile(); } void PrinterFileSystem::DownloadCheckFiles(std::string const &path) { for (size_t i = 0; i < m_file_list.size(); ++i) { auto &file = m_file_list[i]; if ((file.flags & FF_DOWNLOAD) != 0 && file.download) continue; auto path2 = boost::filesystem::path(path) / file.name; boost::system::error_code ec; if (boost::filesystem::file_size(path2, ec) == file.size) { file.flags |= FF_DOWNLOAD; file.local_path = path2.string(); } } } bool PrinterFileSystem::DownloadCheckFile(size_t index) { if (index >= m_file_list.size()) return false; auto &file = m_file_list[index]; if ((file.flags & FF_DOWNLOAD) == 0 || file.local_path.empty()) return false; if (!boost::filesystem::exists(file.local_path)) { file.flags &= ~FF_DOWNLOAD; file.local_path.clear(); SendChangedEvent(EVT_DOWNLOAD, index, file.local_path); return false; } return true; } void PrinterFileSystem::DownloadCancel(size_t index) { if (index == (size_t) -1) return; if (index >= m_file_list.size()) return; auto &file = m_file_list[index]; if ((file.flags & FF_DOWNLOAD) == 0 || !file.download) return; if (file.DownloadProgress() >= 0) CancelRequest(m_download_seq); else file.flags &= ~FF_DOWNLOAD, file.download.reset(); } void PrinterFileSystem::FetchModel(size_t index, std::function callback) { if (m_task_flags & FF_FETCH_MODEL) return; json req; json arr; if (index == (size_t) -1) return; if (index >= m_file_list.size()) return; auto &file = m_file_list[index]; arr.push_back(file.path + "#_rels/.rels"); arr.push_back(file.path + "#3D/3dmodel.model"); arr.push_back(file.path + "#Metadata/model_settings.config"); arr.push_back(file.path + "#Metadata/slice_info.config"); arr.push_back(file.path + "#Metadata/project_settings.config"); for (auto & meta : file.metadata) { if (boost::algorithm::starts_with(meta.first, "plate_thumbnail_")) arr.push_back(file.path + "#" + meta.second); } req["paths"] = arr; req["zip"] = true; m_task_flags |= FF_FETCH_MODEL; std::shared_ptr file_data(new std::string()); m_fetch_model_seq = SendRequest( SUB_FILE, req, [file_data](json const &resp, Void &, unsigned char const *data) -> int { // in work thread, continue recv // receive data boost::uint32_t size = resp["size"]; if (size > 0) { *file_data += std::string((char *) data, size); } return 0; }, [this, file_data, callback](int result, Void const &) { if (result == CONTINUE) return; m_task_flags &= ~FF_FETCH_MODEL; if (result != 0) { auto iter = error_messages.find(result); if (iter != error_messages.end()) *file_data = _u8L(iter->second.c_str()); else file_data->clear(); } callback(result, *file_data); }); } void PrinterFileSystem::FetchModelCancel() { if ((m_task_flags & FF_FETCH_MODEL) == 0) return; CancelRequests2({m_fetch_model_seq}); } size_t PrinterFileSystem::GetCount() const { if (m_group_mode == G_NONE) return m_file_list.size(); return m_group_mode == G_YEAR ? m_group_year.size() : m_group_month.size(); } int PrinterFileSystem::File::DownloadProgress() const { return download ? download->progress : !local_path.empty() ? 100 : -2; } std::string PrinterFileSystem::File::Title() const { return Metadata("Title", ""); } std::string PrinterFileSystem::File::Metadata(std::string const &key, std::string const &dflt) const { auto iter = metadata.find(key); return iter == metadata.end() || iter->second.empty() ? dflt : iter->second; } size_t PrinterFileSystem::GetIndexAtTime(boost::uint32_t time) { auto iter = std::upper_bound(m_file_list.begin(), m_file_list.end(), File{"", "", time}); size_t n = std::distance(m_file_list.begin(), iter) - 1; if (m_group_mode == G_NONE) { return n; } auto & group = m_group_mode == G_YEAR ? m_group_year : m_group_month; auto iter2 = std::upper_bound(group.begin(), group.end(), n); return std::distance(group.begin(), iter2) - 1; } void PrinterFileSystem::ToggleSelect(size_t index) { if (m_group_mode != G_NONE) { size_t beg = m_group_mode == G_YEAR ? m_group_month[m_group_year[index]] : m_group_month[index]; size_t end_month = m_group_mode == G_YEAR ? ((index + 1) < m_group_year.size() ? m_group_year[index + 1] : m_group_month.size()) : index + 1; size_t end = end_month < m_group_month.size() ? m_group_month[end_month] : m_file_list.size(); if ((m_group_flags[index] & FF_SELECT) == 0) { for (int i = beg; i < end; ++i) { if ((m_file_list[i].flags & FF_SELECT) == 0) { m_file_list[i].flags |= FF_SELECT; ++m_select_count; } } m_group_flags[index] |= FF_SELECT; } else { for (int i = beg; i < end; ++i) { if (m_file_list[i].flags & FF_SELECT) { m_file_list[i].flags &= ~FF_SELECT; --m_select_count; } } m_group_flags[index] &= ~FF_SELECT; } } else if (index < m_file_list.size()) { m_file_list[index].flags ^= FF_SELECT; if (m_file_list[index].flags & FF_SELECT) ++m_select_count; else --m_select_count; } SendChangedEvent(EVT_SELECT_CHANGED, m_select_count); } void PrinterFileSystem::SelectAll(bool select) { if (select) { for (auto &f : m_file_list) f.flags |= FF_SELECT; m_select_count = m_file_list.size(); for (auto &s : m_group_flags) s |= FF_SELECT; } else { for (auto &f : m_file_list) f.flags &= ~FF_SELECT; m_select_count = 0; for (auto &s : m_group_flags) s &= ~FF_SELECT; } SendChangedEvent(EVT_SELECT_CHANGED, m_select_count); } size_t PrinterFileSystem::GetSelectCount() const { return m_select_count; } void PrinterFileSystem::SetFocusRange(size_t start, size_t count) { m_lock_start = start; m_lock_end = start + count; if (!m_stopped && (m_task_flags & FF_THUMNAIL) == 0) UpdateFocusThumbnail(); } PrinterFileSystem::File const &PrinterFileSystem::GetFile(size_t index) { if (m_group_mode == G_NONE) return m_file_list[index]; if (m_group_mode == G_YEAR) index = m_group_year[index]; return m_file_list[m_group_month[index]]; } PrinterFileSystem::File const &PrinterFileSystem::GetFile(size_t index, bool &select) { if (m_group_mode == G_NONE) { select = m_file_list[index].IsSelect(); return m_file_list[index]; } select = m_group_flags[index] & FF_SELECT; if (m_group_mode == G_YEAR) index = m_group_year[index]; return m_file_list[m_group_month[index]]; } void PrinterFileSystem::Attached() { boost::unique_lock lock(m_mutex); m_recv_thread = std::move(boost::thread([w = weak_from_this()] { boost::shared_ptr s = w.lock(); if (s) s->RecvMessageThread(); })); } void PrinterFileSystem::Start() { boost::unique_lock l(m_mutex); if (!m_stopped) return; m_stopped = false; m_cond.notify_all(); } void PrinterFileSystem::Retry() { boost::unique_lock l(m_mutex); m_stopped = false; m_cond.notify_all(); } void PrinterFileSystem::SetUrl(std::string const &url) { boost::unique_lock l(m_mutex); m_messages.push_back(url); m_cond.notify_all(); } void PrinterFileSystem::Stop(bool quit) { boost::unique_lock l(m_mutex); if (quit) { m_session.owner = nullptr; } else if (m_stopped) { return; } m_stopped = true; m_cond.notify_all(); } void PrinterFileSystem::BuildGroups() { m_group_year.clear(); m_group_month.clear(); if (m_file_list.empty()) return; wxDateTime t = wxDateTime((time_t) m_file_list.front().time); m_group_year.push_back(0); m_group_month.push_back(0); for (size_t i = 0; i < m_file_list.size(); ++i) { wxDateTime s = wxDateTime((time_t) m_file_list[i].time); if (s.GetYear() != t.GetYear()) { m_group_year.push_back(m_group_month.size()); m_group_month.push_back(i); } else if (s.GetMonth() != t.GetMonth()) { m_group_month.push_back(i); } t = s; } } void PrinterFileSystem::UpdateGroupSelect() { m_group_flags.clear(); int beg = 0; if (m_group_mode != G_NONE) { auto group = m_group_mode == G_YEAR ? m_group_year : m_group_month; if (m_group_mode == G_YEAR) for (auto &g : group) g = m_group_month[g]; m_group_flags.resize(group.size(), FF_SELECT); for (int i = 0; i < m_file_list.size(); ++i) { if ((m_file_list[i].flags & FF_SELECT) == 0) { auto iter = std::upper_bound(group.begin(), group.end(), i); m_group_flags[iter - group.begin() - 1] &= ~FF_SELECT; if (iter == group.end()) break; i = *iter - 1; // start from next group } } } } void PrinterFileSystem::DeleteFilesContinue() { std::vector indexes; std::vector names; std::vector paths; for (size_t i = 0; i < m_file_list.size(); ++i) if ((m_file_list[i].flags & FF_DELETED) && !m_file_list[i].name.empty()) { indexes.push_back(i); auto &file = m_file_list[i]; if (file.path.empty()) names.push_back(file.name); else paths.push_back(file.path); if (names.size() >= 64 || paths.size() >= 64) break; } m_task_flags &= ~FF_DELETED; if (names.empty() && paths.empty()) return; json req; json arr; if (paths.empty()) { for (auto &name : names) arr.push_back(name); req["delete"] = arr; } else { for (auto &path : paths) arr.push_back(path); req["paths"] = arr; } m_task_flags |= FF_DELETED; auto type = std::make_pair(m_file_type, m_file_storage); SendRequest( FILE_DEL, req, nullptr, [indexes, type, names = paths.empty() ? names : paths, bypath = !paths.empty(), this](int, Void const &) { // TODO: for (size_t i = indexes.size() - 1; i != size_t(-1); --i) FileRemoved(type, indexes[i], names[i], bypath); SendChangedEvent(EVT_FILE_CHANGED, indexes.size()); DeleteFilesContinue(); }); } void PrinterFileSystem::DownloadNextFile() { size_t index = size_t(-1); for (size_t i = 0; i < m_file_list.size(); ++i) { if (m_file_list[i].IsDownload() && m_file_list[i].DownloadProgress() == -1) { index = i; break; } } m_task_flags &= ~FF_DOWNLOAD; if (index >= m_file_list.size()) return; auto &file = m_file_list[index]; json req; if (file.path.empty()) req["file"] = file.name; else req["path"] = file.path; SendChangedEvent(EVT_DOWNLOAD, index, m_file_list[index].name); std::shared_ptr download(m_file_list[index].download); download->index = index; download->name = file.name; download->path = file.path; m_task_flags |= FF_DOWNLOAD; m_download_seq = SendRequest( FILE_DOWNLOAD, req, [download](json const &resp, Progress &prog, unsigned char const *data) -> int { // in work thread, continue recv size_t size = resp.value("size", 0); prog.size = resp["offset"]; prog.total = resp["total"]; if (prog.size == 0) { download->ofs.open(download->local_path, std::ios::binary); if (!download->ofs) { download->error = last_system_error(); wxLogWarning("PrinterFileSystem::DownloadNextFile open error: %s\n", wxString::FromUTF8(download->error)); return FILE_OPEN_ERR; } } if (download->total && (download->size != prog.size || download->total != prog.total)) { wxLogWarning("PrinterFileSystem::DownloadNextFile data error: %d != %d\n", download->size, prog.size); } // receive data download->ofs.write((char const *) data, size); if (!download->ofs) { download->error = last_system_error(); wxLogWarning("PrinterFileSystem::DownloadNextFile write error: %s\n", wxString::FromUTF8(download->error)); return FILE_READ_WRITE_ERR; } download->boost_md5.process_bytes(data, size); prog.size += size; download->total = prog.total; download->size = prog.size; if (prog.size < prog.total) { return 0; } download->ofs.close(); int result = 0; std::string md5 = resp["file_md5"]; // check size and md5 if (prog.size == prog.total) { boost::uuids::detail::md5::digest_type digest; download->boost_md5.get_digest(digest); for (int i = 0; i < 4; ++i) digest[i] = boost::endian::endian_reverse(digest[i]); std::string str_md5; const auto char_digest = reinterpret_cast(&digest[0]); boost::algorithm::hex(char_digest, char_digest + sizeof(digest), std::back_inserter(str_md5)); if (!boost::iequals(str_md5, md5)) { wxLogWarning("PrinterFileSystem::DownloadNextFile checksum error: %s != %s\n", str_md5, md5); boost::system::error_code ec; boost::filesystem::rename(download->local_path, download->local_path + ".tmp", ec); result = FILE_CHECK_ERR; } } else { result = FILE_SIZE_ERR; } if (result != 0) { boost::system::error_code ec; boost::filesystem::remove(download->local_path, ec); } return result; }, [this, download, type = std::make_pair(m_file_type, m_file_storage)](int result, Progress const &data) { int progress = data.total ? data.size * 100 / data.total : 0; if (result == CONTINUE) { if (download->progress == progress) return; } download->progress = progress; if (download->index != size_t(-1)) { auto file_index = FindFile(type, download->index, download->path.empty() ? download->name : download->path, !download->path.empty()); download->index = file_index.second; if (download->index != size_t(-1)) { auto &file = file_index.first[download->index]; if (result == CONTINUE) ; else if (result == SUCCESS) file.download.reset(), file.local_path = download->local_path; else if (result == ERROR_CANCEL) file.download.reset(), file.flags &= ~FF_DOWNLOAD; else // FAILED file.download.reset(); if (&file_index.first == &m_file_list) SendChangedEvent(EVT_DOWNLOAD, download->index, result ? download->error : file.local_path, result); } } if (result != CONTINUE) DownloadNextFile(); }); } enum ThumbnailType { OldThumbnail = 0, VideoThumbnail = 1, ModelMetadata = 2, ModelThumbnail = 3, FinishThumbnail }; void PrinterFileSystem::UpdateFocusThumbnail() { m_task_flags &= ~FF_THUMNAIL; if (m_lock_start >= m_file_list.size() || m_lock_start >= m_lock_end) return; size_t start = m_lock_start; size_t end = std::min(m_lock_end, GetCount()); std::vector names; std::vector paths; for (; start < end; ++start) { auto &file = GetFile(start); if ((file.flags & FF_THUMNAIL) == 0) { if (m_file_type == F_MODEL) { const_cast(file).metadata.emplace("Time", "..."); const_cast(file).metadata.emplace("Weight", "..."); } if (file.path.empty()) names.push_back({file.name, ""}); else paths.push_back({file.name, file.path}); if (names.size() >= 5 || paths.size() >= 5) break; if ((file.flags & FF_THUMNAIL_RETRY) != 0) { const_cast(file).flags &= ~FF_THUMNAIL_RETRY; break; } } } if (names.empty() && paths.empty()) return; m_task_flags |= FF_THUMNAIL; UpdateFocusThumbnail2(std::make_shared>(paths.empty() ? names : paths), paths.empty() ? OldThumbnail : m_file_type == F_MODEL ? ModelMetadata : VideoThumbnail); } bool PrinterFileSystem::ParseThumbnail(File &file) { std::istringstream iss(file.local_path, std::ios::binary); return ParseThumbnail(file, iss); } static std::string durationString(long duration) { static boost::regex rx("^0d(0h)?"); auto time = boost::format("%1%d%2%h%3%m") % (duration / 86400) % ((duration % 86400) / 3600) % ((duration % 3600) / 60); return boost::regex_replace(time.str(), rx, ""); } bool PrinterFileSystem::ParseThumbnail(File &file, std::istream &is) { Slic3r::DynamicPrintConfig config; Slic3r::Model model; Slic3r::PlateDataPtrs plate_data_list; Slic3r::Semver file_version; if (!Slic3r::load_gcode_3mf_from_stream(is, &config, &model, &plate_data_list, &file_version)) return false; float time = 0.f; float weight = 0.f; for (auto &plate : plate_data_list) { time += atof(plate->gcode_prediction.c_str()); weight += atof(plate->gcode_weight.c_str()); if (!plate->gcode_file.empty() && !plate->thumbnail_file.empty()) file.metadata.emplace("plate_thumbnail_" + std::to_string(plate->plate_index), plate->thumbnail_file); } file.metadata.emplace("Title", model.model_info->model_name); file.metadata.emplace("Time", durationString(round(time))); file.metadata.emplace("Weight", std::to_string(int(round(weight))) + 'g'); auto thumbnail = model.model_info->metadata_items["Thumbnail"]; if (thumbnail.empty() && !plate_data_list.empty()) { thumbnail = plate_data_list.front()->thumbnail_file; } file.metadata.emplace("Thumbnail", thumbnail); return true; } void PrinterFileSystem::UpdateFocusThumbnail2(std::shared_ptr> files, int type) { json req; json arr; if (type == OldThumbnail) { for (auto &file : *files) arr.push_back(file.name); req["files"] = arr; } else { if (type == VideoThumbnail) { for (auto &file : *files) arr.push_back(file.path + "#thumbnail"); } else if (type == ModelMetadata) { for (auto &file : *files) { arr.push_back(file.path + "#_rels/.rels"); arr.push_back(file.path + "#3D/3dmodel.model"); arr.push_back(file.path + "#Metadata/model_settings.config"); arr.push_back(file.path + "#Metadata/slice_info.config"); arr.push_back(file.path + "#Metadata/project_settings.config"); } req["zip"] = true; } else { // ModelThumbnail, FinishThumbnail std::vector fails; for (auto &file : *files) { if ((file.flags & FF_THUMNAIL) == 0) { fails.push_back(file.path); file.flags |= FF_THUMNAIL; } } for (auto &path : fails) { auto iter = std::find_if(m_file_list.begin(), m_file_list.end(), [&path](auto &f) { return f.path == path; }); if (iter != m_file_list.end()) { if (type == ModelThumbnail) iter->metadata.clear(); iter->flags |= fails.size() == 1 ? FF_THUMNAIL : FF_THUMNAIL_RETRY; } } if (type == ModelThumbnail) { for (auto &file : *files) { auto thumbnail = file.metadata["Thumbnail"]; if (!thumbnail.empty()) { arr.push_back(file.path + "#" + thumbnail); file.flags &= ~FF_THUMNAIL; file.local_path.clear(); } } } if (arr.empty()) { UpdateFocusThumbnail(); return; } } req["paths"] = arr; } SendRequest( SUB_FILE, req, [type, files](json const &resp, File &file, unsigned char const *data) -> int { // in work thread, continue recv // receive data wxString mimetype = resp.value("mimetype", ""); std::string thumbnail = resp.value("thumbnail", ""); std::string path = resp.value("path", ""); boost::uint32_t size = resp.value("size", 0); bool cont = resp.value("continue", false); if (size == 0) { file.name = thumbnail; file.path = path; return FILE_SIZE_ERR; } auto n = type == ModelMetadata ? std::string::npos : path.find_last_of('#'); // ModelMetadata is zipped without subpath auto path2 = n == std::string::npos ? path : path.substr(0, n); auto subpath = n == std::string::npos ? path : path.substr(n + 1); auto iter = std::find_if(files->begin(), files->end(), [&path2](auto &f) { return f.path == path2; }); if (cont) { if (iter != files->end()) iter->local_path += std::string((char *) data, size); return 0; } if (type == ModelMetadata) { if (iter != files->end()) file.local_path = iter->local_path + std::string((char *) data, size); else file.local_path = std::string((char *) data, size); ParseThumbnail(file); } else { if (mimetype.empty()) { if (subpath.empty()) subpath = thumbnail; auto n = subpath.find_last_of('.'); if (n != std::string::npos) mimetype = "image/" + subpath.substr(n + 1); else if (subpath == "thumbnail") mimetype = "image/jpeg"; // default jpg } if (iter != files->end() && !iter->local_path.empty()) { iter->local_path += std::string((char *) data, size); data = reinterpret_cast(iter->local_path.c_str()); size = iter->local_path.size(); } wxMemoryInputStream mis(data, size); mimetype.Replace("jpg", "jpeg"); file.thumbnail = wxImage(mis, mimetype); if (!file.thumbnail.IsOk()) { BOOST_LOG_TRIVIAL(info) << "PrinterFileSystem: parse thumbnail failed" << size << mimetype; } } file.name = thumbnail; file.path = path; return 0; }, [this, files, type](int result, File const &file) { auto n = file.name.find_last_of('.'); auto name = n == std::string::npos ? file.name : file.name.substr(0, n) + ".mp4"; n = (type == ModelMetadata) ? std::string::npos : file.path.find_last_of('#'); auto path = n == std::string::npos ? file.path : file.path.substr(0, n); auto iter = path.empty() ? std::find_if(m_file_list.begin(), m_file_list.end(), [&name](auto &f) { return f.name == name; }) : std::find_if(m_file_list.begin(), m_file_list.end(), [&path](auto &f) { return f.path == path; }); auto iter2 = path.empty() ? std::find_if(files->begin(), files->end(), [&name](auto &f) { return f.name == name; }) : std::find_if(files->begin(), files->end(), [&path](auto &f) { return f.path == path; }); if (iter != m_file_list.end()) { if (type == ModelMetadata) { iter->metadata = file.metadata; auto thumbnail = iter->metadata["Thumbnail"]; if (thumbnail.empty()) iter->flags |= FF_THUMNAIL; // DOTO: retry on fail int index = iter - m_file_list.begin(); SendChangedEvent(EVT_THUMBNAIL, index, file.name); if (iter2 != files->end()) iter2->metadata = file.metadata; } else { iter->flags |= FF_THUMNAIL; // DOTO: retry on fail if (file.thumbnail.IsOk()) { iter->thumbnail = file.thumbnail; int index = iter - m_file_list.begin(); SendChangedEvent(EVT_THUMBNAIL, index, file.name); } } } if (iter2 != files->end()) iter2->flags |= FF_THUMNAIL; // have received response if (result != CONTINUE) UpdateFocusThumbnail2(files, type == ModelMetadata ? ModelThumbnail : FinishThumbnail); }); } std::pair PrinterFileSystem::FindFile(std::pair type, size_t index, std::string const &name, bool by_path) { FileList & file_list = type == std::make_pair(m_file_type, m_file_storage) ? m_file_list : m_file_list_cache[type]; if (index >= file_list.size() || (by_path ? file_list[index].path : file_list[index].name) != name) { auto iter = std::find_if(m_file_list.begin(), file_list.end(), [name, by_path](File &f) { return (by_path ? f.path : f.name) == name; }); if (iter == m_file_list.end()) return {file_list, -1}; index = std::distance(m_file_list.begin(), iter); } return {file_list, index}; } void PrinterFileSystem::FileRemoved(std::pair type, size_t index, std::string const &name, bool by_path) { auto file_index = FindFile(type, index, name, by_path); if (file_index.second == size_t(-1)) return; if (&file_index.first == &m_file_list) { auto removeFromGroup = [](std::vector &group, size_t index, size_t total) { for (auto iter = group.begin(); iter != group.end(); ++iter) { size_t index2 = -1; if (*iter < index) continue; if (*iter == index) { auto iter2 = iter + 1; if (index + 1 == (iter2 == group.end() ? total : *iter2)) { index2 = std::distance(group.begin(), iter); } ++iter; } for (; iter != group.end(); ++iter) { --*iter; } return index2; } return size_t(-1); }; size_t index2 = removeFromGroup(m_group_month, index, m_file_list.size()); if (index2 < m_group_month.size()) { int index3 = removeFromGroup(m_group_year, index2, m_group_month.size()); if (index3 < m_group_year.size()) { m_group_year.erase(m_group_year.begin() + index3); if (m_group_mode == G_YEAR) m_group_flags.erase(m_group_flags.begin() + index3); } m_group_month.erase(m_group_month.begin() + index2); if (m_group_mode == G_MONTH) m_group_flags.erase(m_group_flags.begin() + index2); } } file_index.first.erase(file_index.first.begin() + index); } struct CallbackEvent : wxCommandEvent { CallbackEvent(std::function const &callback, boost::weak_ptr owner) : wxCommandEvent(EVT_FILE_CALLBACK), callback(callback), owner(owner) {} ~CallbackEvent(){ if (!owner.expired()) callback(); } std::function const callback; boost::weak_ptr owner; }; void PrinterFileSystem::PostCallback(std::function const& callback) { wxCommandEvent *e = new CallbackEvent(callback, boost::weak_ptr(shared_from_this())); wxQueueEvent(this, e); } void PrinterFileSystem::SendChangedEvent(wxEventType type, size_t index, std::string const &str, long extra) { wxCommandEvent event(type); event.SetEventObject(this); event.SetInt(index); if (!str.empty()) event.SetString(wxString::FromUTF8(str.c_str())); else if (auto iter = error_messages.find(extra); iter != error_messages.end()) event.SetString(_L(iter->second.c_str())); else if (extra > CONTINUE && extra != ERROR_CANCEL) event.SetString(wxString::Format(_L("Error code: %d"), int(extra))); event.SetExtraLong(extra); if (wxThread::IsMain()) ProcessEventLocally(event); else wxPostEvent(this, event); } void PrinterFileSystem::DumpLog(void * thiz, int, tchar const *msg) { BOOST_LOG_TRIVIAL(info) << "PrinterFileSystem: " << wxString(msg).ToUTF8().data(); static_cast(thiz)->Bambu_FreeLogMsg(msg); } boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback) { if (m_session.tunnel == nullptr) { Retry(); callback(ERROR_PIPE, json(), nullptr); return 0; } boost::uint32_t seq = m_sequence + m_callbacks.size(); json root; root["cmdtype"] = type; root["sequence"] = seq; root["req"] = req; std::ostringstream oss; oss << root; auto msg = oss.str(); boost::unique_lock l(m_mutex); m_messages.push_back(msg); m_callbacks.push_back(callback); m_cond.notify_all(); return seq; } void PrinterFileSystem::InstallNotify(int type, callback_t2 const &callback) { type -= NOTIFY_FIRST; if (m_notifies.size() <= size_t(type)) m_notifies.resize(type + 1); m_notifies[type] = callback; } void PrinterFileSystem::CancelRequest(boost::uint32_t seq) { CancelRequests({seq}); } void PrinterFileSystem::CancelRequests(std::vector const &seqs) { json req; json arr; for (auto seq : seqs) arr.push_back(seq); req["tasks"] = arr; SendRequest(TASK_CANCEL, req, [this](int result, json const &resp, unsigned char const *) -> int { if (result != 0) return result; json tasks = resp["tasks"]; std::vector seqs; for (auto &f : tasks) seqs.push_back(f); CancelRequests2(seqs); return 0; }); } void PrinterFileSystem::CancelRequests2(std::vector const &seqs) { std::vector> callbacks; boost::unique_lock l(m_mutex); for (auto &f : seqs) { boost::uint32_t seq = f; seq -= m_sequence; if (size_t(seq) >= m_callbacks.size()) continue; auto &c = m_callbacks[seq]; if (c == nullptr) continue; callbacks.emplace_back(f, c); c = nullptr; } while (!m_callbacks.empty() && m_callbacks.front() == nullptr) { m_callbacks.pop_front(); ++m_sequence; } l.unlock(); for (auto &c : callbacks) { wxLogInfo("PrinterFileSystem::CancelRequests2: %u\n", c.first); c.second(ERROR_CANCEL, json(), nullptr); } } void PrinterFileSystem::RecvMessageThread() { Bambu_Sample sample; boost::unique_lock l(m_mutex); Reconnect(l, 0); while (true) { if (m_stopped && (m_session.owner == nullptr || (m_messages.empty() && m_callbacks.empty()))) { Reconnect(l, 0); // Close and wait start again if (m_session.owner == nullptr) { // clear callbacks first auto callbacks(std::move(m_callbacks)); break; } } if (!m_messages.empty()) { auto & msg = m_messages.front(); // OutputDebugStringA(msg.c_str()); // OutputDebugStringA("\n"); wxLogInfo("PrinterFileSystem::SendRequest >>>: \n%s\n", wxString::FromUTF8(msg)); l.unlock(); int n = Bambu_SendMessage(m_session.tunnel, CTRL_TYPE, msg.c_str(), msg.length()); l.lock(); if (n == 0) m_messages.pop_front(); else if (n != Bambu_would_block) { Reconnect(l, n); continue; } } l.unlock(); int n = Bambu_ReadSample(m_session.tunnel, &sample); l.lock(); if (n == 0) { HandleResponse(l, sample); } else if (n == Bambu_stream_end) { m_stopped = true; Reconnect(l, m_status == ListSyncing ? ERROR_RES_BUSY : ERROR_PIPE); } else if (n == Bambu_would_block) { m_cond.timed_wait(l, boost::posix_time::milliseconds(m_messages.empty() && m_callbacks.empty() ? 1000 : 20)); } else { Reconnect(l, n); } } // while } void PrinterFileSystem::HandleResponse(boost::unique_lock &l, Bambu_Sample const &sample) { unsigned char const *end = sample.buffer + sample.size; unsigned char const *json_end = (unsigned char const *) memchr(sample.buffer, '\n', sample.size); while (json_end && json_end + 3 < end && json_end[1] != '\n') json_end = (unsigned char const *) memchr(json_end + 2, '\n', end - json_end - 2); if (json_end) json_end += 2; else json_end = end; std::string msg((char const *) sample.buffer, json_end - sample.buffer); json root; //OutputDebugStringA(msg.c_str()); //OutputDebugStringA("\n"); wxLogInfo("PrinterFileSystem::HandleResponse <<<: \n%s\n", wxString::FromUTF8(msg)); std::istringstream iss(msg); int cmd = 0; int seq = -1; int result = 0; json resp; try { iss >> root; if (!root["result"].is_null()) { result = root["result"]; seq = root["sequence"]; resp = root["reply"]; } else { // maybe notify cmd = root["cmdtype"]; seq = root["sequence"]; resp = root["notify"]; } } catch (...) { result = ERROR_JSON; return; } if (cmd > 0) { if (cmd < NOTIFY_FIRST) return; cmd -= NOTIFY_FIRST; if (size_t(cmd) >= m_notifies.size()) return; auto n = m_notifies[cmd]; l.unlock(); n(result, resp, json_end); l.lock(); } else { int seq2 = seq - m_sequence; if (size_t(seq2) >= m_callbacks.size()) return; auto c = m_callbacks[seq2]; if (c == nullptr) return; l.unlock(); int result2 = c(result, resp, json_end); l.lock(); if (result2 != CONTINUE) { int seq2 = seq - m_sequence; m_callbacks[seq2] = callback_t2(); if (seq2 == 0) { while (!m_callbacks.empty() && m_callbacks.front() == nullptr) { m_callbacks.pop_front(); ++m_sequence; } } if (result == CONTINUE) { l.unlock(); CancelRequest(seq); l.lock(); } } } } void PrinterFileSystem::Reconnect(boost::unique_lock &l, int result) { if (m_session.tunnel) { auto tunnel = m_session.tunnel; m_session.tunnel = nullptr; wxLogMessage("PrinterFileSystem::Reconnect close %d", result); l.unlock(); Bambu_Close(tunnel); Bambu_Destroy(tunnel); l.lock(); } if (m_session.owner == nullptr) return; json r; while (!m_callbacks.empty()) { auto c = m_callbacks.front(); m_callbacks.pop_front(); ++m_sequence; if (c) c(result, r, nullptr); } m_messages.clear(); if (result) m_cond.timed_wait(l, boost::posix_time::seconds(10)); while (true) { while (m_stopped) { if (m_session.owner == nullptr) return; m_cond.wait(l); } wxLogMessage("PrinterFileSystem::Reconnect Initializing"); m_status = Status::Initializing; m_last_error = 0; SendChangedEvent(EVT_STATUS_CHANGED, m_status); // wait for url while (!m_stopped && m_messages.empty()) m_cond.wait(l); if (m_stopped || m_messages.empty()) continue; std::string url = m_messages.front(); m_messages.clear(); if (url.size() < 2) { wxLogMessage("PrinterFileSystem::Reconnect Initialize failed: %s", wxString::FromUTF8(url)); m_last_error = atoi(url.c_str()); if (m_last_error == 0) m_stopped = true; } else { wxLogInfo("PrinterFileSystem::Reconnect Initialized: %s", wxString::FromUTF8(url)); l.unlock(); m_status = Status::Connecting; wxLogMessage("PrinterFileSystem::Reconnect Connecting"); SendChangedEvent(EVT_STATUS_CHANGED, m_status); Bambu_Tunnel tunnel = nullptr; int ret = Bambu_Create(&tunnel, url.c_str()); if (ret == 0) { Bambu_SetLogger(tunnel, DumpLog, this); ret = Bambu_Open(tunnel); } if (ret == 0) do { ret = Bambu_StartStreamEx ? Bambu_StartStreamEx(tunnel, CTRL_TYPE) : Bambu_StartStream(tunnel, false); } while (ret == Bambu_would_block); l.lock(); if (ret == 0) { m_session.tunnel = tunnel; wxLogMessage("PrinterFileSystem::Reconnect Connected"); break; } else if (ret == 1) { m_stopped = true; ret = ERROR_RES_BUSY; } if (tunnel) { Bambu_Close(tunnel); Bambu_Destroy(tunnel); } m_last_error = ret; } wxLogMessage("PrinterFileSystem::Reconnect Failed"); m_status = Status::Failed; SendChangedEvent(EVT_STATUS_CHANGED, m_status, "", url.size() < 2 ? 1 : m_last_error); m_cond.timed_wait(l, boost::posix_time::seconds(10)); } m_status = Status::ListSyncing; SendChangedEvent(EVT_STATUS_CHANGED, m_status); #ifdef PRINTER_FILE_SYSTEM_TEST PostCallback([this] { SendChangedEvent(EVT_FILE_CHANGED); }); #else PostCallback([this] { m_task_flags = 0; ListAllFiles(); }); #endif } #include #if defined(_MSC_VER) || defined(_WIN32) #include #else #include #endif #if defined(_MSC_VER) || defined(_WIN32) static HMODULE module = NULL; #else static void* module = NULL; #endif static void* get_function(const char* name) { void* function = nullptr; if (!module) return function; #if defined(_MSC_VER) || defined(_WIN32) function = GetProcAddress(module, name); #else function = dlsym(module, name); #endif if (!function) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find function %1%") % name; } return function; } #define GET_FUNC(x) lib.x = reinterpret_cast(get_function(#x)) StaticBambuLib &StaticBambuLib::get() { static StaticBambuLib lib; // first load the library if (lib.Bambu_Open) return lib; if (!module) { module = Slic3r::NetworkAgent::get_bambu_source_entry(); } if (!module) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", can not Load Library"; } GET_FUNC(Bambu_Create); GET_FUNC(Bambu_Open); GET_FUNC(Bambu_StartStream); GET_FUNC(Bambu_StartStreamEx); GET_FUNC(Bambu_GetStreamCount); GET_FUNC(Bambu_GetStreamInfo); GET_FUNC(Bambu_SendMessage); GET_FUNC(Bambu_ReadSample); GET_FUNC(Bambu_Close); GET_FUNC(Bambu_Destroy); GET_FUNC(Bambu_SetLogger); GET_FUNC(Bambu_FreeLogMsg); if (!lib.Bambu_Open) lib.Bambu_Create = Fake_Bambu_Create; return lib; } extern "C" BambuLib *bambulib_get() { return &StaticBambuLib::get(); }