#! /usr/bin/python # Session Exchange # By Taybin Rutkin # Copyright 2004, under the GPL VERSION='0.1.1' #twisted libraries from twisted.internet import gtk2reactor gtk2reactor.install() from twisted.internet import reactor, protocol import twisted.internet.error #pygtk libraries import gobject import gtk #standard python2.2 libraries import getopt import os import os.path import re import shelve import string import sys import xml.dom.pulldom def get_header_size(filename): size = 0 file = open(filename, 'r') while True: chunk = file.read(4) size += 4 if chunk == "data": file.close() return size + 4 #include the size chunk after "data" if not chunk: file.close() return None def append_empty_data(self, filename, size): file = open(filename, 'a') file.seek(size-1) file.write('\x00') file.close() def get_sound_list(snapshot): doc = xml.dom.pulldom.parse(snapshot) seen = {} soundlist = [] for event, node in doc: if event=='START_ELEMENT' and node.nodeName=='Source': soundlist.append(str(node.getAttribute('name'))) return soundlist def raise_error(string, parent): dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, string) dialog.run() dialog.destroy() class Data(object): def delete_snap(self, session, collab, snap): sessions = self._data['sessions'] sessions[session]['collabs'][collab]['snaps'].remove(snap) self._data['sessions'] = sessions def delete_collab(self,session, collab): sessions = self._data['sessions'] del sessions[session]['collabs'][collab] self._data['sessions'] = sessions def delete_session(self, session): sessions = self._data['sessions'] del sessions[session] self._data['sessions'] = sessions def add_snap(self, session_name, collab_name, snap_name): sessions = self._data['sessions'] sessions[session_name]['collabs'][collab_name]['snaps'].append(snap_name) sessions[session_name]['collabs'][collab_name]['snaps'].sort() self._data['sessions'] = sessions g_display.update_snap_view() def add_collab(self, session_name, collab_name, ip_address, port): sessions = self._data['sessions'] sessions[session_name]['collabs'][collab_name] = {} sessions[session_name]['collabs'][collab_name]['snaps'] = [] sessions[session_name]['collabs'][collab_name]['sounds'] = [] sessions[session_name]['collabs'][collab_name]['ip'] = ip_address sessions[session_name]['collabs'][collab_name]['port'] = port self._data['sessions'] = sessions client = ExchangeClientFactory(session_name, collab_name, None, self.debug_mode) reactor.connectTCP(ip_address, port, client) g_display.show_status("connecting") g_display.update_collab_view() def add_session(self, session_path): sessions = self._data['sessions'] session_name = session_path[session_path.rfind('/', 0, len(session_path)-2)+1: -1] sessions[session_name] = {} sessions[session_name]['path'] = session_path sessions[session_name]['collabs'] = {} sessions[session_name]['collabs'][self._data['user']] = {} sessions[session_name]['collabs'][self._data['user']]['snaps'] = [] sessions[session_name]['collabs'][self._data['user']]['sounds'] = [] self._data['sessions'] = sessions self.rescan_session(session_name) def rescan_session(self, session_name): sessions = self._data['sessions'] session_path = sessions[session_name]['path'] sessions[session_name]['collabs'][self._data['user']]['snaps'] = self._scan_snapshots(session_path) sessions[session_name]['collabs'][self._data['user']]['sounds'] = self._scan_sounds(session_path) self._data['sessions'] = sessions g_display.update_snap_view() print self._data['sessions'] def create_session(self, session_path): try: os.mkdir(session_path) os.mkdir(session_path+"/sounds") except OSError: raise_error("Could not create session directory", g_display.window) return sessions = self._data['sessions'] session_name = session_path[session_path.rfind('/', 0, len(session_path)-2)+1: ] sessions[session_name] = {} sessions[session_name]['path'] = session_path sessions[session_name]['collabs'] = {} sessions[session_name]['collabs'][self._data['user']] = {} sessions[session_name]['collabs'][self._data['user']]['snaps'] = [] sessions[session_name]['collabs'][self._data['user']]['sounds'] = [] self._data['sessions'] = sessions print self._data['sessions'] def get_session_path(self, session): sessions = self._data['sessions'] return sessions[session]['path'] def get_user(self): return self._data['user'] def set_user(self, username): self._data['user'] = username def get_collab_ip(self, session, collab): sessions = self._data['sessions'] return sessions[session]['collabs'][collab]['ip'] def close(self): self._data.close() def get_sessions(self): sessions = self._data['sessions'] sess = sessions.keys() sess.sort() return sess def get_collabs(self, session): if session: sessions = self._data['sessions'] collabs = sessions[session]['collabs'].keys() collabs.sort() return collabs else: return [] def get_snaps(self, session, collab): if session and collab: sessions = self._data['sessions'] snaps = sessions[session]['collabs'][collab]['snaps'] snaps.sort() return snaps else: return [] def get_sounds(self, session, collab): if session and collab: sessions = self._data['sessions'] sounds = sessions[session]['collabs'][self._data['user']]['sounds'] sounds.sort() return sounds else: return [] def _scan_snapshots(self, session): snaps = [] files = os.listdir(session) pattern = re.compile(r'\.ardour$') for file in files: if pattern.search(file): snaps.append(file[0:-7]) print file[0:-7] return snaps def _scan_sounds(self, session): sounds = [] files = os.listdir(session+'/sounds') pattern = re.compile(r'\.peak$') for file in files: if not pattern.search(file): sounds.append(file) return sounds def __init__(self, *args): self._data = shelve.open(os.path.expanduser('~/.session_exchange'), 'c') self.port = 8970 self.debug_mode = False if len(self._data.keys()) < 1: self._data['sessions'] = {} self._data['user'] = '' self._collabs = {} from twisted.protocols.basic import FileSender class FileSenderLimited(FileSender): def beginFileTransfer(self, file, consumer, limit, transform = None): self.file = file self.consumer = consumer self.CHUNK_SIZE = limit self.transform = transform self.consumer.registerProducer(self, False) self.deferred = defer.Deferred() return self.deferred def resumeProducing(self): chunk = '' chunk = self.file.read(self.CHUNK_SIZE) if self.transform: chunk = self.transform(chunk) self.consumer.write(chunk) self.lastSent = chunk[-1] self.file = None self.consumer.unregisterProducer() self.deferred.callback(self.lastSent) self.deferred = None from twisted.protocols.basic import LineReceiver class ExchangeServer (LineReceiver): def __init__(self): self.state = "IDLE" def error(self, message): self.sendLine("ERROR") self.sendLine(message) self.transport.loseConnection() def connectionLost(self, reason): print "server: connection lost: ", reason def connectionMade(self): print "server: connection made" def lineReceived(self, data): print "server: ", data if self.state == "SESSION": if g_data.get_sessions().count(data): self.session_name = data self.state = "IDLE" self.sendLine("OK") else: self.error(data + " doesn't exist on server") elif self.state == "SNAPSHOT": if g_data.get_snaps(self.session_name, g_data.get_user()).count(data): filename = g_data.get_session_path(self.session_name)+data+'.ardour' print filename self.sendLine(str(os.stat(filename).st_size)) self.sendLine("OK") self.file = open(filename, 'r') file_sender = FileSender() cb = file_sender.beginFileTransfer(self.file, self.transport) cb.addCallback(self.file_done) else: self.error("snapshot: " + data + " doesn't exist on server") elif self.state == "SOUNDFILE" or self.state == "SOUNDFILE_HEADER": if g_data.get_sounds(self.session_name, g_data.get_user()).count(data): filename = g_data.get_session_path(self.session_name)+"/sounds/"+data print filename if self.state == "SOUNDFILE": self.sendLine(str(os.stat(filename).st_size)) else: #SOUNDFILE_HEADER header_size = get_header_size(filename) if header_size: self.sendLine(str(header_size)) else: self.error('soundfile: ' + data + 'doesn\'t have "data" chunk') self.sendLine("OK") self.file = open(filename, 'r') if self.state == "SOUNDFILE": file_sender = FileSender() cb = file_sender.beginFileTransfer(self.file, self.transport) else: # SOUNDFILE_HEADER file_sender = FileSenderLimited() cb = file_sender.beginFileTransfer(self.file, self.transport, header_size) cb.addCallback(self.file_done) else: self.error("soundfile: " + data + "doesn't exist on server") elif self.state == "SOUNDFILE_SIZE": if g_data.get_sounds(self.session_name, g_data.get_user()).count(data): filename = g_data.get_session_path(self.session_name)+"/sounds/"+data print filename self.sendLine(str(os.stat(filename).st_size)) self.state = "IDLE" elif data == "SESSION": self.state = "SESSION" elif data == "SNAPS": self.state = "SNAPS" for snap in g_data.get_snaps(self.session_name, g_data.get_user()): self.sendLine(snap) self.sendLine("OK") self.state = "IDLE" elif data == "SNAPSHOT": self.state = "SNAPSHOT" elif data == "SOUNDFILE": self.state = "SOUNDFILE" elif data == "SOUNDFILE_HEADER": self.state = "SOUNDFILE_HEADER" elif data == "SOUNDFILE_SIZE": self.state = "SOUNDFILE_SIZE" def file_done(self, data): print "server: file done" self.file.close() self.state = "IDLE" class ExchangeServerFactory(protocol.ServerFactory): protocol = ExchangeServer def __init__(self): pass class ExchangeClient (LineReceiver): def __init__(self, session_name, collab_name, snap_name, debug_mode): self.session_name = session_name self.collab_name = collab_name self.snap_name = snap_name self.debug_mode = debug_mode self.state = "IDLE" def connectionLost(self, reason): g_display.show_status("Connection lost") def connectionMade(self): g_display.show_status("Connection made") self.state = "SESSION" self.sendLine("SESSION") self.sendLine(self.session_name) def rawDataReceived(self, data): self.file.write(data) self.received += len(data) print self.received, self.filesize if self.received >= self.filesize: self.setLineMode() self.file.close() g_data.rescan_session(self.session_name) if self.state == "SNAPSHOT": self.sounds = get_sound_list(self.filename) if len(self.sounds): self.sound_index = 0 if self.debug_mode: self.state = "SOUNDFILE_HEADER" self.sendLine("SOUNDFILE_HEADER") else: self.state = "SOUNDFILE" self.sendLine("SOUNDFILE") self.sendLine(self.sounds[self.sound_index]) else: self.transport.loseConnection() elif self.state == "SOUNDFILE": self.sound_index += 1 if self.sound_index > len(self.sounds)-1: self.transport.loseConnection() else: self.sendLine("SOUNDFILE") self.sendLine(self.sounds[self.sound_index]) elif self.state == "SOUNDFILE_HEADER": self.state = "SOUNDFILE_SIZE" self.sendLine("SOUNDFILE_SIZE") self.sendLine(self.sounds[self.sound_index]) def lineReceived(self, data): print "client: ", data if data == "ERROR": self.state = "ERROR" elif data == "OK": if self.state == "SESSION": if self.snap_name: self.state = "SNAPSHOT" self.sendLine("SNAPSHOT") self.sendLine(self.snap_name) else: self.state = "SNAPS" self.sendLine("SNAPS") elif self.state == "SNAPS": self.transport.loseConnection() elif self.state == "SNAPSHOT": self.setRawMode() self.filename = g_data.get_session_path(self.session_name)+'/'+self.snap_name+'.ardour' self.file = open(self.filename, 'w') self.received = 0 elif self.state == "SOUNDFILE" or self.state == "SOUNDFILE_HEADER": self.setRawMode() self.filename = g_data.get_session_path(self.session_name)+'/sounds/'+self.sounds[self.sound_index] self.file = open(self.filename, 'w') self.received = 0 elif self.state == "ERROR": raise_error(data, g_display.window) elif self.state == "SNAPS": g_data.add_snap(self.session_name, self.collab_name, data) elif self.state == "SNAPSHOT": self.filesize = int(data) elif self.state == "SOUNDFILE": self.filesize = int(data) elif self.state == "SOUNDFILE_HEADER": self.filesize = int(data) elif self.state == "SOUNDFILE_SIZE": append_empty_data(self.filename, int(data)) self.sound_index += 1 if self.sound_index > len(self.sounds)-1: self.transport.loseConnection() else: self.state = "SOUNDFILE_HEADER" self.sendLine("SOUNDFILE_HEADER") self.sendLine(self.sounds[self.sound_index]) class ExchangeClientFactory(protocol.ClientFactory): def buildProtocol(self, addr): return ExchangeClient(self.session_name, self.collab_name, self.snap_name, self.debug_mode) def clientConnectionFailed(self, connector, reason): raise_error('Connection failed: ' + reason.getErrorMessage(), g_display.window) g_display.show_status('Connection failed') def __init__(self, session_name, collab_name, snap_name, debug_mode): self.session_name = session_name self.collab_name = collab_name self.snap_name = snap_name self.debug_mode = debug_mode class HelperWin(object): def delete_me(self, window): self = 0 class Preferences(HelperWin): def __init__(self): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title('Preferences') self.window.connect('destroy', self.delete_me) main_box = gtk.VBox() self.window.add(main_box) hbox1 = gtk.HBox() label1 = gtk.Label("User") self.user = gtk.Entry() self.user.set_text(g_data.get_user()) hbox1.pack_start(label1) hbox1.pack_start(self.user) main_box.pack_start(hbox1) ok_btn = gtk.Button("Ok") ok_btn.connect('clicked', self.ok_clicked) main_box.pack_start(ok_btn) self.window.show_all() def ok_clicked(self, btn): g_data.set_user(self.user.get_text()) self.window.hide_all() def show_all(self): self.window.show_all() class AddCollaborator(HelperWin): def __init__(self, session): self.session_name = session self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title('Fetch Session') self.window.connect('destroy', self.delete_me) main_box = gtk.VBox() self.window.add(main_box) hbox0 = gtk.HBox() label0 = gtk.Label("Collaborator") self.collab = gtk.Entry() self.collab.connect('key-release-event', self.key_press) hbox0.pack_start(label0) hbox0.pack_start(self.collab) main_box.pack_start(hbox0) hbox1 = gtk.HBox() label1 = gtk.Label("IP Address") self.address = gtk.Entry() self.address.connect('key-release-event', self.key_press) hbox1.pack_start(label1) hbox1.pack_start(self.address) main_box.pack_start(hbox1) hbox2 = gtk.HBox() label2 = gtk.Label("Port Number") self.port = gtk.Entry() self.port.connect('key-release-event', self.key_press) self.port.set_text(str(g_data.port)) hbox2.pack_start(label2) hbox2.pack_start(self.port) main_box.pack_start(hbox2) hbox3 = gtk.HBox() label3 = gtk.Label("Username") label3.set_sensitive(False) self.username = gtk.Entry() self.username.set_sensitive(False) hbox3.pack_start(label3) hbox3.pack_start(self.username) main_box.pack_start(hbox3) hbox4 = gtk.HBox() label4 = gtk.Label("Password") label4.set_sensitive(False) self.password = gtk.Entry() self.password.set_sensitive(False) hbox4.pack_start(label4) hbox4.pack_start(self.password) main_box.pack_start(hbox4) self.ok_btn = gtk.Button(gtk.STOCK_OK) self.ok_btn.set_use_stock(True) self.ok_btn.connect('clicked', self.ok_clicked) self.ok_btn.set_sensitive(False) main_box.pack_start(self.ok_btn) self.window.show_all() def key_press(self, event, data): if self.collab.get_text() and self.address.get_text() and self.port.get_text(): self.ok_btn.set_sensitive(True) else: self.ok_btn.set_sensitive(False) return True def ok_clicked(self, btn): self.window.hide_all() g_data.add_collab(self.session_name, self.collab.get_text(), self.address.get_text(), int(self.port.get_text())) self.collab.set_text('') self.address.set_text('') self.port.set_text('') self.username.set_text('') self.password.set_text('') def show_all(self): self.window.show_all() class ArdourShareWindow(object): def menuitem_cb(self, window, action, widget): print self, window, action, widget def add_collaborator_cb(self, window, action, widget): if self.session: self.add_session = AddCollaborator(self.session) def fetch_snapshot_cb(self, window, action, widget): if self.session and self.collab and self.collab != g_data.get_user(): client = ExchangeClientFactory(self.session, self.collab, self.snap, g_data.debug_mode) reactor.connectTCP(g_data.get_collab_ip(self.session, self.collab), g_data.port, client) def preferences_cb(self, window, action, widget): self.preferences = Preferences() def add_session_ok_file_btn_clicked(self, w): filename = self.file_sel.get_filename() if filename.endswith(".ardour"): g_data.add_session(filename[0:filename.rfind("/")+1]) self.update_session_view() else: raise_error("Not an Ardour session", self.window) self.file_sel.destroy() def add_session_cb(self, window, action, widget): if g_data.get_user(): self.file_sel = gtk.FileSelection("Add Session...") self.file_sel.ok_button.connect("clicked", self.add_session_ok_file_btn_clicked) self.file_sel.cancel_button.connect("clicked", lambda w: self.file_sel.destroy()) self.file_sel.connect("destroy", lambda w: self.file_sel.destroy()) self.file_sel.show() else: raise_error("Set the user name in the preferences first", self.window) def create_session_cb(self, window, action, widget): if g_data.get_user(): self.file_sel = gtk.FileSelection("Create Session...") self.file_sel.ok_button.connect("clicked", self.create_file_ok_btn_clicked) self.file_sel.cancel_button.connect("clicked", lambda w: self.file_sel.destroy()) self.file_sel.connect("destroy", lambda w: self.file_sel.destroy()) self.file_sel.show() else: raise_error("Set the user name in the preferences first", self.window) def create_file_ok_btn_clicked(self, w): filename = self.file_sel.get_filename() if len(filename) > 0: g_data.create_session(filename) self.update_session_view() else: raise_error("Not an Ardour session", self.window) self.file_sel.destroy() def update_session_view(self): self.session_model.clear() for session in g_data.get_sessions(): self.session_model.set(self.session_model.append(), 0, session) def update_collab_view(self): self.collab_model.clear() for collab in g_data.get_collabs(self.session): self.collab_model.set(self.collab_model.append(), 0, collab) def update_snap_view(self): self.snap_model.clear() for snap in g_data.get_snaps(self.session, self.collab): self.snap_model.set(self.snap_model.append(), 0, snap) def cb_session_selection_changed(self, selection_object): selected = [] selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path)) for x in selected: self.session = self.session_model[x][0] self.selected_type = "session" self.update_collab_view() def cb_collab_selection_changed(self, selection_object): selected = [] selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path)) for x in selected: self.collab = self.collab_model[x][0] self.selected_type = "collab" self.update_snap_view() def cb_snap_selection_changed(self, selection_object): selected = [] selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path)) for x in selected: self.snap = self.snap_model[x][0] self.selected_type = "snap" def delete_cb(self, window, action, widget): if self.selected_type == "session": g_data.delete_session(self.session) self.session = "" self.collab = "" self.snap = "" elif self.selected_type == "collab": g_data.delete_collab(self.session, self.collab) self.collab = "" self.snap = "" elif self.selected_type == "snap": g_data.delete_snap(self.session, self.collab, self.snap) self.snap = "" self.update_session_view() self.update_collab_view() self.update_snap_view() self.selected_type = "" def show_status(self, text): mid = self.status_bar.push(self._status_cid, text) if self._status_mid: self.status_bar.remove(self._status_cid, self._status_mid) self._status_mid = mid def __init__(self): self.selected_type = "" self.session = "" self.collab = g_data.get_user() self.snap = "" self.preferences = 0 self.add_collab = 0 self.add_session = 0 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title('Session Exchange') self.window.set_size_request(400, 200) self.window.connect('destroy', lambda win: gtk.main_quit()) accel_group = gtk.AccelGroup() self.window.add_accel_group(accel_group) main_box = gtk.VBox() self.window.add(main_box) menu_items = ( ('/_File', None, None, 0, ''), ('/File/_Add Session...','A', self.add_session_cb, 0, ''), ('/File/Create _Session...', 'S', self.create_session_cb, 0, ''), ('/File/sep1', None, None, 0, ''), ('/File/_Quit', 'Q', gtk.main_quit, 0, '', gtk.STOCK_QUIT), ('/_Edit', None, None, 0, '' ), ('/Edit/Cu_t', 'X', self.menuitem_cb, 0, '', gtk.STOCK_CUT), ('/Edit/_Copy', 'C', self.menuitem_cb, 0, '', gtk.STOCK_COPY), ('/Edit/_Paste', 'V', self.menuitem_cb, 0, '', gtk.STOCK_PASTE), ('/Edit/_Delete', None, self.delete_cb, 0, '', gtk.STOCK_DELETE), ('/Edit/sep1', None, None, 0, ''), ('/Edit/Add Colla_borator...','B', self.add_collaborator_cb,0,''), ('/Edit/_Fetch Snapshot','F', self.fetch_snapshot_cb,0,''), ('/Edit/sep1', None, None, 0, ''), ('/Edit/_Preferences...','P', self.preferences_cb, 0, '') ) #need to hold a reference to the item_factory or the menubar will disappear. self.item_factory = gtk.ItemFactory(gtk.MenuBar, '
', accel_group) self.item_factory.create_items(menu_items, self.window) main_box.pack_start(self.item_factory.get_widget('
'), gtk.FALSE) pane1 = gtk.HPaned() pane2 = gtk.HPaned() pane1.pack2(pane2, gtk.TRUE, gtk.FALSE) scroll1 = gtk.ScrolledWindow() scroll1.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) pane1.pack1(scroll1, gtk.TRUE, gtk.FALSE) scroll2 = gtk.ScrolledWindow() scroll2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) pane2.pack1(scroll2, gtk.TRUE, gtk.FALSE) scroll3 = gtk.ScrolledWindow() scroll3.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) pane2.pack2(scroll3, gtk.TRUE, gtk.FALSE) self.session_model = gtk.ListStore(gobject.TYPE_STRING) view1 = gtk.TreeView(self.session_model) column1 = gtk.TreeViewColumn('Sessions', gtk.CellRendererText(), text=0) view1.append_column(column1) self.session_selection = view1.get_selection() self.session_selection.connect("changed", self.cb_session_selection_changed) scroll1.add(view1) self.update_session_view() self.collab_model = gtk.ListStore(gobject.TYPE_STRING) view2 = gtk.TreeView(self.collab_model) column2 = gtk.TreeViewColumn('Collaborators', gtk.CellRendererText(), text=0) view2.append_column(column2) self.collab_selection = view2.get_selection() self.collab_selection.connect("changed", self.cb_collab_selection_changed) scroll2.add(view2) self.snap_model = gtk.ListStore(gobject.TYPE_STRING) view3 = gtk.TreeView(self.snap_model) column3 = gtk.TreeViewColumn('Snapshots', gtk.CellRendererText(), text=0) view3.append_column(column3) self.snap_selection = view3.get_selection() self.snap_selection.connect("changed", self.cb_snap_selection_changed) scroll3.add(view3) main_box.pack_start(pane1, gtk.TRUE, gtk.TRUE) self.status_bar = gtk.Statusbar() main_box.pack_start(self.status_bar, gtk.FALSE) self._status_cid = self.status_bar.get_context_id('display') self._status_mid = '' self.window.show_all() def print_help(): print """ -h, --help -n, --no-server Only act as a client -p, --port Defaults to 8970 -d, --debug Infers audio files. For debugging Ardour. -v, --version Version """ sys.exit(2) def main(): try: opts, args = getopt.getopt(sys.argv[1:], "hp:ndv", ["help", "port=", "no-server", "debug", "version"]) except getopt.GetoptError: print_help() server = True for o, a in opts: if o in ("-h", "--help"): print_help() if o in ("-d", "--debug"): g_display.window.set_title('Session Exchange: Debug Mode') g_data.debug_mode = True if o in ("-p", "--port"): g_data.port = int(a) if o in ("-n", "--no-server"): server = False if o in ("-v", "--version"): print VERSION sys.exit(2) if (server): try: reactor.listenTCP(g_data.port, ExchangeServerFactory()) except twisted.internet.error.CannotListenError: print "Can not listen on a port number under 1024 unless run as root" sys.exit(2) reactor.run() g_data.close() # global objects g_data = Data() g_display = ArdourShareWindow() if __name__ == '__main__': main()