13

I have QGIS-Project-Files saved to a network drive and was wondering if it is possible to warn users that a project file is already opened by an other user to prevent concurrent editing of the same project file.

I was thinking of showing an alert like this:

enter image description here

Does anyone have a clue how this could be done?

I found some information on stackexchange but I am not sure if these could be implemented in QGIS in some generic way(not a manually added 'openProject' macro):

https://stackoverflow.com/questions/21126108/ensuring-that-my-program-is-not-doing-a-concurrent-file-write/21149744#21149744

https://stackoverflow.com/questions/11114492/check-if-a-file-is-not-open-not-used-by-other-process-in-python

I know that QGIS shows a warning if I try to save a project file another user has changed since I opened it.... But it would be even better if I got the information in the moment when I open the project that someone else has opened the project, too.

The best option would be to implement this in QGIS core (C++) but a pythonic solution would be also interesting.

A generic C++ solution is probably harder as it has to check the exclusive file access on different OS's (windows, linux, mac).

Thomas B
  • 8,807
  • 1
  • 21
  • 62
  • 1
    Add code in startup.py https://gis.stackexchange.com/a/318817/49538 or you can create plugin and execute your code when plugin initialised for example – Fran Raga Dec 18 '19 at 11:36
  • Yeah, so, these 200 rep were quite the waste on my answer, right; you were looking explicitly for an extension to it with OS based file locking. I didn't see this until now. It would help tremendously to know the OS you are using, as a solution will be very specific. Working on the OS level in this context is not my expertise, but it also seems that even OS based file locking mechanisms may face stale locks (it keeps happening with different software suites I use), while the APIs change either frequently (Linux), or are not open source (Windows). – geozelot Feb 03 '21 at 08:15
  • @geozelot: As there was no new answer I awarded the bounty manually so it was not wasted :-) . Thanks for your answer. I use Windows and am looking for a solution that works more generec for all QGIS projects. So your code is a good startpoint for this and could perhaps be adapted to work for all projects. – Thomas B Mar 08 '21 at 08:09

1 Answers1

6

Took me a while to find: a long while ago I had a naive lock file implementation in place, using Python Macros (activate and alter in <QGIS>|Project|Properties...|Macros).

I updated the code to use the QGIS 3.x classes and syntax:

import os
import socket
from qgis.core import QgsProject
from PyQt5.QtWidgets import QMessageBox

utility functions

get the executing machine and process id

def _getId(): return socket.getfqdn()+'.'+str(os.getpid())

read machine and process id holding lock from lock file

def _getLockerId(file): try: with open(file) as _file: return _file.readline() except: return

create lock file path

def _getLockFile(): _path, _file = __getProjectPath() return os.path.join(_path,'.'+_file+'.lock')

create current project file path root and name

def __getProjectPath(): return os.path.split(QgsProject.instance().absoluteFilePath())

QGIS macros

def openProject(): lockfile = _getLockFile()

try:
    with open(lockfile, &quot;x&quot;) as _file:
        _file.write(_getId())
except OSError:
    _path, _file = __getProjectPath()
    copyfile = os.path.join(_path, _getId() + &quot;__&quot; + _file)

    QMessageBox.information(
        None,
        &quot;Project locked&quot;,
        &quot;Cannot open project with write access: &quot;+
          &quot;the following user (user.PID)\n\n&quot;+_getLockerId(lockfile)+
          &quot;\n\nhas already aquired a lock on the project!&quot;+
          &quot;\nCopying project and switching context to\n\n&quot;+copyfile
    )

    QgsProject.instance().write(copyfile)
    QgsProject.instance().read()
    QgsProject.instance().setDirty()

def saveProject(): pass

def closeProject(): lockfile = _getLockFile()

if _getId() == _getLockerId(lockfile):
    os.remove(lockfile)

This will make QGIS check for the existence of a (hidden) lock file (naming schema: .<project_name>.lock) at the project location (needs write access!), and

  • if present, opens a notification window, copies the project to the same location (naming schema: <socket_user.PID>__<project_name>) and reads the copy into QGIS
  • if not present, one is created holding the current machine and process id, and the original project is opened as usual

The lock holder will delete the file after closing the project.


This worked well enough for a small team, when I had to come up with a quick and dirty solution since no proper DB/versioning system had been at hand. I haven't done extensive testing; things that may be an issue:

  • no safety against stale locks due to killed QGIS instances or whatever; deleting the lock file manually is the way to go then
  • no error handling whatsoever; with write access at the path location, I haven't encountered any issue, but I'm sure there are
  • you'll get a note that "The loaded project file on disk was meanwhile changed.", which refers to the original project file as per the date, but overwrites the copy, so you can safely ignore it

An implementation using the lockfile module may be better; I haven't done any idiomatic, cosmetic or performance updates to the code since I've first written it. Feel free to improve and post as answers.

geozelot
  • 30,050
  • 4
  • 32
  • 56