Browse Source

update template

master
Nils 4 years ago
parent
commit
36b1b28890
  1. 2
      template/engine/duration.py
  2. 4
      template/engine/sequencer.py
  3. 4
      template/engine/session.py
  4. 2
      template/qtgui/mainwindow.py
  5. 127
      template/qtgui/nsmclient.py

2
template/engine/duration.py

@ -48,7 +48,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. 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: if not int(D1024) == D1024:
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. ") logger.error(f"Warning: Lowest duration D0124 has decimal places: {D1024} but should be a plain integer. Your D4 value: {D4} has not enough 2^n factors. ")
D2 = D4 *2 D2 = D4 *2
D1 = D2 *2 D1 = D2 *2

4
template/engine/sequencer.py

@ -272,7 +272,7 @@ class _Subtrack(object):
cbox.Document.get_song().update_playback() cbox.Document.get_song().update_playback()
def recreateThroughUndo(self): def recreateThroughUndo(self):
assert self.calfboxSubTrack is None, calfboxSubTrack assert self.calfboxSubTrack is None, self.calfboxSubTrack
self.calfboxSubTrack = cbox.Document.get_song().add_track() self.calfboxSubTrack = cbox.Document.get_song().add_track()
for pattern, position, length in self._cachedPatterns: for pattern, position, length in self._cachedPatterns:
self.calfboxSubTrack.add_clip(position, 0, length, pattern) #pos, offset, length, pattern. self.calfboxSubTrack.add_clip(position, 0, length, pattern) #pos, offset, length, pattern.
@ -378,7 +378,7 @@ class SequencerInterface(_Interface): #Basically the midi part of a track.
in the past which deleted the midi output but not the cbox-midi in the past which deleted the midi output but not the cbox-midi
data it generated and held""" data it generated and held"""
#Recreate Calfbox Midi Data #Recreate Calfbox Midi Data
assert self.calfboxTrack is None, calfboxTrack assert self.calfboxTrack is None, self.calfboxTrack
self.calfboxTrack = cbox.Document.get_song().add_track() self.calfboxTrack = cbox.Document.get_song().add_track()
self.calfboxTrack.set_name(self.name) #only cosmetic and cbox internal. Useful for debugging, not used in jack. self.calfboxTrack.set_name(self.name) #only cosmetic and cbox internal. Useful for debugging, not used in jack.
for pattern, position, length in self._cachedPatterns: for pattern, position, length in self._cachedPatterns:

4
template/engine/session.py

@ -133,10 +133,10 @@ class Session(object):
logger.info("Loading file complete") logger.info("Loading file complete")
return Data.instanceFromSerializedData(parentSession=self, serializedData=result) return Data.instanceFromSerializedData(parentSession=self, serializedData=result)
else: else:
warn(f"""{absoluteJsonFilePath} was saved with {result["version"]} but we need {METADATA["version"]}""") logger.warning(f"""{absoluteJsonFilePath} was saved with {result["version"]} but we need {METADATA["version"]}""")
sysexit() sysexit()
else: else:
warn(f"""Error. {absoluteJsonFilePath} not loaded. Not a sane {METADATA["name"]} file in json format""") logger.warning(f"""Error. {absoluteJsonFilePath} not loaded. Not a sane {METADATA["name"]} file in json format""")
sysexit() sysexit()

2
template/qtgui/mainwindow.py

@ -217,7 +217,7 @@ class MainWindow(QtWidgets.QMainWindow):
#Decide here if you want only files, only directories, both etc. #Decide here if you want only files, only directories, both etc.
if os.path.isfile(filePath) and filePath.lower().endswith(".sf2"): if os.path.isfile(filePath) and filePath.lower().endswith(".sf2"):
#linkedPath = self.nsmClient.importResource(filePath) #linkedPath = self.nsmClient.importResource(filePath)
print ("received drop", linkedPath) print ("received drop")

127
template/qtgui/nsmclient.py

