Browse Source

Startup autodetection of running sessions and option to connect to them

master
Nils 6 months ago
parent
commit
277ca6720f
  1. 1
      CHANGELOG
  2. 2
      engine/config.py
  3. 3
      engine/nsmservercontrol.py
  4. 1
      engine/start.py
  5. 4
      qtgui/mainwindow.py
  6. 2
      qtgui/movesessionroot.py
  7. 160
      qtgui/startchooserunningnsmd.py

1
CHANGELOG

@ -9,6 +9,7 @@ External contributors notice at the end of the line: (LastName, FirstName / nick
Initial startup-check for old $HOME session root vs new XDG root.
Offers to move the session files to the new location.
Support nsmd 1.6.0 lockfiles. Older nsmd versions still work.
On startup give a choice to connect to an already running session (nsmd 1.6.0)
## 2022-01-15 0.3.1

2
engine/config.py

@ -21,7 +21,7 @@ METADATA={
#release announcements, entries in software directories etc.
"tagline" : 'Music and audio production session manager based on NSM.',
"version" : "0.3.1",
"version" : "0.4.0",
"year" : "2022",
"author" : "Laborejo Software Suite",
"url" : "https://www.laborejo.org/agordejo",

3
engine/nsmservercontrol.py

@ -371,12 +371,15 @@ class NsmServerControl(object):
#self.nsmOSCUrl must be a tuple compatible to the result of urlparse. (hostname, port)
self.singleInstanceSocket = None
if parameterNsmOSCUrl:
#There is either a user provided nsm url via --url commandline or paramter the GUI detected a running nsmd and let the use choose that.
o = urlparse(parameterNsmOSCUrl)
#self.nsmOSCUrl = (o.hostname, o.port) #this forces lowercase. in rare circumstances this is not correct and we must be case sensitive. fix:
self.nsmOSCUrl = o.netloc.split(":")[0], o.port
else:
envResult = self._getNsmOSCUrlFromEnvironment()
if envResult:
#In case there is no actual nsmd running but there still was a NSM_URL env var, e.g. over the network, use this.
#There is a corner case that the env is local but the user chose to ignore the GUI way (nsmd 1.6.0) to proivde us directly with a specific URL.
self.nsmOSCUrl = envResult
else:
#This is the default case. User just starts the GUI. The other modes are concious decisions to either start with URL as parameter or in an NSM environment.

1
engine/start.py

@ -127,7 +127,6 @@ else:
"continueLastSession": args.continueLastSession, #bool
}
if PATHS["startupSession"]:
logger.warning("--continue ignored because --load-session was used.")
PATHS["continueLastSession"] = None #just in case. See --help string

4
qtgui/mainwindow.py

@ -50,6 +50,7 @@ from .resources import *
from .settings import SettingsDialog
from .jacktransport import JackTransportControls
from .movesessionroot import xdgVersionChange
from .startchooserunningnsmd import checkForRunningNsmd
api.eventLoop = EventLoop()
@ -164,7 +165,8 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.stack_loaded_session.customContextMenuRequested.connect(self.customContextMenu)
#nsmd 1.6.0
xdgVersionChange(self.qtApp)
xdgVersionChange(self.qtApp) #may present a blocking dialog, may do nothing.
checkForRunningNsmd(self.qtApp, PATHS) #may present a blocking dialog, may do nothing. Injects nsm url into PATHS
#Api Callbacks
api.callbacks.sessionClosed.append(self.reactCallback_sessionClosed)

2
qtgui/movesessionroot.py

@ -71,7 +71,7 @@ def xdgVersionChange(qtApp):
except RuntimeError: #no home dir!
oldexists = False
xdgdatahome = getenv("$XDG_DATA_HOME")
xdgdatahome = getenv("XDG_DATA_HOME")
if not xdgdatahome:
try:
xdgdatahome = pathlib.Path("~/.local/share").expanduser()

160
qtgui/startchooserunningnsmd.py

@ -0,0 +1,160 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net )
This file is part of the Laborejo Software Suite ( https://www.laborejo.org ).
This application 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
"""
import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library
import pathlib
from os import getenv, listdir
from sys import argv as sysargv
#Third Party
from PyQt5 import QtCore, QtGui, QtWidgets
#Our own files
from .movesessionroot import nsmVersionGreater160
def checkForRunningNsmd(qtApp, PATHS:dict):
"""Before we start the engine with our own nsmd server:
nsmd >= 1.6.0 introduced run file discovery of sessions and empty nsmd.
Ask the user if they want to connect to one of these.
Injects the path into PATHS["url"], which is used by engine.api.startEngine()
"""
if PATHS["url"] or any("url" in param for param in sysargv): #this is really the same test twice...
#This is not our problem anymore.
return
if not nsmVersionGreater160():
return #see docstring
parent = qtApp.desktop()
if not getenv("XDG_RUNTIME_DIR"):
logger.warning("Your system has no environment variable $XDG_RUNTIME_DIR. That is unusual for Linux. Automatic detection of already running sessions deactivated.")
return #On Linux that should exist. But if not, no reason to crash.
logger.info("Detecting if there are already nsmd or sessions running under this user")
session_rundir = pathlib.Path(getenv("XDG_RUNTIME_DIR"), "nsm")
logger.info(f"Supposed nsmd session rundir: {session_rundir}")
nsmd_rundir = pathlib.Path(getenv("XDG_RUNTIME_DIR"), "nsm", "d")
logger.info(f"Supposed nsmd server rundir: {nsmd_rundir}")
if not session_rundir.exists() or not session_rundir.is_dir():
logger.info("nsmd rundir does not exist. Continue.")
return
existing_daemons = listdir(nsmd_rundir)
existing_sessions = listdir(session_rundir)
if existing_sessions:
existing_sessions.remove("d") #Remove the daemon subdir
if not existing_daemons:
logger.info("nsmd rundir exist, but is empty. Continue.")
return #if there are no daemons there are no sessions.
#There are nsmd running under this user, maybe even open sessions.
#Now build a list for the user to choose from.
#If a session is running on an nsmd show this, otherwise show the empty nsmd
sessions = []
nsmd_pids = set()
for s in existing_sessions:
res = {}
sessions.append(res)
with open(pathlib.Path(session_rundir, s), "r") as f:
res["hashName"] = s #name of the lockfile. has hash postfix.
res["path"] = pathlib.Path(f.readline().strip("\n")) #file path in ~/.local/share/nsm
res["name"] = res["path"].name #present this to the user.
res["url"] = f.readline().strip("\n") #nsmd url
res["pid"] = int(f.readline().strip("\n")) #nsmd pid
nsmd_pids.add(res["pid"])
daemons = []
for d in existing_daemons:
#d is a file with a PID as name. e.g. 9627
#inside is the NSM_URL.
if int(d) in nsmd_pids: #we already have a running session on this server
continue
else: #empty nsmd.
res = {}
daemons.append(res)
with open(pathlib.Path(nsmd_rundir, d), "r") as f:
res["pid"] = d
res["url"] = f.readline().strip("\n") #just the first line
res["name"] = QtCore.QCoreApplication.translate("StartChooseRunningSession", "Empty Server") + " " + res["url"]
#We now have all empty nsmd in var daemons
assert sessions or daemons
PATHS["url"] = ChooseSessionWidget(qtApp, sessions+daemons).url #"Global Variable"
class ChooseSessionWidget(QtWidgets.QDialog):
"""return value in self.url. Might be None"""
def __init__(self, qtApp, sessionDicts:list):
super().__init__() #QDialog can't parent to qtApp
self.qtApp = qtApp
self.setModal(True) #block until closed
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
self.label = QtWidgets.QLabel(self)
self.label.setText(QtCore.QCoreApplication.translate("StartChooseRunningSession", "Select session or server to connect to.\nCancel to start our own server."))
self.layout.addWidget(self.label)
self.comboBox = QtWidgets.QComboBox(self)
self.layout.addWidget(self.comboBox)
for s in sessionDicts:
self.comboBox.addItem(s["name"], s["url"]) #Show name, use url as data.
self.buttonBox = QtWidgets.QDialogButtonBox(self)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.layout.addWidget(self.buttonBox)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.exec()
def accept(self):
self.url = self.comboBox.currentData() #easy abstraction so that the caller does not need to know our widget name
super().accept()
def reject(self):
self.url = None
super().reject()
Loading…
Cancel
Save