From 6c3218ee055651375c7c7d972653de6ec1692f49 Mon Sep 17 00:00:00 2001
From: Nils <>
Date: Fri, 10 Apr 2020 23:23:57 +0200
Subject: [PATCH] Better logging with file names
---
engine/__init__.py | 2 +-
engine/main.py | 2 +-
engine/pattern.py | 1 +
engine/track.py | 2 +
qtgui/constantsAndConfigs.py | 2 +-
qtgui/mainwindow.py | 48 +++---
qtgui/pattern_grid.py | 3 +
qtgui/songeditor.py | 2 +
qtgui/timeline.py | 2 +
template/calfbox/py/_cbox2.py | 2 +-
template/engine/api.py | 4 +-
template/engine/data.py | 4 +-
template/engine/duration.py | 6 +-
template/engine/history.py | 4 +-
template/engine/input_midi.py | 4 +-
template/engine/metronome.py | 4 +-
template/engine/midi.py | 4 +-
template/engine/pitch.py | 4 +-
template/engine/sampler_sf2.py | 14 +-
template/engine/sequencer.py | 18 ++-
template/engine/session.py | 27 ++--
template/qtgui/about.py | 2 +-
template/qtgui/chooseSessionDirectory.py | 5 +-
template/qtgui/constantsAndConfigs.py | 3 +-
template/qtgui/debugScript.py | 5 +-
template/qtgui/mainwindow.py | 16 +-
template/qtgui/menu.py | 3 +-
template/qtgui/nsmclient.py | 177 ++++++++++++++---------
template/qtgui/nsmsingleserver.py | 5 +-
template/qtgui/submenus.py | 3 +
template/qtgui/usermanual.py | 3 +-
template/start.py | 38 +++--
32 files changed, 256 insertions(+), 163 deletions(-)
diff --git a/engine/__init__.py b/engine/__init__.py
index 301e00d..2c2d148 100644
--- a/engine/__init__.py
+++ b/engine/__init__.py
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#This file only exists as a reminder to _not_ create it again wrongly in the future.
diff --git a/engine/main.py b/engine/main.py
index f9c1f83..80bb2ae 100644
--- a/engine/main.py
+++ b/engine/main.py
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library Modules
diff --git a/engine/pattern.py b/engine/pattern.py
index e658027..34ce8dd 100644
--- a/engine/pattern.py
+++ b/engine/pattern.py
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library Modules
from typing import List, Set, Dict, Tuple
diff --git a/engine/track.py b/engine/track.py
index 525b5ad..49cbf20 100644
--- a/engine/track.py
+++ b/engine/track.py
@@ -20,6 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Standard Library Modules
from typing import List, Set, Dict, Tuple
diff --git a/qtgui/constantsAndConfigs.py b/qtgui/constantsAndConfigs.py
index 6541b63..7d73a8a 100644
--- a/qtgui/constantsAndConfigs.py
+++ b/qtgui/constantsAndConfigs.py
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
from template.qtgui.constantsAndConfigs import ConstantsAndConfigs as TemplateConstantsAndConfigs
diff --git a/qtgui/mainwindow.py b/qtgui/mainwindow.py
index acf7db1..3cf60a9 100644
--- a/qtgui/mainwindow.py
+++ b/qtgui/mainwindow.py
@@ -19,7 +19,7 @@ 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 .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library Modules
import os.path
@@ -109,25 +109,25 @@ class MainWindow(TemplateMainWindow):
self.ui.toStartButton.setText("")
self.ui.toStartButton.setIcon(QtGui.QIcon(':tostart.png'))
self.ui.toStartButton.setToolTip(QtCore.QCoreApplication.translate("PlaybackControls", "[Home] Jump to Start"))
- self.ui.toStartButton.clicked.connect(api.toStart)
+ self.ui.toStartButton.clicked.connect(api.rewind)
self.ui.centralwidget.addAction(self.ui.actionToStart) #no action without connection to a widget.
self.ui.actionToStart.triggered.connect(self.ui.toStartButton.click)
- ##Song Editor
- self.ui.songEditorView.parentMainWindow = self
- self.songEditor = SongEditor(parentView=self.ui.songEditorView)
+ ##Song Editor
+ self.ui.songEditorView.parentMainWindow = self
+ self.songEditor = SongEditor(parentView=self.ui.songEditorView)
self.ui.songEditorView.setScene(self.songEditor)
- self.ui.songEditorView.setViewport(QtWidgets.QOpenGLWidget())
-
- self.ui.trackEditorView.parentMainWindow = self
+ self.ui.songEditorView.setViewport(QtWidgets.QOpenGLWidget())
+
+ self.ui.trackEditorView.parentMainWindow = self
self.trackLabelEditor = TrackLabelEditor(parentView=self.ui.trackEditorView)
self.ui.trackEditorView.setScene(self.trackLabelEditor)
- self.ui.trackEditorView.setViewport(QtWidgets.QOpenGLWidget())
+ self.ui.trackEditorView.setViewport(QtWidgets.QOpenGLWidget())
- self.ui.timelineView.parentMainWindow = self
+ self.ui.timelineView.parentMainWindow = self
self.timeline = Timeline(parentView=self.ui.timelineView)
self.ui.timelineView.setScene(self.timeline)
- self.ui.timelineView.setViewport(QtWidgets.QOpenGLWidget())
+ self.ui.timelineView.setViewport(QtWidgets.QOpenGLWidget())
#Sync the vertical trackEditorView scrollbar (which is never shown) with the songEditorView scrollbar.
self.ui.songEditorView.setVerticalScrollBar(self.ui.trackEditorView.verticalScrollBar()) #this seems backwards, but it is correct :)
@@ -135,9 +135,9 @@ class MainWindow(TemplateMainWindow):
#Sync the horizontal timelineView scrollbar (which is never shown) with the songEditorView scrollbar.
self.ui.songEditorView.setHorizontalScrollBar(self.ui.timelineView.horizontalScrollBar()) #this seems backwards, but it is correct :)
- ##Pattern Editor
+ ##Pattern Editor
self.ui.gridView.parentMainWindow = self
-
+
self.patternGrid = PatternGrid(parentView=self.ui.gridView)
self.ui.gridView.setScene(self.patternGrid)
self.ui.gridView.setRenderHints(QtGui.QPainter.TextAntialiasing)
@@ -163,7 +163,7 @@ class MainWindow(TemplateMainWindow):
#There is always a track. Forcing that to be active is better than having to hide all the pattern widgets, or to disable them.
#However, we need the engine to be ready.
self.chooseCurrentTrack(api.session.data.tracks[0].export()) #By Grabthar's hammer, by the suns of Worvan, what a hack! #TODO: Access to the sessions data structure directly instead of api. Not good. Getter function or api @property is cleaner.
-
+
self.ui.gridView.horizontalScrollBar().setSliderPosition(0)
self.ui.gridView.verticalScrollBar().setSliderPosition(0)
@@ -215,15 +215,15 @@ class MainWindow(TemplateMainWindow):
newTrackExporDict = api.createSiblingTrack(self.currentTrackId)
self.chooseCurrentTrack(newTrackExporDict)
- def _populatePatternToolbar(self):
+ def _populatePatternToolbar(self):
self.patternToolbar.contextMenuEvent = api.nothing #remove that annoying Checkbox context menu
-
+
spacerItemLeft = QtWidgets.QWidget()
spacerItemLeft.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
spacerItemRight = QtWidgets.QWidget()
spacerItemRight.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
- self.patternToolbar.addWidget(spacerItemLeft)
+ self.patternToolbar.addWidget(spacerItemLeft)
#spacerItemRight is added as last widget
#Actual widgets
@@ -232,11 +232,11 @@ class MainWindow(TemplateMainWindow):
transposeControls = TransposeControls(parentScene=self.patternGrid)
self.patternToolbar.addWidget(transposeControls)
-
+
#Finally add a spacer to center all widgets
- self.patternToolbar.addWidget(spacerItemRight)
-
-
+ self.patternToolbar.addWidget(spacerItemRight)
+
+
def _populateToolbar(self):
self.ui.toolBar.contextMenuEvent = api.nothing #remove that annoying Checkbox context menu
@@ -483,6 +483,6 @@ class MainWindow(TemplateMainWindow):
error= ("fail", "delete", "merge")[s.errorHandling.currentIndex()]
api.convert_subdivisions(value, error)
- #Override template functions
- def _stretchXCoordinates(*args): pass
-
+ #Override template functions
+ def _stretchXCoordinates(*args): pass
+
diff --git a/qtgui/pattern_grid.py b/qtgui/pattern_grid.py
index b5d4108..bfb617d 100644
--- a/qtgui/pattern_grid.py
+++ b/qtgui/pattern_grid.py
@@ -18,6 +18,9 @@ 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 .
"""
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
from time import time
import engine.api as api #Session is already loaded and created, no duplication.
from template.engine import pitch
diff --git a/qtgui/songeditor.py b/qtgui/songeditor.py
index c737e49..0ce4a78 100644
--- a/qtgui/songeditor.py
+++ b/qtgui/songeditor.py
@@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
from time import time
import engine.api as api #Session is already loaded and created, no duplication.
from PyQt5 import QtCore, QtGui, QtWidgets
diff --git a/qtgui/timeline.py b/qtgui/timeline.py
index f8a7ef6..37d6dca 100644
--- a/qtgui/timeline.py
+++ b/qtgui/timeline.py
@@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
SIZE_UNIT = 25 #this is in manual sync with songeditor.py SIZE_UNIT
diff --git a/template/calfbox/py/_cbox2.py b/template/calfbox/py/_cbox2.py
index 94cd182..0191cd5 100644
--- a/template/calfbox/py/_cbox2.py
+++ b/template/calfbox/py/_cbox2.py
@@ -100,7 +100,7 @@ def find_calfbox():
cblib = os.environ["CALFBOXLIBABSPATH"]
else:
cblib = find_library('calfbox')
- logging.info("Loading calfbox shared library: %s" % (cblib))
+ logger.info:("Loading calfbox shared library: %s" % (cblib))
cb = cdll.LoadLibrary(cblib)
return cb
diff --git a/template/engine/api.py b/template/engine/api.py
index 5bde20e..c5d2e43 100644
--- a/template/engine/api.py
+++ b/template/engine/api.py
@@ -20,7 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Python Standard Library
import os.path
diff --git a/template/engine/data.py b/template/engine/data.py
index 5f108d2..d7df9a9 100644
--- a/template/engine/data.py
+++ b/template/engine/data.py
@@ -20,7 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
class Data(object):
"""Base class to all Data. Data is the class that gets added to the session. Consider this an
diff --git a/template/engine/duration.py b/template/engine/duration.py
index 57aa894..246fe7e 100644
--- a/template/engine/duration.py
+++ b/template/engine/duration.py
@@ -24,7 +24,9 @@ along with this program. If not, see .
This file handles various durations and their conversions.
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
from engine.config import METADATA
@@ -47,7 +49,7 @@ D512 = int(D256 / 2)
D1024 = int(D512 / 2) # set this to a number with many factors, like 210. According to http://homes.sice.indiana.edu/donbyrd/CMNExtremes.htm this is the real world limit.
if not int(D1024) == D1024:
- logging.error(f"Warning: Lowest duration D0124 has decimal places: {D0124} but should be a plain integer. Your D4 value: {D4} has not enough 2^n factors. ")
+ logger.error:(f"Warning: Lowest duration D0124 has decimal places: {D0124} but should be a plain integer. Your D4 value: {D4} has not enough 2^n factors. ")
D2 = D4 *2
D1 = D2 *2
diff --git a/template/engine/history.py b/template/engine/history.py
index f39e267..99efd63 100644
--- a/template/engine/history.py
+++ b/template/engine/history.py
@@ -19,7 +19,9 @@ 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 .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
from contextlib import contextmanager
diff --git a/template/engine/input_midi.py b/template/engine/input_midi.py
index 50f41ef..3685f4b 100644
--- a/template/engine/input_midi.py
+++ b/template/engine/input_midi.py
@@ -19,7 +19,9 @@ 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 .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Python Standard Library
diff --git a/template/engine/metronome.py b/template/engine/metronome.py
index a07e6d2..93f7582 100644
--- a/template/engine/metronome.py
+++ b/template/engine/metronome.py
@@ -20,7 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Standard Library Modules
from typing import Tuple
diff --git a/template/engine/midi.py b/template/engine/midi.py
index bdfb3c8..bf30555 100644
--- a/template/engine/midi.py
+++ b/template/engine/midi.py
@@ -24,7 +24,9 @@ along with this program. If not, see .
This file handles various pitches and their conversions.
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Third Party Modules
#Template Modules
diff --git a/template/engine/pitch.py b/template/engine/pitch.py
index 6623e21..4c154af 100644
--- a/template/engine/pitch.py
+++ b/template/engine/pitch.py
@@ -24,7 +24,9 @@ along with this program. If not, see .
This file handles various pitches and their conversions.
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Third Party Modules
#Template Modules
diff --git a/template/engine/sampler_sf2.py b/template/engine/sampler_sf2.py
index 3597b18..8f64898 100644
--- a/template/engine/sampler_sf2.py
+++ b/template/engine/sampler_sf2.py
@@ -20,7 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Python Standard Lib
import os.path
@@ -89,7 +91,7 @@ class Sampler_sf2(Data):
try:
cbox.JackIO.Metadata.set_port_order(portname, channelNumber)
except Exception as e: #No Jack Meta Data
- logging.error(e)
+ logger.error:(e)
#Also sort the mixing channels
try:
@@ -98,7 +100,7 @@ class Sampler_sf2(Data):
portname = f"{cbox.JackIO.status().client_name}:right_mix"
cbox.JackIO.Metadata.set_port_order(portname, 34)
except Exception as e: #No Jack Meta Data
- logging.error(e)
+ logger.error:(e)
@@ -139,7 +141,7 @@ class Sampler_sf2(Data):
def loadSoundfont(self, filePath, defaultSoundfont=None):
"""defaultSoundfont is a special case. The path is not saved"""
- logging.info(f"loading path: \"{filePath}\" defaultSoundfont parameter: {defaultSoundfont}")
+ logger.info:(f"loading path: \"{filePath}\" defaultSoundfont parameter: {defaultSoundfont}")
#Remove the old link, if present. We cannot unlink directly in loadSoundfont because it is quite possible that a user will try out another soundfont but decide not to save but close and reopen to get his old soundfont back.
if self.filePath and os.path.islink(self.filePath):
@@ -188,7 +190,7 @@ class Sampler_sf2(Data):
for youchoose_bank in self.patchlist.keys():
for youchoose_program, youchoose_name in self.patchlist[youchoose_bank].items():
youchoose_bank, youchoose_program, youchoose_name
- logging.info(METADATA["name"] + f": Channel {channel} Old bank/program not possible in new soundfont. Switching to first available program.")
+ logger.info:(METADATA["name"] + f": Channel {channel} Old bank/program not possible in new soundfont. Switching to first available program.")
self.setPatch(channel, youchoose_bank, youchoose_program)
break #inner loop. one instrument is enough.
@@ -249,7 +251,7 @@ class Sampler_sf2(Data):
cbox.JackIO.Metadata.set_pretty_name(portnameL, f"{str(channel).zfill(2)}-L : {name}")
cbox.JackIO.Metadata.set_pretty_name(portnameR, f"{str(channel).zfill(2)}-R : {name}")
except Exception as e: #No Jack Meta Data
- logging.error(e)
+ logger.error:(e)
def updateAllChannelJackMetadaPrettyname(self):
diff --git a/template/engine/sequencer.py b/template/engine/sequencer.py
index 075c37a..a8ec53e 100644
--- a/template/engine/sequencer.py
+++ b/template/engine/sequencer.py
@@ -19,7 +19,9 @@ 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 .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Standard Library
from typing import List, Dict, Tuple, Iterable
@@ -110,7 +112,7 @@ class Score(Data):
try:
cbox.JackIO.Metadata.set_all_port_order(order)
except Exception as e: #No Jack Meta Data
- logging.error(e)
+ logger.error:(e)
def trackById(self, trackId:int):
for track in self.tracks:
@@ -318,7 +320,7 @@ class SequencerInterface(_Interface): #Basically the midi part of a track.
def _processAfterInit(self):
#Create midi out and cbox track
- logging.info("Creating empty SequencerInterface instance")
+ logger.info:("Creating empty SequencerInterface instance")
super()._processAfterInit()
self.cboxMidiOutUuid = cbox.JackIO.create_midi_output(self._name)
self.calfboxTrack.set_external_output(self.cboxMidiOutUuid)
@@ -517,7 +519,7 @@ class TempoMap(object):
def __init__(self, parentData):
- logging.info("Creating empty TempoMap instance")
+ logger.info:("Creating empty TempoMap instance")
self.parentData = parentData
self._tempoMap = {0:(120.0, 4, 4)} # 4/4, 120bpm. will not be used on startup, but is needed if transportMaster is switched on
self._isTransportMaster = False
@@ -550,7 +552,7 @@ class TempoMap(object):
@isTransportMaster.setter
def isTransportMaster(self, value:bool):
- logging.info(f"Jack Transport Master status: {value}")
+ logger.info:(f"Jack Transport Master status: {value}")
self._isTransportMaster = value
if value:
self._sendToCbox() #reactivate existing tempo map
@@ -574,7 +576,7 @@ class TempoMap(object):
#Don't use the following. Empty tempo maps are allowed, especially in jack transport slave mode.
#Instead set a default tempo 120 on init explicitly
#if not self._tempoMap:
- # logging.error("Found invalid tempo map. Forcing to 120 bpm. Please correct manually")
+ # logger.error:("Found invalid tempo map. Forcing to 120 bpm. Please correct manually")
# self._tempoMap = {0, 120.0}
def _clearCboxTempoMap(self):
@@ -636,7 +638,7 @@ class TempoMap(object):
assert 0 in self._tempoMap, self._tempoMap
return self._tempoMap[0][0] #second [0] is the tuple (tempo, timesig, timesig)
else:
- logging.info("Requested Quarter Notes per Minute, but we are not transport master")
+ logger.info:("Requested Quarter Notes per Minute, but we are not transport master")
return None
#Save / Load / Export
@@ -650,7 +652,7 @@ class TempoMap(object):
@classmethod
def instanceFromSerializedData(cls, parentData, serializedData):
- logging.info("Loading TempoMap from saved file")
+ logger.info:("Loading TempoMap from saved file")
self = cls.__new__(cls)
self.parentData = parentData
self._tempoMap = serializedData["tempoMap"] #json saves dict-keys as strings. We revert back in sanitize()
diff --git a/template/engine/session.py b/template/engine/session.py
index 3901391..941f07b 100644
--- a/template/engine/session.py
+++ b/template/engine/session.py
@@ -19,7 +19,8 @@ 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 .
"""
-import logging; logging.info("import {}".format(__file__))
+
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Python Standard Library
from warnings import warn
@@ -82,7 +83,7 @@ class Session(object):
return jsonDataAsString.replace(self.sessionPrefix, "")
def nsm_openOrNewCallback(self, ourPath, sessionName, ourClientNameUnderNSM):
- logging.info("New/Open session")
+ logger.info:("New/Open session")
cbox.init_engine("")
#Most set config must be called before start audio. Set audio outputs seems to be an exception.
@@ -110,13 +111,13 @@ class Session(object):
self.data = None #This makes debugging output nicer. If we init Data() here all errors will be presented as follow-up error "while handling exception FileNotFoundError".
except (NotADirectoryError, PermissionError) as e:
self.data = None
- logging.error("Will not load or save because: " + e.__repr__())
+ logger.error:("Will not load or save because: " + e.__repr__())
if not self.data:
self.data = Data(parentSession = self)
- logging.info("New/Open session complete")
+ logger.info:("New/Open session complete")
def openFromJson(self, absoluteJsonFilePath):
- logging.info("Loading file start")
+ logger.info:("Loading file start")
with open(absoluteJsonFilePath, "r", encoding="utf-8") as f:
try:
text = self.addSessionPrefix(f.read())
@@ -129,7 +130,7 @@ class Session(object):
self.guiWasSavedAsNSMVisible = result["guiWasSavedAsNSMVisible"]
self.guiSharedDataToSave = result["guiSharedDataToSave"]
assert type(self.guiSharedDataToSave) is dict, self.guiSharedDataToSave
- logging.info("Loading file complete")
+ logger.info:("Loading file complete")
return Data.instanceFromSerializedData(parentSession=self, serializedData=result)
else:
warn(f"""{absoluteJsonFilePath} was saved with {result["version"]} but we need {METADATA["version"]}""")
@@ -145,7 +146,7 @@ class Session(object):
if not os.path.exists(ourPath):
os.makedirs(ourPath)
except Exception as e:
- logging.error("Will not load or save because: " + e.__repr__())
+ logger.error:("Will not load or save because: " + e.__repr__())
result = self.data.serialize()
result["origin"] = METADATA["url"]
@@ -161,19 +162,19 @@ class Session(object):
with open(self.absoluteJsonFilePath, "w", encoding="utf-8") as f:
f.write(jsonData)
except Exception as e:
- logging.error("Will not load or save because: " + e.__repr__())
+ logger.error:("Will not load or save because: " + e.__repr__())
return self.absoluteJsonFilePath
def stopSession(self):
"""This got registered with atexit in the nsm new or open callback above.
will handle all python exceptions, but not segfaults of C modules. """
- logging.info("Starting Quit through @atexit, session.stopSession")
+ logger.info:("Starting Quit through @atexit, session.stopSession")
self.eventLoop.stop()
- logging.info("@atexit: Event loop stopped")
+ logger.info:("@atexit: Event loop stopped")
#Don't do that. We are just a client.
#cbox.Transport.stop()
- #logging.info("@atexit: Calfbox Transport stopped ")
+ #logger.info:("@atexit: Calfbox Transport stopped ")
cbox.stop_audio()
- logging.info("@atexit: Calfbox Audio stopped ")
+ logger.info:("@atexit: Calfbox Audio stopped ")
cbox.shutdown_engine()
- logging.info("@atexit: Calfbox Engine shutdown ")
+ logger.info:("@atexit: Calfbox Engine shutdown ")
diff --git a/template/qtgui/about.py b/template/qtgui/about.py
index 4958164..91b6603 100644
--- a/template/qtgui/about.py
+++ b/template/qtgui/about.py
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Lib
from random import choice
diff --git a/template/qtgui/chooseSessionDirectory.py b/template/qtgui/chooseSessionDirectory.py
index 9bbe377..578751e 100644
--- a/template/qtgui/chooseSessionDirectory.py
+++ b/template/qtgui/chooseSessionDirectory.py
@@ -20,7 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Standard Lib
from tempfile import gettempdir
@@ -44,7 +45,7 @@ class ChooseSessionDirectory(QtWidgets.QDialog):
def __init__(self, qtApp):
language = QtCore.QLocale().languageToString(QtCore.QLocale().language())
- logging.info("{}: Language set to {}".format(METADATA["name"], language))
+ logger.info:("{}: Language set to {}".format(METADATA["name"], language))
if language in METADATA["supportedLanguages"]:
templateTranslator = QtCore.QTranslator()
templateTranslator.load(METADATA["supportedLanguages"][language], ":/template/translations/") #colon to make it a resource URL
diff --git a/template/qtgui/constantsAndConfigs.py b/template/qtgui/constantsAndConfigs.py
index ccadec5..78b92a8 100644
--- a/template/qtgui/constantsAndConfigs.py
+++ b/template/qtgui/constantsAndConfigs.py
@@ -20,7 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Third Party Modules
from PyQt5 import QtWidgets, QtCore, QtGui
diff --git a/template/qtgui/debugScript.py b/template/qtgui/debugScript.py
index c20418e..bab62ed 100644
--- a/template/qtgui/debugScript.py
+++ b/template/qtgui/debugScript.py
@@ -20,6 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
+
import os.path
#from code import InteractiveInterpreter
import logging
@@ -41,7 +44,7 @@ class DebugScriptRunner(object):
def _createEmptyDebugScript(self):
assert self.absoluteScriptFilePath
- logging.info(f"{self.nsmClient.ourClientNameUnderNSM}: Script file not found. Initializing: {self.absoluteScriptFilePath}")
+ logger.info:(f"{self.nsmClient.ourClientNameUnderNSM}: Script file not found. Initializing: {self.absoluteScriptFilePath}")
text = ("""#! /usr/bin/env python3"""
"\n"
"""# -*- coding: utf-8 -*-"""
diff --git a/template/qtgui/mainwindow.py b/template/qtgui/mainwindow.py
index 2b614fa..3ba9601 100644
--- a/template/qtgui/mainwindow.py
+++ b/template/qtgui/mainwindow.py
@@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#Standard Library
import os
@@ -95,15 +95,15 @@ class EventLoop(object):
def start(self):
"""The event loop MUST be started after the Qt Application instance creation"""
- logging.info("Starting fast qt event loop")
+ logger.info:("Starting fast qt event loop")
self.fastLoop.start(20)
- logging.info("Starting slow qt event loop")
+ logger.info:("Starting slow qt event loop")
self.slowLoop.start(100)
def stop(self):
- logging.info("Stopping fast qt event loop")
+ logger.info:("Stopping fast qt event loop")
self.fastLoop.stop()
- logging.info("Stopping slow qt event loop")
+ logger.info:("Stopping slow qt event loop")
self.slowLoop.stop()
api.session.eventLoop = EventLoop()
@@ -113,7 +113,7 @@ api.session.eventLoop = EventLoop()
#Setup the translator before classes are set up. Otherwise we can't use non-template translation.
#to test use LANGUAGE=de_DE.UTF-8 . not LANG=
language = QtCore.QLocale().languageToString(QtCore.QLocale().language())
-logging.info("{}: Language set to {}".format(METADATA["name"], language))
+logger.info:("{}: Language set to {}".format(METADATA["name"], language))
if language in METADATA["supportedLanguages"]:
templateTranslator = QtCore.QTranslator()
templateTranslator.load(METADATA["supportedLanguages"][language], ":/template/translations/") #colon to make it a resource URL
@@ -355,13 +355,13 @@ class MainWindow(QtWidgets.QMainWindow):
#Close and exit
def _nsmQuit(self, ourPath, sessionName, ourClientNameUnderNSM):
- logging.info("Qt main window received NSM exit callback. Calling pythons system exit. ")
+ logger.info:("Qt main window received NSM exit callback. Calling pythons system exit. ")
self.storeWindowSettings()
#api.stopEngine() #will be called trough sessions atexit
#self.qtApp.quit() #does not work. This will fail and pynsmclient2 will send SIGKILL
sysexit() #works, NSM cleanly detects a quit. Triggers the session atexit condition
- logging.error("Code executed after sysexit. This message should not have been visible.")
+ logger.error:("Code executed after sysexit. This message should not have been visible.")
#Code here never gets executed.
def closeEvent(self, event):
diff --git a/template/qtgui/menu.py b/template/qtgui/menu.py
index 0201cc9..1bd9466 100644
--- a/template/qtgui/menu.py
+++ b/template/qtgui/menu.py
@@ -20,7 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
#Standard Library
diff --git a/template/qtgui/nsmclient.py b/template/qtgui/nsmclient.py
index 60dcbe8..bf52e95 100644
--- a/template/qtgui/nsmclient.py
+++ b/template/qtgui/nsmclient.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
-PyNSMClient 2.1 - A Non Session Manager Client-Library in one file.
+PyNSMClient - A Non Session Manager Client-Library in one file.
The Non-Session-Manager by Jonathan Moore Liles : http://non.tuxfamily.org/nsm/
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )
@@ -28,6 +28,8 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
+import logging;
+logger = None #filled by init with prettyName
import struct
import socket
@@ -37,7 +39,6 @@ import os.path
import shutil
from uuid import uuid4
from sys import argv
-import logging
from signal import signal, SIGTERM, SIGINT, SIGKILL #react to exit signals to close the client gracefully. Or kill if the client fails to do so.
from urllib.parse import urlparse
@@ -173,13 +174,13 @@ class _IncomingMessage(object):
elif param == "s": # String.
val, index = self.get_string(self._dgram, index)
else:
- logging.warning("pynsm2: Unhandled parameter type: {0}".format(param))
+ logger.warning("Unhandled parameter type: {0}".format(param))
continue
self._parameters.append(val)
except ValueError as pe:
#raise ValueError('Found incorrect datagram, ignoring it', pe)
# Raising an error is not ignoring it!
- logging.warning("pynsm2: Found incorrect datagram, ignoring it. {}".format(pe))
+ logger.warning("Found incorrect datagram, ignoring it. {}".format(pe))
@property
def oscpath(self):
@@ -259,18 +260,21 @@ class NSMClient(object):
Does not run an event loop itself and depends on the host loop.
E.g. a Qt timer or just a simple while True: sleep(0.1) in Python."""
- def __init__(self, prettyName, supportsSaveStatus, saveCallback, openOrNewCallback, exitProgramCallback, hideGUICallback=None, showGUICallback=None, broadcastCallback=None, loggingLevel = "info"):
+ def __init__(self, prettyName, supportsSaveStatus, saveCallback, openOrNewCallback, exitProgramCallback, hideGUICallback=None, showGUICallback=None, broadcastCallback=None, sessionIsLoadedCallback=None, loggingLevel = "info"):
self.nsmOSCUrl = self.getNsmOSCUrl() #this fails and raises NSMNotRunningError if NSM is not available. Host programs can ignore it or exit their program.
self.realClient = True
- self.cachedSaveStatus = True #save status checks for this.
+ self.cachedSaveStatus = None #save status checks for this.
+ global logger
+ logger = logging.getLogger(prettyName)
+ logger.info("import")
if loggingLevel == "info" or loggingLevel == 20:
- logging.getLogger().setLevel(logging.INFO) #development
- logging.info(prettyName + ":pynsm2: Starting PyNSM2 Client with logging level INFO. Switch to 'error' for a release!") #the NSM name is not ready yet so we just use the pretty name
+ logging.basicConfig(level=logging.INFO) #development
+ logger.info("Starting PyNSM2 Client with logging level INFO. Switch to 'error' for a release!") #the NSM name is not ready yet so we just use the pretty name
elif loggingLevel == "error" or loggingLevel == 40:
- logging.getLogger().setLevel(logging.ERROR) #production
+ logging.basicConfig(level=logging.ERROR) #production
else:
raise ValueError("Unknown logging level: {}. Choose 'info' or 'error'".format(loggingLevel))
@@ -281,23 +285,30 @@ class NSMClient(object):
self.saveCallback = saveCallback
self.exitProgramCallback = exitProgramCallback
self.openOrNewCallback = openOrNewCallback #The host needs to: Create a jack client with ourClientNameUnderNSM - Open the saved file and all its resources
- self.broadcastCallback = broadcastCallback if broadcastCallback else None
- self.hideGUICallback = hideGUICallback if hideGUICallback else None #if this stays None we don't ever need to check for it. This function will never be called by NSM anyway.
- self.showGUICallback = showGUICallback if showGUICallback else None #if this stays None we don't ever need to check for it. This function will never be called by NSM anyway.
+ self.broadcastCallback = broadcastCallback
+ self.hideGUICallback = hideGUICallback
+ self.showGUICallback = showGUICallback
+ self.sessionIsLoadedCallback = sessionIsLoadedCallback
+ #Reactions get the raw _IncomingMessage OSC object
+ #A client can add to reactions.
self.reactions = {
"/nsm/client/save" : self._saveCallback,
- "/nsm/client/show_optional_gui" : self.showGUICallback,
- "/nsm/client/hide_optional_gui" : self.hideGUICallback,
+ "/nsm/client/show_optional_gui" : lambda msg: self.showGUICallback(),
+ "/nsm/client/hide_optional_gui" : lambda msg: self.hideGUICallback(),
+ "/nsm/client/session_is_loaded" : self._sessionIsLoadedCallback,
+ #Hello source-code reader. You can add your own reactions here by nsmClient.reactions[oscpath]=func, where func gets the raw _IncomingMessage OSC object as argument.
#broadcast is handled directly by the function because it has more parameters
}
- self.discardReactions = set(["/nsm/client/session_is_loaded"])
+ #self.discardReactions = set(["/nsm/client/session_is_loaded"])
+ self.discardReactions = set()
#Networking and Init
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp
self.sock.bind(('', 0)) #pick a free port on localhost.
-
+ ip, port = self.sock.getsockname()
+ self.ourOscUrl = f"osc.udp://{ip}:{port}/"
self.executableName = self.getExecutableName()
@@ -322,6 +333,47 @@ class NSMClient(object):
self.sock.setblocking(False) #We have waited for tha handshake. Now switch blocking off because we expect sock.recvfrom to be empty in 99.99...% of the time so we shouldn't wait for the answer.
#After this point the host must include self.reactToMessage in its event loop
+
+ def reactToMessage(self):
+ """This is the main loop message. It is added to the clients event loop."""
+ try:
+ data, addr = self.sock.recvfrom(4096) #4096 is quite big. We don't expect nsm messages this big. Better safe than sorry. However, messages will crash the program if they are bigger than 4096.
+ except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not.
+ return None
+
+ msg = _IncomingMessage(data)
+ if msg.oscpath in self.reactions:
+ self.reactions[msg.oscpath](msg)
+ elif msg.oscpath in self.discardReactions:
+ pass
+ elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/open", "Loaded."]: #NSM sends that all programs of the session were loaded.
+ logger.info ("Got /reply Loaded from NSM Server")
+ elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/save", "Saved."]: #NSM sends that all program-states are saved. Does only happen from the general save instruction, not when saving our client individually
+ logger.info ("Got /reply Saved from NSM Server")
+ elif msg.isBroadcast:
+ if self.broadcastCallback:
+ logger.info (f"Got broadcast with messagePath {msg.oscpath} and listOfArguments {msg.params}")
+ self.broadcastCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM, msg.oscpath, msg.params)
+ else:
+ logger.info (f"No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}")
+ elif msg.oscpath == "/error":
+ logger.warning("Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
+ else:
+ logger.warning("Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
+
+
+ def send(self, path:str, listOfParameters:list, host=None, port=None):
+ """Send any osc message. Defaults to nsmd URL.
+ Will not wait for an answer but return None."""
+ if host and port:
+ url = (host, port)
+ else:
+ url = self.nsmOSCUrl
+ msg = _OutgoingMessage(path)
+ for arg in listOfParameters:
+ msg.add_arg(arg) #type is auto-determined by outgoing message
+ self.sock.sendto(msg.build(), url)
+
def getNsmOSCUrl(self):
"""Return and save the nsm osc url or raise an error"""
nsmOSCUrl = getenv("NSM_URL")
@@ -381,13 +433,13 @@ class NSMClient(object):
if msg.oscpath == "/error":
originalMessage, errorCode, reason = msg.params
- logging.error("Code {}: {}".format(errorCode, reason))
+ logger.error("Code {}: {}".format(errorCode, reason))
quit()
elif msg.oscpath == "/reply":
nsmAnnouncePath, welcomeMessage, managerName, self.serverFeatures = msg.params
assert nsmAnnouncePath == "/nsm/server/announce", nsmAnnouncePath
- logging.info(self.prettyName + ":pynsm2: Got /reply " + welcomeMessage)
+ logger.info("Got /reply " + welcomeMessage)
#Wait for /nsm/client/open
data, addr = self.sock.recvfrom(1024)
@@ -395,9 +447,9 @@ class NSMClient(object):
assert msg.oscpath == "/nsm/client/open", msg.oscpath
self.ourPath, self.sessionName, self.ourClientNameUnderNSM = msg.params
self.ourClientId = os.path.splitext(self.ourClientNameUnderNSM)[1][1:]
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Got '/nsm/client/open' from NSM. Telling our client to load or create a file with name {}".format(self.ourPath))
+ logger.info("Got '/nsm/client/open' from NSM. Telling our client to load or create a file with name {}".format(self.ourPath))
self.openOrNewCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) #Host function to either load an existing session or create a new one.
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Our client should be done loading or creating the file {}".format(self.ourPath))
+ logger.info("Our client should be done loading or creating the file {}".format(self.ourPath))
replyToOpen = _OutgoingMessage("/reply")
replyToOpen.add_arg("/nsm/client/open")
replyToOpen.add_arg("{} is opened or created".format(self.prettyName))
@@ -409,7 +461,7 @@ class NSMClient(object):
message = "/nsm/client/gui_is_shown" if isVisible else "/nsm/client/gui_is_hidden"
self.isVisible = isVisible
guiVisibility = _OutgoingMessage(message)
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Telling NSM that our clients switched GUI visibility to: {}".format(message))
+ logger.info("Telling NSM that our clients switched GUI visibility to: {}".format(message))
self.sock.sendto(guiVisibility.build(), self.nsmOSCUrl)
def announceSaveStatus(self, isClean):
@@ -418,11 +470,11 @@ class NSMClient(object):
message = "/nsm/client/is_clean" if isClean else "/nsm/client/is_dirty"
self.cachedSaveStatus = isClean
saveStatus = _OutgoingMessage(message)
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Telling NSM that our clients save state is now: {}".format(message))
+ logger.info("Telling NSM that our clients save state is now: {}".format(message))
self.sock.sendto(saveStatus.build(), self.nsmOSCUrl)
- def _saveCallback(self):
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Telling our client to save as {}".format(self.ourPath))
+ def _saveCallback(self, msg):
+ logger.info("Telling our client to save as {}".format(self.ourPath))
self.saveCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM)
replyToSave = _OutgoingMessage("/reply")
replyToSave.add_arg("/nsm/client/save")
@@ -431,31 +483,11 @@ class NSMClient(object):
#it is assumed that after saving the state is clear
self.announceSaveStatus(isClean = True)
- def reactToMessage(self):
- try:
- data, addr = self.sock.recvfrom(4096) #4096 is quite big. We don't expect nsm messages this big. Better safe than sorry. See next lines comment
- except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not.
- return None
- msg = _IncomingMessage(data) #However, messages will crash the program if they are bigger than 4096.
- if msg.oscpath in self.reactions:
- self.reactions[msg.oscpath]()
- elif msg.oscpath in self.discardReactions:
- pass
- elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/open", "Loaded."]: #NSM sends that all programs of the session were loaded.
- logging.info (self.ourClientNameUnderNSM + ":pynsm2: Got /reply Loaded from NSM Server")
- elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/save", "Saved."]: #NSM sends that all program-states are saved. Does only happen from the general save instruction, not when saving our client individually
- logging.info (self.ourClientNameUnderNSM + ":pynsm2: Got /reply Saved from NSM Server")
- elif msg.isBroadcast:
- if self.broadcastCallback:
- logging.info (self.ourClientNameUnderNSM + f":pynsm2: Got broadcast with messagePath {msg.oscpath} and listOfArguments {msg.params}")
- self.broadcastCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM, msg.oscpath, msg.params)
- else:
- logging.info (self.ourClientNameUnderNSM + f":pynsm2: No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}")
- elif msg.oscpath == "/error":
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
- else:
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
+ def _sessionIsLoadedCallback(self, msg):
+ if self.sessionIsLoadedCallback:
+ logger.info("Telling our client that the session has finished loading")
+ self.sessionIsLoadedCallback()
def sigtermHandler(self, signal, frame):
"""Wait for the user to quit the program
@@ -471,33 +503,33 @@ class NSMClient(object):
gdb --args python foo.py
the Python signal handler will not work. This has nothing to do with this library.
"""
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Telling our client to quit.")
+ logger.info("Telling our client to quit.")
self.exitProgramCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM)
#There is a chance that exitProgramCallback will hang and the program won't quit. However, this is broken design and bad programming. We COULD place a timeout here and just kill after 10s or so, but that would make quitting our responsibility and fixing a broken thing.
#If we reach this point we have reached the point of no return. Say goodbye.
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: Client did not quit on its own. Sending SIGKILL.")
+ logger.warning("Client did not quit on its own. Sending SIGKILL.")
kill(getpid(), SIGKILL)
- logging.error(self.ourClientNameUnderNSM + ":pynsm2: pynsm2: SIGKILL did nothing. Do it manually.")
+ logger.error("SIGKILL did nothing. Do it manually.")
def debugResetDataAndExit(self):
"""This is solely meant for debugging and testing. The user way of action should be to
remove the client from the session and add a new instance, which will get a different
NSM-ID.
Afterwards we perform a clean exit."""
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: debugResetDataAndExit will now delete {} and then request an exit.".format(self.ourPath))
+ logger.warning("debugResetDataAndExit will now delete {} and then request an exit.".format(self.ourPath))
if os.path.exists(self.ourPath):
if os.path.isfile(self.ourPath):
try:
os.remove(self.ourPath)
except Exception as e:
- logging.info(e)
+ logger.info(e)
elif os.path.isdir(self.ourPath):
try:
shutil.rmtree(self.ourPath)
except Exception as e:
- logging.info(e)
+ logger.info(e)
else:
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: {} does not exist.".format(self.ourPath))
+ logger.info("{} does not exist.".format(self.ourPath))
self.serverSendExitToSelf()
def serverSendExitToSelf(self):
@@ -508,13 +540,13 @@ class NSMClient(object):
Using this method will not result in a NSM-"client died unexpectedly" message that usually
happens a client quits on its own. This message is harmless but may confuse a user."""
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: instructing the NSM-Server to send SIGTERM to ourselves.")
+ logger.info("instructing the NSM-Server to send SIGTERM to ourselves.")
if "server-control" in self.serverFeatures:
message = _OutgoingMessage("/nsm/server/stop")
message.add_arg("{}".format(self.ourClientId))
self.sock.sendto(message.build(), self.nsmOSCUrl)
else:
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: ...but the NSM-Server does not support server control. Quitting on our own. Server only supports: {}".format(self.serverFeatures))
+ logger.warning("...but the NSM-Server does not support server control. Quitting on our own. Server only supports: {}".format(self.serverFeatures))
kill(getpid(), SIGTERM) #this calls the exit callback but nsm will output something like "client died unexpectedly."
def serverSendSaveToSelf(self):
@@ -523,14 +555,14 @@ class NSMClient(object):
NSM server so our client thinks it received a Save instruction. This leads to a clean
state with a good saveStatus and no required extra functionality in the client."""
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: instructing the NSM-Server to send Save to ourselves.")
+ logger.info("instructing the NSM-Server to send Save to ourselves.")
if "server-control" in self.serverFeatures:
#message = _OutgoingMessage("/nsm/server/save") # "Save All" Command.
message = _OutgoingMessage("/nsm/gui/client/save")
message.add_arg("{}".format(self.ourClientId))
self.sock.sendto(message.build(), self.nsmOSCUrl)
else:
- logging.warning(self.ourClientNameUnderNSM + ":pynsm2: ...but the NSM-Server does not support server control. Server only supports: {}".format(self.serverFeatures))
+ logger.warning("...but the NSM-Server does not support server control. Server only supports: {}".format(self.serverFeatures))
def changeLabel(self, label:str):
"""This function is implemented because it is provided by NSM. However, it does not much.
@@ -539,7 +571,7 @@ class NSMClient(object):
We would have to send it every startup ourselves.
This is fine for us as clients, but you need to provide a GUI field to enter that label."""
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Telling the NSM-Server that our label is now " + label)
+ logger.info("Telling the NSM-Server that our label is now " + label)
message = _OutgoingMessage("/nsm/client/label")
message.add_arg(label) #s:label
self.sock.sendto(message.build(), self.nsmOSCUrl)
@@ -547,13 +579,18 @@ class NSMClient(object):
def broadcast(self, path:str, arguments:list):
"""/nsm/server/broadcast s:path [arguments...]
We, as sender, will not receive the broadcast back.
+
+ Broadcasts starting with /nsm are not allowed and will get discarded by the server
"""
- logging.info(self.ourClientNameUnderNSM + ":pynsm2: Sending broadcast " + path + repr(arguments))
- message = _OutgoingMessage("/nsm/server/broadcast")
- message.add_arg(path)
- for arg in arguments:
- message.add_arg(arg) #type autodetect
- self.sock.sendto(message.build(), self.nsmOSCUrl)
+ if path.startswith("/nsm"):
+ logger.warning("Attempted broadbast starting with /nsm. Not allwoed")
+ else:
+ logger.info("Sending broadcast " + path + repr(arguments))
+ message = _OutgoingMessage("/nsm/server/broadcast")
+ message.add_arg(path)
+ for arg in arguments:
+ message.add_arg(arg) #type autodetect
+ self.sock.sendto(message.build(), self.nsmOSCUrl)
def importResource(self, filePath):
"""aka. import into session
@@ -614,14 +651,14 @@ class NSMClient(object):
if filePathInOurSession:
#loadResource from our session dir. Portable session, manually copied beforehand or just loading a link again.
linkedPath = filePath #we could return here, but we continue to get the tests below.
- logging.info(self.ourClientNameUnderNSM + f":pynsm2: tried to import external resource {filePath} but this is already in our session directory. We use this file directly instead. ")
+ logger.info(f"tried to import external resource {filePath} but this is already in our session directory. We use this file directly instead. ")
elif linkedPathAlreadyExists and os.readlink(linkedPath) == filePath:
#the imported file already exists as link in our session dir. We do not link it again but simply report the existing link.
#We only check for the first target of the existing link and do not follow it through to a real file.
#This way all user abstractions and file structures will be honored.
linkedPath = linkedPath
- logging.info(self.ourClientNameUnderNSM + f":pynsm2: tried to import external resource {filePath} but this was already linked to our session directory before. We use the old link: {linkedPath} ")
+ logger.info(f"tried to import external resource {filePath} but this was already linked to our session directory before. We use the old link: {linkedPath} ")
elif linkedPathAlreadyExists:
#A new file shall be imported but it would create a linked name which already exists in our session dir.
@@ -630,13 +667,13 @@ class NSMClient(object):
uniqueLinkedPath = firstpart + "." + uuid4().hex + extension
assert not os.path.exists(uniqueLinkedPath)
os.symlink(filePath, uniqueLinkedPath)
- logging.info(self.ourClientNameUnderNSM + f":pysm2: tried to import external resource {filePath} but potential target link {linkedPath} already exists. Linked to {uniqueLinkedPath} instead.")
+ logger.info(self.ourClientNameUnderNSM + f":pysm2: tried to import external resource {filePath} but potential target link {linkedPath} already exists. Linked to {uniqueLinkedPath} instead.")
linkedPath = uniqueLinkedPath
else: #this is the "normal" case. External resources will be linked.
assert not os.path.exists(linkedPath)
os.symlink(filePath, linkedPath)
- logging.info(self.ourClientNameUnderNSM + f":pynsm2: imported external resource {filePath} as link {linkedPath}")
+ logger.info(f"imported external resource {filePath} as link {linkedPath}")
assert os.path.exists(linkedPath), linkedPath
return linkedPath
diff --git a/template/qtgui/nsmsingleserver.py b/template/qtgui/nsmsingleserver.py
index f68547c..0ae3632 100644
--- a/template/qtgui/nsmsingleserver.py
+++ b/template/qtgui/nsmsingleserver.py
@@ -20,7 +20,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
import os, socket, asyncio
from signal import signal, SIGTERM, SIGUSR1
from threading import Thread
@@ -105,7 +106,7 @@ def startSingleNSMServer(directory):
#loop.run_forever()
#asyncio.run(asyncio.start_server(handle_client, 'localhost', SERVER_PORT))
- logging.info(f"Starting fake NSM server on port {SERVER_PORT}")
+ logger.info:(f"Starting fake NSM server on port {SERVER_PORT}")
#For Carla:
signal(SIGUSR1, NSMProtocol.staticSave)
diff --git a/template/qtgui/submenus.py b/template/qtgui/submenus.py
index 8469864..70fb1f5 100644
--- a/template/qtgui/submenus.py
+++ b/template/qtgui/submenus.py
@@ -20,6 +20,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+import logging; logger = logging.getLogger(__name__); logger.info("import")
+
+
from typing import Iterable, Callable, Tuple
from PyQt5 import QtCore, QtGui, QtWidgets
import engine.api as api
diff --git a/template/qtgui/usermanual.py b/template/qtgui/usermanual.py
index c7ef498..ba821ca 100644
--- a/template/qtgui/usermanual.py
+++ b/template/qtgui/usermanual.py
@@ -20,8 +20,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
-import logging; logging.info("import {}".format(__file__))
-
+import logging; logger = logging.getLogger(__name__); logger.info("import")
#System Wide Modules
from PyQt5 import QtCore, QtWidgets, QtGui
diff --git a/template/start.py b/template/start.py
index 9d6f453..e23656e 100644
--- a/template/start.py
+++ b/template/start.py
@@ -47,11 +47,15 @@ args = parser.parse_args()
import logging
if args.verbose:
- logging.getLogger().setLevel(logging.INFO) #development
+ logging.basicConfig(level=logging.INFO) #development
+ #logging.getLogger().setLevel(logging.INFO) #development
else:
- logging.getLogger().setLevel(logging.ERROR) #production
+ logging.basicConfig(level=logging.ERROR) #production
+ #logging.getLogger().setLevel(logging.ERROR) #production
+
+logger = logging.getLogger(__name__)
+logger.info("import")
-logging.info("import {}".format(__file__))
"""set up python search path before the program starts and cbox gets imported.
We need to be earliest, so let's put it here.
@@ -67,11 +71,11 @@ import os.path
try:
from compiledprefix import prefix
compiledVersion = True
- logging.info("Compiled prefix found: {}".format(prefix))
+ logger.info:("Compiled prefix found: {}".format(prefix))
except ModuleNotFoundError as e:
compiledVersion = False
-logging.info("Compiled version: {}".format(compiledVersion))
+logger.info:("Compiled version: {}".format(compiledVersion))
cboxSharedObjectVersionedName = "lib"+METADATA["shortName"]+".so." + METADATA["version"]
@@ -122,12 +126,12 @@ else:
if os.path.exists (os.path.join(_root, "site-packages", "calfbox", "cbox.py")):
#add to the front to have higher priority than system site-packages
- logging.info("Will attempt to start with local calfbox python module: {}".format(os.path.join(_root, "site-packages", "calfbox", "cbox.py")))
+ logger.info:("Will attempt to start with local calfbox python module: {}".format(os.path.join(_root, "site-packages", "calfbox", "cbox.py")))
sys.path.insert(0, os.path.join(os.path.join(_root, "site-packages")))
#else try to use system-wide calfbox. Check for this and if the .so exists at the end of this file.
-logging.info("PATHS: {}".format(PATHS))
+logger.info:("PATHS: {}".format(PATHS))
def exitWithMessage(message:str):
@@ -231,7 +235,7 @@ def profiler(*pargs, **kwds):
from tempfile import NamedTemporaryFile
cprofPath = NamedTemporaryFile().name + ".cprof"
pr.dump_stats(cprofPath)
- logging.info("{}: write profiling data to {}".format(METADATA["name"], cprofPath))
+ logger.info:("{}: write profiling data to {}".format(METADATA["name"], cprofPath))
print (f"pyprof2calltree -k -i {cprofPath}")
pr = cProfile.Profile()
@@ -243,6 +247,18 @@ def profiler(*pargs, **kwds):
#Program execution
yield
+#Catch Exceptions even if PyQt crashes.
+import sys
+sys._excepthook = sys.excepthook
+def exception_hook(exctype, value, traceback):
+ """This hook purely exists to call sys.exit(1) even on a Qt crash
+ so that atexit gets triggered"""
+ #print(exctype, value, traceback)
+ logger.error("Caught crash in execpthook. Trying too execute atexit anyway")
+ sys._excepthook(exctype, value, traceback)
+ sys.exit(1)
+sys.excepthook = exception_hook
+
def startPseudoNSMServer(path):
from os import getenv
@@ -288,13 +304,13 @@ if args.mute:
#Make sure calfbox is available.
if "CALFBOXLIBABSPATH" in os.environ:
- logging.info("Looking for calfbox shared library in absolute path: {}".format(os.environ["CALFBOXLIBABSPATH"]))
+ logger.info:("Looking for calfbox shared library in absolute path: {}".format(os.environ["CALFBOXLIBABSPATH"]))
else:
- logging.info("Looking for calfbox shared library systemwide through ctypes.util.find_library")
+ logger.info:("Looking for calfbox shared library systemwide through ctypes.util.find_library")
try:
from calfbox import cbox
- logging.info("{}: using cbox python module from {} . Local version has higher priority than system wide.".format(METADATA["name"], os.path.abspath(cbox.__file__)))
+ logger.info:("{}: using cbox python module from {} . Local version has higher priority than system wide.".format(METADATA["name"], os.path.abspath(cbox.__file__)))
except Exception as e:
print (e)