@ -12,24 +12,24 @@ MIT License
Copyright 2014-2020 Nils Hilbricht https://www.laborejo.org Copyright 2014-2020 Nils Hilbricht https://www.laborejo.org
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software. substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
import logging; import logging;
logger = None #filled by init with prettyName logger = None #filled by init with prettyName
import struct import struct
import socket import socket
@ -51,9 +51,9 @@ class _IncomingMessage(object):
def __init__(self, dgram): def __init__(self, dgram):
#NSM Broadcasts are bundles, but very simple ones. We only need to care about the single message it contains. #NSM Broadcasts are bundles, but very simple ones. We only need to care about the single message it contains.
#Therefore we can strip the bundle prefix and handle it as normal message. #Therefore we can strip the bundle prefix and handle it as normal message.
if b"#bundle" in dgram: if b"#bundle" in dgram:
bundlePrefix, singleMessage = dgram.split(b"/", maxsplit=1) bundlePrefix, singleMessage = dgram.split(b"/", maxsplit=1)
dgram = b"/" + singleMessage # / eaten by split dgram = b"/" + singleMessage # / eaten by split
self.isBroadcast = True self.isBroadcast = True
else: else:
@ -108,13 +108,13 @@ class _IncomingMessage(object):
ValueError if the datagram could not be parsed. ValueError if the datagram could not be parsed.
""" """
#First test for empty string, which is nothing, followed by a terminating \x00 padded by three additional \x00. #First test for empty string, which is nothing, followed by a terminating \x00 padded by three additional \x00.
if dgram[start_index:].startswith(b"\x00\x00\x00\x00"): if dgram[start_index:].startswith(b"\x00\x00\x00\x00"):
return "", start_index + 4 return "", start_index + 4
#Otherwise we have a non-empty string that must follow the rules of the docstring. #Otherwise we have a non-empty string that must follow the rules of the docstring.
offset = 0 offset = 0
try: try:
while dgram[start_index + offset] != 0: while dgram[start_index + offset] != 0:
offset += 1 offset += 1
if offset == 0: if offset == 0:
@ -128,7 +128,7 @@ class _IncomingMessage(object):
# do it ourselves. # do it ourselves.
if offset > len(dgram[start_index:]): if offset > len(dgram[start_index:]):
raise ValueError('Datagram is too short') raise ValueError('Datagram is too short')
data_str = dgram[start_index:start_index + offset] data_str = dgram[start_index:start_index + offset]
return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset
except IndexError as ie: except IndexError as ie:
raise ValueError('Could not parse datagram %s' % ie) raise ValueError('Could not parse datagram %s' % ie)
@ -155,7 +155,7 @@ class _IncomingMessage(object):
def parse_datagram(self): def parse_datagram(self):
try: try:
self._address_regexp, index = self.get_string(self._dgram, 0) self._address_regexp, index = self.get_string(self._dgram, 0)
if not self._dgram[index:]: if not self._dgram[index:]:
# No params is legit, just return now. # No params is legit, just return now.
return return
@ -276,8 +276,8 @@ class NSMClient(object):
elif loggingLevel == "error" or loggingLevel == 40: elif loggingLevel == "error" or loggingLevel == 40:
logging.basicConfig(level=logging.ERROR) #production logging.basicConfig(level=logging.ERROR) #production
else: else:
raise ValueError("Unknown logging level: {}. Choose 'info' or 'error'".format(loggingLevel)) logging.warning("Unknown logging level: {}. Choose 'info' or 'error'".format(loggingLevel))
logging.basicConfig(level=logging.INFO) #development
#given parameters, #given parameters,
self.prettyName = prettyName #keep this consistent! Settle for one name. self.prettyName = prettyName #keep this consistent! Settle for one name.
@ -298,16 +298,15 @@ class NSMClient(object):
"/nsm/client/hide_optional_gui" : lambda msg: self.hideGUICallback(), "/nsm/client/hide_optional_gui" : lambda msg: self.hideGUICallback(),
"/nsm/client/session_is_loaded" : self._sessionIsLoadedCallback, "/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. #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 #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() self.discardReactions = set()
#Networking and Init #Networking and Init
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp
self.sock.bind(('', 0)) #pick a free port on localhost. self.sock.bind(('', 0)) #pick a free port on localhost.
ip, port = self.sock.getsockname() ip, port = self.sock.getsockname()
self.ourOscUrl = f"osc.udp://{ip}:{port}/" self.ourOscUrl = f"osc.udp://{ip}:{port}/"
self.executableName = self.getExecutableName() self.executableName = self.getExecutableName()
@ -324,7 +323,9 @@ class NSMClient(object):
self.ourClientId = None # the "file extension" of ourClientNameUnderNSM self.ourClientId = None # the "file extension" of ourClientNameUnderNSM
self.isVisible = None #set in announceGuiVisibility self.isVisible = None #set in announceGuiVisibility
self.saveStatus = True # true is clean. false means we need saving. self.saveStatus = True # true is clean. false means we need saving.
self.announceOurselves() self.announceOurselves()
assert self.serverFeatures, self.serverFeatures assert self.serverFeatures, self.serverFeatures
assert self.sessionName, self.sessionName assert self.sessionName, self.sessionName
assert self.ourPath, self.ourPath assert self.ourPath, self.ourPath
@ -333,18 +334,20 @@ 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. 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 #After this point the host must include self.reactToMessage in its event loop
#We assume we are save at startup. #We assume we are save at startup.
self.announceSaveStatus(isClean = True) self.announceSaveStatus(isClean = True)
logger.info("NSMClient client init complete. Going into listening mode.")
def reactToMessage(self): def reactToMessage(self):
"""This is the main loop message. It is added to the clients event loop.""" """This is the main loop message. It is added to the clients event loop."""
try: 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. 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. except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not.
return None return None
msg = _IncomingMessage(data) msg = _IncomingMessage(data)
if msg.oscpath in self.reactions: if msg.oscpath in self.reactions:
self.reactions[msg.oscpath](msg) self.reactions[msg.oscpath](msg)
elif msg.oscpath in self.discardReactions: elif msg.oscpath in self.discardReactions:
@ -355,13 +358,13 @@ class NSMClient(object):
logger.info ("Got /reply Saved from NSM Server") logger.info ("Got /reply Saved from NSM Server")
elif msg.isBroadcast: elif msg.isBroadcast:
if self.broadcastCallback: if self.broadcastCallback:
logger.info (f"Got broadcast with messagePath {msg.oscpath} and listOfArguments {msg.params}") 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) self.broadcastCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM, msg.oscpath, msg.params)
else: else:
logger.info (f"No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}") logger.info (f"No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}")
elif msg.oscpath == "/error": elif msg.oscpath == "/error":
logger.warning("Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params)) logger.warning("Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
else: else:
logger.warning("Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params)) logger.warning("Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
@ -371,11 +374,11 @@ class NSMClient(object):
if host and port: if host and port:
url = (host, port) url = (host, port)
else: else:
url = self.nsmOSCUrl url = self.nsmOSCUrl
msg = _OutgoingMessage(path) msg = _OutgoingMessage(path)
for arg in listOfParameters: for arg in listOfParameters:
msg.add_arg(arg) #type is auto-determined by outgoing message msg.add_arg(arg) #type is auto-determined by outgoing message
self.sock.sendto(msg.build(), url) self.sock.sendto(msg.build(), url)
def getNsmOSCUrl(self): def getNsmOSCUrl(self):
"""Return and save the nsm osc url or raise an error""" """Return and save the nsm osc url or raise an error"""
@ -418,28 +421,30 @@ class NSMClient(object):
else: else:
return "" return ""
logger.info("Sending our NSM-announce message")
announce = _OutgoingMessage("/nsm/server/announce") announce = _OutgoingMessage("/nsm/server/announce")
announce.add_arg(self.prettyName) #s:application_name announce.add_arg(self.prettyName) #s:application_name
announce.add_arg(buildClientFeaturesString()) #s:capabilities announce.add_arg(buildClientFeaturesString()) #s:capabilities
announce.add_arg(self.executableName) #s:executable_name announce.add_arg(self.executableName) #s:executable_name
announce.add_arg(1) #i:api_version_major announce.add_arg(1) #i:api_version_major
announce.add_arg(2) #i:api_version_minor announce.add_arg(2) #i:api_version_minor
announce.add_arg(int(getpid())) #i:pid announce.add_arg(int(getpid())) #i:pid
hostname, port = self.nsmOSCUrl hostname, port = self.nsmOSCUrl
assert hostname, self.nsmOSCUrl assert hostname, self.nsmOSCUrl
assert port, self.nsmOSCUrl assert port, self.nsmOSCUrl
self.sock.sendto(announce.build(), self.nsmOSCUrl) self.sock.sendto(announce.build(), self.nsmOSCUrl)
#Wait for /reply (aka 'Howdy, what took you so long?) #Wait for /reply (aka 'Howdy, what took you so long?)
data, addr = self.sock.recvfrom(1024) data, addr = self.sock.recvfrom(1024)
msg = _IncomingMessage(data) msg = _IncomingMessage(data)
if msg.oscpath == "/error": if msg.oscpath == "/error":
originalMessage, errorCode, reason = msg.params originalMessage, errorCode, reason = msg.params
logger.error("Code {}: {}".format(errorCode, reason)) logger.error("Code {}: {}".format(errorCode, reason))
quit() quit()
elif msg.oscpath == "/reply": elif msg.oscpath == "/reply":
nsmAnnouncePath, welcomeMessage, managerName, self.serverFeatures = msg.params nsmAnnouncePath, welcomeMessage, managerName, self.serverFeatures = msg.params
assert nsmAnnouncePath == "/nsm/server/announce", nsmAnnouncePath assert nsmAnnouncePath == "/nsm/server/announce", nsmAnnouncePath
logger.info("Got /reply " + welcomeMessage) logger.info("Got /reply " + welcomeMessage)
@ -458,7 +463,7 @@ class NSMClient(object):
replyToOpen.add_arg("{} is opened or created".format(self.prettyName)) replyToOpen.add_arg("{} is opened or created".format(self.prettyName))
self.sock.sendto(replyToOpen.build(), self.nsmOSCUrl) self.sock.sendto(replyToOpen.build(), self.nsmOSCUrl)
else: else:
raise ValueError("Unexpected message path after announce".format((msg.oscpath, msg.params))) raise ValueError("Unexpected message path after announce: {}".format((msg.oscpath, msg.params)))
def announceGuiVisibility(self, isVisible): def announceGuiVisibility(self, isVisible):
message = "/nsm/client/gui_is_shown" if isVisible else "/nsm/client/gui_is_hidden" message = "/nsm/client/gui_is_shown" if isVisible else "/nsm/client/gui_is_hidden"
@ -479,7 +484,7 @@ class NSMClient(object):
logger.info("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) self.sock.sendto(saveStatus.build(), self.nsmOSCUrl)
def _saveCallback(self, msg): def _saveCallback(self, msg):
logger.info("Telling our client to save as {}".format(self.ourPath)) logger.info("Telling our client to save as {}".format(self.ourPath))
self.saveCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) self.saveCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM)
replyToSave = _OutgoingMessage("/reply") replyToSave = _OutgoingMessage("/reply")
@ -492,8 +497,10 @@ class NSMClient(object):
def _sessionIsLoadedCallback(self, msg): def _sessionIsLoadedCallback(self, msg):
if self.sessionIsLoadedCallback: if self.sessionIsLoadedCallback:
logger.info("Telling our client that the session has finished loading") logger.info("Received 'Session is Loaded'. Our client supports it. Forwarding message...")
self.sessionIsLoadedCallback() self.sessionIsLoadedCallback()
else:
logger.info("Received 'Session is Loaded'. Our client does not support it, which is the default. Discarding message...")
def sigtermHandler(self, signal, frame): def sigtermHandler(self, signal, frame):
"""Wait for the user to quit the program """Wait for the user to quit the program
@ -560,15 +567,15 @@ class NSMClient(object):
We offer a clean solution in calling this function which will trigger a round trip over the We offer a clean solution in calling this function which will trigger a round trip over the
NSM server so our client thinks it received a Save instruction. This leads to a clean 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.""" state with a good saveStatus and no required extra functionality in the client."""
logger.info("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: if "server-control" in self.serverFeatures:
#message = _OutgoingMessage("/nsm/server/save") # "Save All" Command. #message = _OutgoingMessage("/nsm/server/save") # "Save All" Command.
message = _OutgoingMessage("/nsm/gui/client/save") message = _OutgoingMessage("/nsm/gui/client/save")
message.add_arg("{}".format(self.ourClientId)) message.add_arg("{}".format(self.ourClientId))
self.sock.sendto(message.build(), self.nsmOSCUrl) self.sock.sendto(message.build(), self.nsmOSCUrl)
else: else:
logger.warning("...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): def changeLabel(self, label:str):
"""This function is implemented because it is provided by NSM. However, it does not much. """This function is implemented because it is provided by NSM. However, it does not much.
@ -578,9 +585,9 @@ class NSMClient(object):
This is fine for us as clients, but you need to provide a GUI field to enter that label.""" This is fine for us as clients, but you need to provide a GUI field to enter that label."""
logger.info("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 = _OutgoingMessage("/nsm/client/label")
message.add_arg(label) #s:label message.add_arg(label) #s:label
self.sock.sendto(message.build(), self.nsmOSCUrl) self.sock.sendto(message.build(), self.nsmOSCUrl)
def broadcast(self, path:str, arguments:list): def broadcast(self, path:str, arguments:list):
"""/nsm/server/broadcast s:path [arguments...] """/nsm/server/broadcast s:path [arguments...]
@ -590,27 +597,27 @@ class NSMClient(object):
""" """
if path.startswith("/nsm"): if path.startswith("/nsm"):
logger.warning("Attempted broadbast starting with /nsm. Not allwoed") logger.warning("Attempted broadbast starting with /nsm. Not allwoed")
else: else:
logger.info("Sending broadcast " + path + repr(arguments)) logger.info("Sending broadcast " + path + repr(arguments))
message = _OutgoingMessage("/nsm/server/broadcast") message = _OutgoingMessage("/nsm/server/broadcast")
message.add_arg(path) message.add_arg(path)
for arg in arguments: for arg in arguments:
message.add_arg(arg) #type autodetect message.add_arg(arg) #type autodetect
self.sock.sendto(message.build(), self.nsmOSCUrl) self.sock.sendto(message.build(), self.nsmOSCUrl)
def importResource(self, filePath): def importResource(self, filePath):
"""aka. import into session """aka. import into session
ATTENTION! You will still receive an absolute path from this function. You need to make ATTENTION! You will still receive an absolute path from this function. You need to make
sure yourself that this path will not be saved in your save file, but rather use a place- sure yourself that this path will not be saved in your save file, but rather use a place-
holder that gets replaced by the actual session path each time. A good point is after holder that gets replaced by the actual session path each time. A good point is after
serialisation. search&replace for the session prefix ("ourPath") and replace it with a tag serialisation. search&replace for the session prefix ("ourPath") and replace it with a tag
e.g. <sessionDirectory>. The opposite during load. e.g. <sessionDirectory>. The opposite during load.
Only such a behaviour will make your session portable. Only such a behaviour will make your session portable.
Do not use the following pattern: An alternative that comes to mind is to only work with Do not use the following pattern: An alternative that comes to mind is to only work with
relative paths and force your programs workdir to the session directory. Better work with relative paths and force your programs workdir to the session directory. Better work with
absolute paths internally . absolute paths internally .
Symlinks given path into session dir and returns the linked path relative to the ourPath. Symlinks given path into session dir and returns the linked path relative to the ourPath.
It can handles single files as well as whole directories. It can handles single files as well as whole directories.
@ -633,11 +640,11 @@ class NSMClient(object):
or of our client program. We do not provide any means to unlink or delete files from the or of our client program. We do not provide any means to unlink or delete files from the
session directory. session directory.
""" """
#Even if the project was not saved yet now it is time to make our directory in the NSM dir. #Even if the project was not saved yet now it is time to make our directory in the NSM dir.
if not os.path.exists(self.ourPath): if not os.path.exists(self.ourPath):
os.makedirs(self.ourPath) os.makedirs(self.ourPath)
filePath = os.path.abspath(filePath) #includes normalisation filePath = os.path.abspath(filePath) #includes normalisation
if not os.path.exists(self.ourPath):raise FileNotFoundError(self.ourPath) if not os.path.exists(self.ourPath):raise FileNotFoundError(self.ourPath)
if not os.path.isdir(self.ourPath): raise NotADirectoryError(self.ourPath) if not os.path.isdir(self.ourPath): raise NotADirectoryError(self.ourPath)

Loading…
Cancel
Save