diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index f41411c1..8a273eb9 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -167,6 +167,7 @@ ComputerManager::ComputerManager(QObject *parent) settings.setArrayIndex(i); NvComputer* computer = new NvComputer(settings); m_KnownHosts[computer->uuid] = computer; + m_LastSerializedHosts[computer->uuid] = *computer; } settings.endArray(); @@ -250,6 +251,14 @@ void DelayedFlushThread::run() { // Reset the delayed flush flag to ensure any racing saveHosts() call will set it again m_ComputerManager->m_NeedsDelayedFlush = false; + + // Update the last serialized hosts map under the delayed flush mutex + m_ComputerManager->m_LastSerializedHosts.clear(); + for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) { + // Copy the current state of the NvComputer to allow us to check later if we need + // to serialize it again when attribute updates occur. + m_ComputerManager->m_LastSerializedHosts[computer->uuid] = *computer; + } } // Perform the flush @@ -433,6 +442,17 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer, computer->deleteLater(); } +void ComputerManager::saveHost(NvComputer *computer) +{ + // If no serializable properties changed, don't bother saving hosts + QMutexLocker lock(&m_DelayedFlushMutex); + if (!m_LastSerializedHosts.value(computer->uuid).isEqualSerialized(*computer)) { + // Queue a request for a delayed flush to QSettings outside of the lock + lock.unlock(); + saveHosts(); + } +} + void ComputerManager::handleComputerStateChanged(NvComputer* computer) { emit computerStateChanged(computer); @@ -442,8 +462,8 @@ void ComputerManager::handleComputerStateChanged(NvComputer* computer) emit quitAppCompleted(QVariant()); } - // Save updated hosts to QSettings - saveHosts(); + // Save updates to this host + saveHost(computer); } QVector ComputerManager::getComputers() @@ -479,7 +499,7 @@ public: m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid); } - // Persist the new host list + // Persist the new host list with this computer deleted m_ComputerManager->saveHosts(); // Delete the polling entry first. This will stop all polling threads too. @@ -520,9 +540,6 @@ void ComputerManager::renameHost(NvComputer* computer, QString name) void ComputerManager::clientSideAttributeUpdated(NvComputer* computer) { - // Persist the change - saveHosts(); - // Notify the UI of the state change handleComputerStateChanged(computer); } @@ -580,7 +597,7 @@ private: break; case NvPairingManager::PairState::PAIRED: // Persist the newly pinned server certificate for this host - m_ComputerManager->saveHosts(); + m_ComputerManager->saveHost(m_Computer); emit pairingCompleted(m_Computer, nullptr); break; diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 4ea2e8fc..351f3c7a 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -251,6 +251,8 @@ private slots: private: void saveHosts(); + void saveHost(NvComputer* computer); + QHostAddress getBestGlobalAddressV6(QVector& addresses); void startPollingComputer(NvComputer* computer); @@ -259,6 +261,7 @@ private: QReadWriteLock m_Lock; QMap m_KnownHosts; QMap m_PollEntries; + QHash m_LastSerializedHosts; // Protected by m_DelayedFlushMutex QSharedPointer m_MdnsServer; QMdnsEngine::Browser* m_MdnsBrowser; QVector m_PendingResolution; diff --git a/app/backend/nvcomputer.cpp b/app/backend/nvcomputer.cpp index 693111ee..1144d38f 100644 --- a/app/backend/nvcomputer.cpp +++ b/app/backend/nvcomputer.cpp @@ -94,7 +94,7 @@ void NvComputer::serialize(QSettings& settings, bool serializeApps) const settings.setValue(SER_NVIDIASOFTWARE, isNvidiaServerSoftware); // Avoid deleting an existing applist if we couldn't get one - if (!appList.isEmpty()) { + if (!appList.isEmpty() && serializeApps) { settings.remove(SER_APPLIST); settings.beginWriteArray(SER_APPLIST); for (int i = 0; i < appList.count(); i++) { @@ -105,6 +105,21 @@ void NvComputer::serialize(QSettings& settings, bool serializeApps) const } } +bool NvComputer::isEqualSerialized(const NvComputer &that) const +{ + return this->name == that.name && + this->hasCustomName == that.hasCustomName && + this->uuid == that.uuid && + this->macAddress == that.macAddress && + this->localAddress == that.localAddress && + this->remoteAddress == that.remoteAddress && + this->ipv6Address == that.ipv6Address && + this->manualAddress == that.manualAddress && + this->serverCert == that.serverCert && + this->isNvidiaServerSoftware == that.isNvidiaServerSoftware && + this->appList == that.appList; +} + void NvComputer::sortAppList() { std::stable_sort(appList.begin(), appList.end(), [](const NvApp& app1, const NvApp& app2) { diff --git a/app/backend/nvcomputer.h b/app/backend/nvcomputer.h index e90fe1a8..ab9e0b30 100644 --- a/app/backend/nvcomputer.h +++ b/app/backend/nvcomputer.h @@ -8,6 +8,16 @@ #include #include +class CopySafeReadWriteLock : public QReadWriteLock +{ +public: + CopySafeReadWriteLock() = default; + + // Don't actually copy the QReadWriteLock + CopySafeReadWriteLock(const CopySafeReadWriteLock&) {} + CopySafeReadWriteLock& operator=(const CopySafeReadWriteLock &) { return *this; } +}; + class NvComputer { friend class PcMonitorThread; @@ -22,6 +32,12 @@ private: bool pendingQuit; public: + NvComputer() = default; + + NvComputer(const NvComputer& other) = default; + + NvComputer& operator=(const NvComputer &) = default; + explicit NvComputer(NvHTTP& http, QString serverInfo); explicit NvComputer(QSettings& settings); @@ -51,6 +67,9 @@ public: void serialize(QSettings& settings, bool serializeApps) const; + bool + isEqualSerialized(const NvComputer& that) const; + enum PairState { PS_UNKNOWN, @@ -91,9 +110,10 @@ public: QSslCertificate serverCert; QVector appList; bool isNvidiaServerSoftware; + // Remember to update isEqualSerialized() when adding fields here! // Synchronization - mutable QReadWriteLock lock; + mutable CopySafeReadWriteLock lock; private: uint16_t externalPort;