void ItemsManagerWorker::Init() { items_.clear(); std::string items = data_manager_.Get("items"); if (items.size() != 0) { rapidjson::Document doc; doc.Parse(items.c_str()); for (auto item = doc.Begin(); item != doc.End(); ++item) items_.push_back(std::make_shared<Item>(*item)); } tabs_.clear(); std::string tabs = data_manager_.Get("tabs"); if (tabs.size() != 0) { rapidjson::Document doc; if (doc.Parse(tabs.c_str()).HasParseError()) { QLOG_ERROR() << "Malformed tabs data:" << tabs.c_str() << "The error was" << rapidjson::GetParseError_En(doc.GetParseError()); return; } for (auto &tab : doc) { if (!tab.HasMember("n") || !tab["n"].IsString()) { QLOG_ERROR() << "Malformed tabs data:" << tabs.c_str() << "Tab doesn't contain its name (field 'n')."; continue; } tabs_.push_back(tab["n"].GetString()); } } emit ItemsRefreshed(items_, tabs_, true); }
void ItemsManager::OnItemsRefreshed(const Items &items, const std::vector<std::string> &tabs) { shop_.ExpireShopData(); if (shop_.auto_update()) shop_.SubmitShopToForum(); emit ItemsRefreshed(items, tabs); }
MainWindow::MainWindow(std::unique_ptr<Application> app): app_(std::move(app)), ui(new Ui::MainWindow), current_search_(nullptr), search_count_(0), auto_online_(app_->data_manager(), app_->sensitive_data_manager()) { #ifdef Q_OS_WIN32 createWinId(); taskbar_button_ = new QWinTaskbarButton(this); taskbar_button_->setWindow(this->windowHandle()); #endif image_cache_ = new ImageCache(Filesystem::UserDir() + "/cache"); InitializeUi(); InitializeLogging(); InitializeSearchForm(); NewSearch(); image_network_manager_ = new QNetworkAccessManager; connect(image_network_manager_, SIGNAL(finished(QNetworkReply*)), this, SLOT(OnImageFetched(QNetworkReply*))); connect(&app_->items_manager(), SIGNAL(ItemsRefreshed(Items, std::vector<std::string>)), this, SLOT(OnItemsRefreshed())); connect(&app_->items_manager(), SIGNAL(StatusUpdate(ItemsFetchStatus)), this, SLOT(OnItemsManagerStatusUpdate(ItemsFetchStatus))); connect(&update_checker_, &UpdateChecker::UpdateAvailable, this, &MainWindow::OnUpdateAvailable); connect(&auto_online_, &AutoOnline::Update, this, &MainWindow::OnOnlineUpdate); }
void ItemsManager::Start() { thread_ = std::make_unique<QThread>(); worker_ = std::make_unique<ItemsManagerWorker>(app_, thread_.get()); connect(thread_.get(), SIGNAL(started()), worker_.get(), SLOT(Init())); connect(this, SIGNAL(UpdateSignal()), worker_.get(), SLOT(Update())); connect(worker_.get(), SIGNAL(StatusUpdate(ItemsFetchStatus)), this, SLOT(OnStatusUpdate(ItemsFetchStatus))); connect(worker_.get(), SIGNAL(ItemsRefreshed(Items, std::vector<std::string>)), this, SLOT(OnItemsRefreshed(Items, std::vector<std::string>))); worker_->moveToThread(thread_.get()); thread_->start(); }
void ItemsManagerWorker::OnTabReceived(int request_id) { if (!replies_.count(request_id)) { QLOG_WARN() << "Received a reply for request" << request_id << "that was not requested."; return; } ItemsReply reply = replies_[request_id]; QLOG_INFO() << "Received a reply for" << reply.request.location.GetHeader().c_str(); QByteArray bytes = reply.network_reply->readAll(); rapidjson::Document doc; doc.Parse(bytes.constData()); bool error = false; if (!doc.IsObject()) { QLOG_WARN() << request_id << "got a non-object response"; error = true; } else if (doc.HasMember("error")) { // this can happen if user is browsing stash in background and we can't know about it QLOG_WARN() << request_id << "got 'error' instead of stash tab contents"; error = true; } // re-queue a failed request if (error) QueueRequest(reply.request.network_request, reply.request.location); ++requests_completed_; if (!error) ++total_completed_; bool throttled = false; if (requests_completed_ == requests_needed_ && queue_.size() > 0) { throttled = true; QLOG_INFO() << "Sleeping one minute to prevent throttling."; QTimer::singleShot(kThrottleSleep * 1000, this, SLOT(FetchItems())); } CurrentStatusUpdate status = CurrentStatusUpdate(); status.state = throttled ? ProgramState::ItemsPaused : ProgramState::ItemsReceive; status.progress = total_completed_; status.total = total_needed_; if (total_completed_ == total_needed_) status.state = ProgramState::ItemsCompleted; emit StatusUpdate(status); if (error) return; ParseItems(&doc["items"], reply.request.location, doc.GetAllocator()); if (total_completed_ == total_needed_) { // all requests completed emit ItemsRefreshed(items_, tabs_, false); // since we build items_as_string_ in a hackish way inside ParseItems last character will either be // ' ' when no items were parsed or ',' when at least one item is parsed, and the first character is '[' items_as_string_[items_as_string_.size() - 1] = ']'; // DataManager is thread safe so it's ok to call it here data_manager_.Set("items", items_as_string_); data_manager_.Set("tabs", tabs_as_string_); updating_ = false; QLOG_INFO() << "Finished updating stash."; // if we're at the verge of getting throttled, sleep so we don't if (requests_completed_ == kThrottleRequests) QTimer::singleShot(kThrottleSleep, this, SLOT(PreserveSelectedCharacter())); else PreserveSelectedCharacter(); } reply.network_reply->deleteLater(); }
void ItemsManagerWorker::OnTabReceived(int request_id) { if (!replies_.count(request_id)) { QLOG_WARN() << "Received a reply for request" << request_id << "that was not requested."; return; } ItemsReply reply = replies_[request_id]; bool cache_status = reply.network_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); if (cache_status) { QLOG_DEBUG() << "Received a cached reply for" << reply.request.location.GetHeader().c_str(); ++cached_requests_completed_; ++total_cached_; } else { QLOG_DEBUG() << "Received a reply for" << reply.request.location.GetHeader().c_str(); } QByteArray bytes = reply.network_reply->readAll(); rapidjson::Document doc; doc.Parse(bytes.constData()); bool error = false; if (!doc.IsObject()) { QLOG_WARN() << request_id << "got a non-object response"; error = true; } else if (doc.HasMember("error")) { // this can happen if user is browsing stash in background and we can't know about it QLOG_WARN() << request_id << "got 'error' instead of stash tab contents"; error = true; } // re-queue a failed request if (error) { // We can 'cache' error response document so make sure we remove it // before reque tab_cache_->remove(reply.request.network_request.url()); QueueRequest(reply.request.network_request, reply.request.location); } ++requests_completed_; if (!error) ++total_completed_; bool throttled = false; if (requests_completed_ == requests_needed_ && queue_.size() > 0) { if (cached_requests_completed_ > 0) { // We basically don't want cached requests to count against throttle limit // so if we did get any cached requests fetch up to that number without a // large delay QTimer::singleShot(1, [&]() { FetchItems(cached_requests_completed_); }); } else { throttled = true; QLOG_DEBUG() << "Sleeping one minute to prevent throttling."; QTimer::singleShot(kThrottleSleep * 1000, this, SLOT(FetchItems())); } } CurrentStatusUpdate status = CurrentStatusUpdate(); status.state = throttled ? ProgramState::ItemsPaused : ProgramState::ItemsReceive; status.progress = total_completed_; status.total = total_needed_; status.cached = total_cached_; if (total_completed_ == total_needed_) status.state = ProgramState::ItemsCompleted; emit StatusUpdate(status); if (error) return; ParseItems(&doc["items"], reply.request.location, doc.GetAllocator()); if (total_completed_ == total_needed_) { // It's possible that we receive character vs stash tabs out of order, or users // move items around in a tab and we get them in a different order. For // consistency we want to present the tab data in a deterministic way to the rest // of the application. Especially so we don't try to update shop when nothing actually // changed. So sort items_ here before emitting and then generate // item list as strings. std::sort(begin(items_), end(items_), [](const std::shared_ptr<Item> &a, const std::shared_ptr<Item> &b) { return b->location() < a->location(); }); QStringList tmp; for (auto const &item: items_) { tmp.push_back(item->json().c_str()); } auto items_as_string = std::string("[") + tmp.join(",").toStdString() + "]"; // all requests completed emit ItemsRefreshed(items_, tabs_, false); // DataStore is thread safe so it's ok to call it here data_.Set("items", items_as_string); data_.Set("tabs", tabs_as_string_); updating_ = false; QLOG_DEBUG() << "Finished updating stash."; // if we're at the verge of getting throttled, sleep so we don't if (requests_completed_ == kThrottleRequests) QTimer::singleShot(kThrottleSleep, this, SLOT(PreserveSelectedCharacter())); else PreserveSelectedCharacter(); } reply.network_reply->deleteLater(); }