Tell a second person who has a project open, and open it read-only

Two people opening the same project on a shared drive could both edit it,
and the last save silently wiped the other's work. A project now has a
hidden lock file beside it (.~plant.qetz.lock, small JSON: user, host,
process, version, since, heartbeat). Opening a project another live session
holds shows who has it, since when and how long ago they were last active,
and offers Open read-only (default), Open anyway, or Cancel.

- The lock is made with an exclusive create, refreshed every minute and
  deleted when the project closes. It is not a QLockFile: that treats any
  lock from another machine as stale after 30 s. Here only a lock made on
  this machine by a process that no longer exists is taken silently.
- A folder the lock cannot be made in, or the setting turned off
  (Settings > General > Projects), opens as before.
- Save As and .qet saved as .qetz move the lock to the new file, and refuse
  a file someone else has open. A project opened read-only for another's
  lock does not save over that file.
- --resave and --set-titleblock refuse a file someone has open unless given
  --ignore-lock; the read-only verbs never look at locks and headless runs
  never take one. A script's qet.save() refuses the same way.

Checked: 17 new unit tests (5 go red when stale detection and the exclusive
create are broken), full ctest 107/107, the dialog and both choices in the
GUI under Xvfb, and --resave refused/allowed from the command line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-16 13:01:54 +13:00
parent c4b5cf2f11
commit 3521c64ba3
17 changed files with 1012 additions and 0 deletions
+15
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cli_export.h"
#include "projectlock.h"
#include "bomexport.h"
#include "bordertitleblock.h"
@@ -1019,6 +1020,7 @@ int run(const QStringList &args)
// --no-slaves and --no-junctions leave rows out of --export-bom.
const bool includeSlaves = filtered.removeAll("--no-slaves") == 0;
const bool includeJunctions = filtered.removeAll("--no-junctions") == 0;
const bool ignoreLock = filtered.removeAll("--ignore-lock") > 0;
QString flag;
QStringList rest;
@@ -1083,6 +1085,19 @@ int run(const QStringList &args)
return exportNets(project, output);
if (format == "links")
return exportLinks(project, output);
//The verbs that write a project file leave alone one that someone
//has open (their lock beside it), unless told to: the read-only
//verbs above never look at locks
if ((format == "resave" || format == "settb") && !ignoreLock) {
const ProjectLock::Holder holder = ProjectLock::liveHolder(output);
if (holder.valid) {
err << "'" << output << "' is open by " << holder.user << " on "
<< holder.host << " (lock file " << ProjectLock::lockPathFor(output)
<< "). Not writing it; close it there, write to another file, "
"or pass --ignore-lock.\n";
return 1;
}
}
if (format == "resave")
return resaveProject(project, output);
if (format == "settb")