/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #ifndef PROJECTLOCK_H #define PROJECTLOCK_H #include #include #include #include /** @brief The ProjectLock class A sidecar file beside a project that says who has it open, so that a second person (or a second QElectroTech) opening the same file on a shared drive is told, instead of both editing and the last save wiping the other's work. The file is ".~.lock" in the project's folder, a small versioned JSON object. It is not the project file itself: a project is saved by writing a temporary file and renaming it over the old one, which would replace a lock kept in the project at every save. Taking the lock is an exclusive create (QFile NewOnly: O_EXCL, or CREATE_NEW on Windows), the one operation shared drives do reliably. This is not a QLockFile: its stale time defaults to 30 s, and for a lock made on another machine it decides by age alone, so another PC would take a lock held for over 30 s. Here a lock is stale only when it was made on this machine by a process that is gone; any other lock is shown to the user, with the age of its heartbeat, to judge. */ class ProjectLock : public QObject { Q_OBJECT public: /// What a lock file says struct Holder { bool valid = false; ///< false: the file could not be read as a lock QString session; QString user; QString host; qint64 pid = 0; QString app; QDateTime since; QDateTime heartbeat; }; enum class Result { Acquired, ///< the lock is ours Held, ///< a live lock of another session is there Unavailable ///< the lock file cannot be made (read-only folder, ...) }; explicit ProjectLock(QObject *parent = nullptr); ~ProjectLock() override; Result acquire(const QString &project_path, bool take_over = false); bool moveTo(const QString &project_path); void release(); bool refresh(); bool isHeld() const {return m_held;} QString projectPath() const {return m_project_path;} QString session() const {return m_session;} Holder blockingHolder() const {return m_blocking;} static QString lockPathFor(const QString &project_path); static Holder readHolder(const QString &project_path); static bool isStale(const Holder &holder); static Holder liveHolder(const QString &project_path, const QString &own_session = QString()); static bool enabled(); static void setEnabled(bool); static bool mayTake(); static void setHeadless(bool); static void setHeartbeatInterval(int msec); static QString thisHost(); static QString thisUser(); signals: /// Another session took the lock over (its "Open anyway") void lockTaken(const ProjectLock::Holder &by); private: QByteArray content(const QDateTime &since, const QDateTime &heartbeat) const; bool createExclusive(const QString &lock_path); static void hide(const QString &lock_path); QString m_project_path; QString m_session; QDateTime m_since; bool m_held = false; Holder m_blocking; QTimer m_timer; }; Q_DECLARE_METATYPE(ProjectLock::Holder) #endif