Merged with trunk R1393.
[ardour.git] / tools / session_exchange.py
1 #! /usr/bin/python
2
3 # Session Exchange
4 # By Taybin Rutkin
5 # Copyright 2004-2005, under the GPL
6
7 VERSION='0.1.2'
8
9 #twisted libraries
10 from twisted.internet import gtk2reactor
11 gtk2reactor.install()
12 from twisted.internet import reactor, protocol
13 import twisted.internet.error
14
15 #pygtk libraries
16 import gobject
17 import gtk
18
19 #standard python2.2 libraries
20 import getopt
21 import os
22 import os.path
23 import re
24 import shelve
25 import string
26 import sys
27 import xml.dom.minidom
28
29 def get_header_size(filename):
30         size = 0
31         file = open(filename, 'r')
32         while True:
33                 chunk = file.read(4)
34                 size += 4
35                 if chunk == "data":
36                         file.close()
37                         return size + 4 #include the size chunk after "data"
38                 if not chunk:
39                         file.close()
40                         return None
41
42 def append_empty_data(self, filename, size):
43         file = open(filename, 'a')
44         file.seek(size-1)
45         file.write('\x00')
46         file.close()
47         
48 def get_sound_list(snapshot):
49         doc = xml.dom.minidom.parse(snapshot)
50         
51         regionlist = []
52         playlists_tag = doc.getElementsByTagName('Playlists')
53         playlists = playlists_tag[0].getElementsByTagName('Playlist')
54         for play in playlists:
55                 regions = play.getElementsByTagName('Region')
56                 for region in regions:
57                         regionlist.append(region.getAttribute('source-0'))
58                         regionlist.append(region.getAttribute('source-1'))
59                         regionlist.append(region.getAttribute('source-2'))
60                         regionlist.append(region.getAttribute('source-3'))
61                         regionlist.append(region.getAttribute('source-4'))
62                         regionlist.append(region.getAttribute('source-5'))
63         
64         sourcelist = {}
65         sources = doc.getElementsByTagName('Source')
66         for source in sources:
67                 sourcelist[source.getAttribute('id')] = str(source.getAttribute('name'))
68
69         soundlist = []
70         for id in regionlist:
71                 if sourcelist.has_key(id):
72                         soundlist.append(sourcelist[id])
73         
74         return soundlist
75
76 def raise_error(string, parent):
77         dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
78         gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, string)
79         
80         dialog.run()
81         dialog.destroy()
82
83 class Data(object):
84         def delete_snap(self, session, collab, snap):
85                 sessions = self._data['sessions']
86                 sessions[session]['collabs'][collab]['snaps'].remove(snap)
87                 self._data['sessions'] = sessions
88         
89         def delete_collab(self,session, collab):
90                 sessions = self._data['sessions']
91                 del sessions[session]['collabs'][collab]
92                 self._data['sessions'] = sessions
93         
94         def delete_session(self, session):
95                 sessions = self._data['sessions']
96                 del sessions[session]
97                 self._data['sessions'] = sessions
98         
99         def add_snap(self, session_name, collab_name, snap_name):
100                 sessions = self._data['sessions']
101                 sessions[session_name]['collabs'][collab_name]['snaps'].append(snap_name)
102                 sessions[session_name]['collabs'][collab_name]['snaps'].sort()
103                 self._data['sessions'] = sessions
104                 
105                 g_display.update_snap_view()
106         
107         def add_collab(self, session_name, collab_name, ip_address, port):
108                 sessions = self._data['sessions']
109                 sessions[session_name]['collabs'][collab_name] = {}
110                 sessions[session_name]['collabs'][collab_name]['snaps'] = []
111                 sessions[session_name]['collabs'][collab_name]['sounds'] = []
112                 sessions[session_name]['collabs'][collab_name]['ip'] = ip_address
113                 sessions[session_name]['collabs'][collab_name]['port'] = port
114                 self._data['sessions'] = sessions
115                 
116                 client = ExchangeClientFactory(session_name, collab_name, None, self.debug_mode)
117                 reactor.connectTCP(ip_address, port, client)
118                 g_display.show_status("connecting")
119                 
120                 g_display.update_collab_view()
121         
122         def add_session(self, session_path):
123                 sessions = self._data['sessions']
124                 
125                 session_name = session_path[session_path.rfind('/', 0, len(session_path)-2)+1: -1]
126                 sessions[session_name] = {}
127                 sessions[session_name]['path'] = session_path 
128                 sessions[session_name]['collabs'] = {}
129                 sessions[session_name]['collabs'][self._data['user']] = {}
130                 sessions[session_name]['collabs'][self._data['user']]['snaps'] = []
131                 sessions[session_name]['collabs'][self._data['user']]['sounds'] = []
132                 
133                 self._data['sessions'] = sessions
134                 
135                 self.rescan_session(session_name)
136
137         def rescan_session(self, session_name):
138                 sessions = self._data['sessions']
139                 
140                 session_path = sessions[session_name]['path']
141                 sessions[session_name]['collabs'][self._data['user']]['snaps'] = self._scan_snapshots(session_path)
142                 sessions[session_name]['collabs'][self._data['user']]['sounds'] = self._scan_sounds(session_path)
143                 
144                 self._data['sessions'] = sessions
145                 
146                 g_display.update_snap_view()
147                 
148                 print self._data['sessions']
149         
150         def create_session(self, session_path):
151                 try:
152                         os.mkdir(session_path)
153                         os.mkdir(session_path+"/sounds")
154                 except OSError:
155                         raise_error("Could not create session directory", g_display.window)
156                         return
157                 
158                 sessions = self._data['sessions']
159                 
160                 session_name = session_path[session_path.rfind('/', 0, len(session_path)-2)+1: ]
161                 sessions[session_name] = {}
162                 sessions[session_name]['path'] = session_path
163                 sessions[session_name]['collabs'] = {}
164                 sessions[session_name]['collabs'][self._data['user']] = {}
165                 sessions[session_name]['collabs'][self._data['user']]['snaps'] = []
166                 sessions[session_name]['collabs'][self._data['user']]['sounds'] = []
167                 
168                 self._data['sessions'] = sessions
169                 print self._data['sessions']
170         
171         def get_session_path(self, session):
172                 sessions = self._data['sessions']
173                 return sessions[session]['path']
174         
175         def get_user(self):
176                 return self._data['user']
177         
178         def set_user(self, username):
179                 self._data['user'] = username
180         
181         def get_collab_ip(self, session, collab):
182                 sessions = self._data['sessions']
183                 return sessions[session]['collabs'][collab]['ip']
184         
185         def close(self):
186                 self._data.close()
187         
188         def get_sessions(self):
189                 sessions = self._data['sessions']
190                 sess = sessions.keys()
191                 sess.sort()
192                 return sess
193         
194         def get_collabs(self, session):
195                 if session:
196                         sessions = self._data['sessions']
197                         collabs = sessions[session]['collabs'].keys()
198                         collabs.sort()
199                         return collabs
200                 else:
201                         return []
202         
203         def get_snaps(self, session, collab):
204                 if session and collab:
205                         sessions = self._data['sessions']
206                         snaps = sessions[session]['collabs'][collab]['snaps']
207                         snaps.sort()
208                         return snaps
209                 else:
210                         return []
211         
212         def get_sounds(self, session, collab):
213                 if session and collab:
214                         sessions = self._data['sessions']
215                         sounds = sessions[session]['collabs'][self._data['user']]['sounds']
216                         sounds.sort()
217                         return sounds
218                 else:
219                         return []
220                 
221         def _scan_snapshots(self, session):
222                 snaps = []
223                 files = os.listdir(session)
224                 pattern = re.compile(r'\.ardour$')
225                 for file in files:
226                         if pattern.search(file):
227                                 snaps.append(file[0:-7])
228                                 print file[0:-7]
229                 return snaps
230         
231         def _scan_sounds(self, session):
232                 sounds = []
233                 files = os.listdir(session+'/sounds')
234                 pattern = re.compile(r'\.peak$')
235                 for file in files:
236                         if not pattern.search(file):
237                                 sounds.append(file)
238                 return sounds
239         
240         def __init__(self, *args):
241                 self._data = shelve.open(os.path.expanduser('~/.session_exchange'), 'c')
242                 self.port = 8970
243                 self.debug_mode = False
244                 if len(self._data.keys()) < 1:
245                         self._data['sessions'] = {}
246                         self._data['user'] = ''
247                 
248                 self._collabs = {}
249
250 from twisted.protocols.basic import FileSender
251 class FileSenderLimited(FileSender):
252         def beginFileTransfer(self, file, consumer, limit, transform = None):
253                 self.file = file
254                 self.consumer = consumer
255                 self.CHUNK_SIZE = limit
256                 self.transform = transform
257                 
258                 self.consumer.registerProducer(self, False)
259                 self.deferred = defer.Deferred()
260                 return self.deferred
261         
262         def resumeProducing(self):
263                 chunk = ''
264                 chunk = self.file.read(self.CHUNK_SIZE)
265                 
266                 if self.transform:
267                         chunk = self.transform(chunk)
268
269                 self.consumer.write(chunk)
270                 self.lastSent = chunk[-1]
271                 self.file = None
272                 self.consumer.unregisterProducer()
273                 self.deferred.callback(self.lastSent)
274                 self.deferred = None
275
276 from twisted.protocols.basic import LineReceiver
277 class ExchangeServer (LineReceiver):
278         def __init__(self):
279                 self.state = "IDLE"
280         
281         def error(self, message):
282                 self.sendLine("ERROR")
283                 self.sendLine(message)
284                 self.transport.loseConnection()
285         
286         def connectionLost(self, reason):
287                 print "server: connection lost: ", reason
288         
289         def connectionMade(self):
290                 print "server: connection made"
291         
292         def lineReceived(self, data):
293                 print "server: ", data
294                 
295                 if self.state == "SESSION":
296                         if g_data.get_sessions().count(data):
297                                 self.session_name = data
298                                 self.state = "IDLE"
299                                 self.sendLine("OK")
300                         else:
301                                 self.error(data + " doesn't exist on server")
302                 elif self.state == "SNAPSHOT":
303                         if g_data.get_snaps(self.session_name, g_data.get_user()).count(data):
304                                 filename = g_data.get_session_path(self.session_name)+data+'.ardour'
305                                 print filename
306                                 self.sendLine(str(os.stat(filename).st_size))
307                                 self.sendLine("OK")
308                                 self.file = open(filename, 'r')
309                                 file_sender = FileSender()
310                                 cb = file_sender.beginFileTransfer(self.file, self.transport)
311                                 cb.addCallback(self.file_done)
312                         else:
313                                 self.error("snapshot: " + data + " doesn't exist on server")
314                 elif self.state == "SOUNDFILE" or self.state == "SOUNDFILE_HEADER":
315                         if g_data.get_sounds(self.session_name, g_data.get_user()).count(data):
316                                 filename = g_data.get_session_path(self.session_name)+"/sounds/"+data
317                                 print filename
318                                 if self.state == "SOUNDFILE":
319                                         self.sendLine(str(os.stat(filename).st_size))
320                                 else:   #SOUNDFILE_HEADER
321                                         header_size = get_header_size(filename)
322                                         if header_size:
323                                                 self.sendLine(str(header_size))
324                                         else:
325                                                 self.error('soundfile: ' + data + 'doesn\'t have "data" chunk')
326                                 self.sendLine("OK")
327                                 self.file = open(filename, 'r')
328                                 if self.state == "SOUNDFILE":
329                                         file_sender = FileSender()
330                                         cb = file_sender.beginFileTransfer(self.file, self.transport)
331                                 else:   # SOUNDFILE_HEADER
332                                         file_sender = FileSenderLimited()
333                                         cb = file_sender.beginFileTransfer(self.file, self.transport, header_size)
334                                 cb.addCallback(self.file_done)
335                         else:
336                                 self.error("soundfile: " + data + "doesn't exist on server")
337                 elif self.state == "SOUNDFILE_SIZE":
338                         if g_data.get_sounds(self.session_name, g_data.get_user()).count(data):
339                                 filename = g_data.get_session_path(self.session_name)+"/sounds/"+data
340                                 print filename
341                                 self.sendLine(str(os.stat(filename).st_size))
342                                 self.state = "IDLE"
343                 elif data == "SESSION":
344                         self.state = "SESSION"
345                 elif data == "SNAPS":
346                         self.state = "SNAPS"
347                         for snap in g_data.get_snaps(self.session_name, g_data.get_user()):
348                                 self.sendLine(snap)
349                         self.sendLine("OK")
350                         self.state = "IDLE"
351                 elif data == "SNAPSHOT":
352                         self.state = "SNAPSHOT"
353                 elif data == "SOUNDFILE":
354                         self.state = "SOUNDFILE"
355                 elif data == "SOUNDFILE_HEADER":
356                         self.state = "SOUNDFILE_HEADER"
357                 elif data == "SOUNDFILE_SIZE":
358                         self.state = "SOUNDFILE_SIZE"
359         
360         def file_done(self, data):
361                 print "server: file done"
362                 self.file.close()
363                 self.state = "IDLE"
364         
365 class ExchangeServerFactory(protocol.ServerFactory):
366         protocol = ExchangeServer
367         
368         def __init__(self):
369                 pass
370
371 class ExchangeClient (LineReceiver):
372         def __init__(self, session_name, collab_name, snap_name, debug_mode):
373                 self.session_name = session_name
374                 self.collab_name = collab_name
375                 self.snap_name = snap_name
376                 self.debug_mode = debug_mode
377                 self.state = "IDLE"
378         
379         def connectionLost(self, reason):
380                 g_display.show_status("Connection lost")
381         
382         def connectionMade(self):
383                 g_display.show_status("Connection made")
384                 self.state = "SESSION"
385                 self.sendLine("SESSION")
386                 self.sendLine(self.session_name)
387         
388         def rawDataReceived(self, data):
389                 self.file.write(data)
390                 self.received += len(data)
391                 print self.received, self.filesize
392                 if self.received >= self.filesize:
393                         self.setLineMode()
394                         self.file.close()
395                         g_data.rescan_session(self.session_name)
396                         if self.state == "SNAPSHOT":
397                                 self.sounds = get_sound_list(self.filename)
398                                 if len(self.sounds):
399                                         self.sound_index = 0
400                                         if self.debug_mode:
401                                                 self.state = "SOUNDFILE_HEADER"
402                                                 self.sendLine("SOUNDFILE_HEADER")
403                                         else:
404                                                 self.state = "SOUNDFILE"
405                                                 self.sendLine("SOUNDFILE")
406                                         self.sendLine(self.sounds[self.sound_index])
407                                 else:
408                                         self.transport.loseConnection()
409                         elif self.state == "SOUNDFILE":
410                                 self.sound_index += 1
411                                 if self.sound_index > len(self.sounds)-1:
412                                         self.transport.loseConnection()
413                                 else:
414                                         self.sendLine("SOUNDFILE")
415                                         self.sendLine(self.sounds[self.sound_index])
416                         elif self.state == "SOUNDFILE_HEADER":
417                                 self.state = "SOUNDFILE_SIZE"
418                                 self.sendLine("SOUNDFILE_SIZE")
419                                 self.sendLine(self.sounds[self.sound_index])
420         
421         def lineReceived(self, data):
422                 print "client: ", data
423                 
424                 if data == "ERROR":
425                         self.state = "ERROR"
426                 elif data == "OK":
427                         if self.state == "SESSION":
428                                 if self.snap_name:
429                                         self.state = "SNAPSHOT"
430                                         self.sendLine("SNAPSHOT")
431                                         self.sendLine(self.snap_name)
432                                 else:
433                                         self.state = "SNAPS"
434                                         self.sendLine("SNAPS")
435                         elif self.state == "SNAPS":
436                                 self.transport.loseConnection()
437                         elif self.state == "SNAPSHOT":
438                                 self.setRawMode()
439                                 self.filename = g_data.get_session_path(self.session_name)+'/'+self.snap_name+'.ardour'
440                                 self.file = open(self.filename, 'w')
441                                 self.received = 0
442                         elif self.state == "SOUNDFILE" or self.state == "SOUNDFILE_HEADER":
443                                 self.setRawMode()
444                                 self.filename = g_data.get_session_path(self.session_name)+'/sounds/'+self.sounds[self.sound_index]
445                                 self.file = open(self.filename, 'w')
446                                 self.received = 0
447                 elif self.state == "ERROR":
448                         raise_error(data, g_display.window)
449                 elif self.state == "SNAPS":
450                         g_data.add_snap(self.session_name, self.collab_name, data)
451                 elif self.state == "SNAPSHOT":
452                         self.filesize = int(data)
453                 elif self.state == "SOUNDFILE":
454                         self.filesize = int(data)
455                 elif self.state == "SOUNDFILE_HEADER":
456                         self.filesize = int(data)
457                 elif self.state == "SOUNDFILE_SIZE":
458                         append_empty_data(self.filename, int(data))
459                         self.sound_index += 1
460                         if self.sound_index > len(self.sounds)-1:
461                                 self.transport.loseConnection()
462                         else:
463                                 self.state = "SOUNDFILE_HEADER"
464                                 self.sendLine("SOUNDFILE_HEADER")
465                                 self.sendLine(self.sounds[self.sound_index])
466
467 class ExchangeClientFactory(protocol.ClientFactory):
468         def buildProtocol(self, addr):
469                 return ExchangeClient(self.session_name, self.collab_name, self.snap_name, self.debug_mode)
470         
471         def clientConnectionFailed(self, connector, reason):
472                 raise_error('Connection failed: ' + reason.getErrorMessage(), g_display.window)
473                 g_display.show_status('Connection failed')
474         
475         def __init__(self, session_name, collab_name, snap_name, debug_mode):
476                 self.session_name = session_name
477                 self.collab_name = collab_name
478                 self.snap_name = snap_name
479                 self.debug_mode = debug_mode
480
481 class HelperWin(object):
482         def delete_me(self, window):
483                 self = 0
484
485 class Preferences(HelperWin):
486         def __init__(self):
487                 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
488                 self.window.set_title('Preferences')
489                 self.window.connect('destroy', self.delete_me)
490                 self.window.set_position(gtk.WIN_POS_MOUSE)
491                 
492                 main_box = gtk.VBox()
493                 self.window.add(main_box)
494                 
495                 hbox1 = gtk.HBox()
496                 label1 = gtk.Label("User")
497                 self.user = gtk.Entry()
498                 self.user.set_text(g_data.get_user())
499                 hbox1.pack_start(label1)
500                 hbox1.pack_start(self.user)
501                 main_box.pack_start(hbox1)
502                 
503                 ok_btn = gtk.Button("Ok")
504                 ok_btn.connect('clicked', self.ok_clicked)
505                 main_box.pack_start(ok_btn)
506                 
507                 self.window.show_all()
508                 
509         def ok_clicked(self, btn):
510                 g_data.set_user(self.user.get_text())
511                 self.window.hide_all()
512                 
513         def show_all(self):
514                 self.window.show_all()
515
516 class AddCollaborator(HelperWin):
517         def __init__(self, session):
518                 self.session_name = session
519                 
520                 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
521                 self.window.set_title('Fetch Session')
522                 self.window.connect('destroy', self.delete_me)
523                 self.window.set_position(gtk.WIN_POS_MOUSE)
524                 
525                 main_box = gtk.VBox()
526                 self.window.add(main_box)
527                 
528                 hbox0 = gtk.HBox()
529                 label0 = gtk.Label("Collaborator")
530                 self.collab = gtk.Entry()
531                 self.collab.connect('key-release-event', self.key_press)
532                 hbox0.pack_start(label0)
533                 hbox0.pack_start(self.collab)
534                 main_box.pack_start(hbox0)
535                 
536                 hbox1 = gtk.HBox()
537                 label1 = gtk.Label("IP Address")
538                 self.address = gtk.Entry()
539                 self.address.connect('key-release-event', self.key_press)
540                 hbox1.pack_start(label1)
541                 hbox1.pack_start(self.address)
542                 main_box.pack_start(hbox1)
543                 
544                 hbox2 = gtk.HBox()
545                 label2 = gtk.Label("Port Number")
546                 self.port = gtk.Entry()
547                 self.port.connect('key-release-event', self.key_press)
548                 self.port.set_text(str(g_data.port))
549                 hbox2.pack_start(label2)
550                 hbox2.pack_start(self.port)
551                 main_box.pack_start(hbox2)
552                 
553                 hbox3 = gtk.HBox()
554                 label3 = gtk.Label("Username")
555                 label3.set_sensitive(False)
556                 self.username = gtk.Entry()
557                 self.username.set_sensitive(False)
558                 hbox3.pack_start(label3)
559                 hbox3.pack_start(self.username)
560                 main_box.pack_start(hbox3)
561                 
562                 hbox4 = gtk.HBox()
563                 label4 = gtk.Label("Password")
564                 label4.set_sensitive(False)
565                 self.password = gtk.Entry()
566                 self.password.set_sensitive(False)
567                 hbox4.pack_start(label4)
568                 hbox4.pack_start(self.password)
569                 main_box.pack_start(hbox4)
570                 
571                 self.ok_btn = gtk.Button(gtk.STOCK_OK)
572                 self.ok_btn.set_use_stock(True)
573                 self.ok_btn.connect('clicked', self.ok_clicked)
574                 self.ok_btn.set_sensitive(False)
575                 main_box.pack_start(self.ok_btn)
576                 
577                 self.window.show_all()
578         
579         def key_press(self, event, data):
580                 if self.collab.get_text() and self.address.get_text() and self.port.get_text():
581                         self.ok_btn.set_sensitive(True)
582                 else:
583                         self.ok_btn.set_sensitive(False)
584                 return True
585         
586         def ok_clicked(self, btn):
587                 self.window.hide_all()
588                 g_data.add_collab(self.session_name, self.collab.get_text(), self.address.get_text(), int(self.port.get_text()))
589                 self.collab.set_text('')
590                 self.address.set_text('')
591                 self.port.set_text('')
592                 self.username.set_text('')
593                 self.password.set_text('')
594         
595         def show_all(self):
596                 self.window.show_all()
597
598 class ArdourShareWindow(object):
599         def menuitem_cb(self, window, action, widget):
600                 print self, window, action, widget
601         
602         def add_collaborator_cb(self, window, action, widget):
603                 if self.session:
604                         self.add_session = AddCollaborator(self.session)
605         
606         def fetch_snapshot_cb(self, window, action, widget):
607                 if self.session and self.collab and self.collab != g_data.get_user():
608                         client = ExchangeClientFactory(self.session, self.collab, self.snap, g_data.debug_mode)
609                         reactor.connectTCP(g_data.get_collab_ip(self.session, self.collab), g_data.port, client)
610         
611         def preferences_cb(self, window, action, widget):
612                 self.preferences = Preferences()
613         
614         def add_session_ok_file_btn_clicked(self, w):
615                 filename = self.file_sel.get_filename()
616                 if filename.endswith(".ardour"):
617                         g_data.add_session(filename[0:filename.rfind("/")+1])
618                         self.update_session_view()
619                 else:
620                         raise_error("Not an Ardour session", self.window)
621                 self.file_sel.destroy()
622         
623         def add_session_cb(self, window, action, widget):
624                 if g_data.get_user():
625                         self.file_sel = gtk.FileSelection("Add Session...")
626                         self.file_sel.ok_button.connect("clicked", self.add_session_ok_file_btn_clicked)
627                         self.file_sel.cancel_button.connect("clicked", lambda w: self.file_sel.destroy())
628                         self.file_sel.connect("destroy", lambda w: self.file_sel.destroy())
629                         self.file_sel.show()
630                 else:
631                         raise_error("Set the user name in the preferences first", self.window)
632         
633         def create_session_cb(self, window, action, widget):
634                 if g_data.get_user():
635                         self.file_sel = gtk.FileSelection("Create Session...")
636                         self.file_sel.ok_button.connect("clicked", self.create_file_ok_btn_clicked)
637                         self.file_sel.cancel_button.connect("clicked", lambda w: self.file_sel.destroy())
638                         self.file_sel.connect("destroy", lambda w: self.file_sel.destroy())
639                         self.file_sel.show()
640                 else:
641                         raise_error("Set the user name in the preferences first", self.window)
642         
643         def create_file_ok_btn_clicked(self, w):
644                 filename = self.file_sel.get_filename()
645                 if len(filename) > 0:
646                         g_data.create_session(filename)
647                         self.update_session_view()
648                 else:
649                         raise_error("Not an Ardour session", self.window)
650                 self.file_sel.destroy()
651         
652         def update_session_view(self):
653                 self.session_model.clear()
654                 for session in g_data.get_sessions():
655                         self.session_model.set(self.session_model.append(), 0, session)
656         
657         def update_collab_view(self):
658                 self.collab_model.clear()
659                 for collab in g_data.get_collabs(self.session):
660                         self.collab_model.set(self.collab_model.append(), 0, collab)
661         
662         def update_snap_view(self):
663                 self.snap_model.clear()
664                 for snap in g_data.get_snaps(self.session, self.collab):
665                         self.snap_model.set(self.snap_model.append(), 0, snap)
666         
667         def cb_session_selection_changed(self, selection_object):
668                 selected = []
669                 selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path))
670                 for x in selected:
671                         self.session = self.session_model[x][0]
672                 self.selected_type = "session"
673                 self.update_collab_view()
674         
675         def cb_collab_selection_changed(self, selection_object):
676                 selected = []
677                 selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path))
678                 for x in selected:
679                         self.collab = self.collab_model[x][0]
680                 self.selected_type = "collab"
681                 self.update_snap_view()
682         
683         def cb_snap_selection_changed(self, selection_object):
684                 selected = []
685                 selection_object.selected_foreach(lambda model, path, iter, sel = selected: sel.append(path))
686                 for x in selected:
687                         self.snap = self.snap_model[x][0]
688                 self.selected_type = "snap"
689         
690         def delete_cb(self, window, action, widget):
691                 if self.selected_type == "session":
692                         g_data.delete_session(self.session)
693                         self.session = ""
694                         self.collab = ""
695                         self.snap = ""
696                 elif self.selected_type == "collab":
697                         g_data.delete_collab(self.session, self.collab)
698                         self.collab = ""
699                         self.snap = ""
700                 elif self.selected_type == "snap":
701                         g_data.delete_snap(self.session, self.collab, self.snap)
702                         self.snap = ""
703                 
704                 self.update_session_view()
705                 self.update_collab_view()
706                 self.update_snap_view()
707                 self.selected_type = ""
708                 
709         def show_status(self, text):
710                 mid = self.status_bar.push(self._status_cid, text)
711                 if self._status_mid:
712                         self.status_bar.remove(self._status_cid, self._status_mid)
713                 self._status_mid = mid
714         
715         def __init__(self):
716                 self.selected_type = ""
717                 self.session = ""
718                 self.collab = g_data.get_user()
719                 self.snap = ""
720                 
721                 self.preferences = 0
722                 self.add_collab = 0
723                 self.add_session = 0
724                 
725                 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
726                 self.window.set_title('Session Exchange')
727                 self.window.set_size_request(400, 200)
728                 self.window.connect('destroy', lambda win: gtk.main_quit())
729                 self.window.set_position(gtk.WIN_POS_MOUSE)
730                 
731                 accel_group = gtk.AccelGroup()
732                 self.window.add_accel_group(accel_group)
733                 
734                 main_box = gtk.VBox()
735                 self.window.add(main_box)
736                 
737                 menu_items = (
738                         ('/_File',            None,         None,             0, '<Branch>'),
739                         ('/File/_Add Session...','<control>A', self.add_session_cb, 0, ''),
740                         ('/File/Create _Session...', '<control>S', self.create_session_cb, 0, ''),
741                         ('/File/sep1',        None,         None,             0, '<Separator>'),
742                         ('/File/_Quit',       '<control>Q', gtk.main_quit,     0, '<StockItem>', gtk.STOCK_QUIT),
743                         ('/_Edit',            None,         None,             0, '<Branch>' ),
744                         ('/Edit/Cu_t',        '<control>X', self.menuitem_cb, 0, '<StockItem>', gtk.STOCK_CUT),
745                         ('/Edit/_Copy',       '<control>C', self.menuitem_cb, 0, '<StockItem>', gtk.STOCK_COPY),
746                         ('/Edit/_Paste',      '<control>V', self.menuitem_cb, 0, '<StockItem>', gtk.STOCK_PASTE),
747                         ('/Edit/_Delete',     None,         self.delete_cb, 0, '<StockItem>', gtk.STOCK_DELETE),
748                         ('/Edit/sep1',        None,         None,             0, '<Separator>'),
749                         ('/Edit/Add Colla_borator...','<control>B', self.add_collaborator_cb,0,''),
750                         ('/Edit/_Fetch Snapshot','<control>F', self.fetch_snapshot_cb,0,''),
751                         ('/Edit/sep1',        None,         None,             0, '<Separator>'),
752                         ('/Edit/_Preferences...','<control>P', self.preferences_cb, 0, '')
753                 )
754                 
755                 #need to hold a reference to the item_factory or the menubar will disappear.
756                 self.item_factory = gtk.ItemFactory(gtk.MenuBar, '<main>', accel_group)
757                 self.item_factory.create_items(menu_items, self.window)
758                 main_box.pack_start(self.item_factory.get_widget('<main>'), gtk.FALSE)
759                 
760                 pane1 = gtk.HPaned()
761                 pane2 = gtk.HPaned()
762                 pane1.pack2(pane2, gtk.TRUE, gtk.FALSE)
763                 
764                 scroll1 = gtk.ScrolledWindow()
765                 scroll1.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
766                 pane1.pack1(scroll1, gtk.TRUE, gtk.FALSE)
767                 scroll2 = gtk.ScrolledWindow()
768                 scroll2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
769                 pane2.pack1(scroll2, gtk.TRUE, gtk.FALSE)
770                 scroll3 = gtk.ScrolledWindow()
771                 scroll3.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
772                 pane2.pack2(scroll3, gtk.TRUE, gtk.FALSE)
773                 
774                 self.session_model = gtk.ListStore(gobject.TYPE_STRING)
775                 view1 = gtk.TreeView(self.session_model)
776                 column1 = gtk.TreeViewColumn('Sessions', gtk.CellRendererText(), text=0)
777                 view1.append_column(column1)
778                 self.session_selection = view1.get_selection()
779                 self.session_selection.connect("changed", self.cb_session_selection_changed)
780                 scroll1.add(view1)
781                 
782                 self.update_session_view()
783                 
784                 self.collab_model = gtk.ListStore(gobject.TYPE_STRING)
785                 view2 = gtk.TreeView(self.collab_model)
786                 column2 = gtk.TreeViewColumn('Collaborators', gtk.CellRendererText(), text=0)
787                 view2.append_column(column2)
788                 self.collab_selection = view2.get_selection()
789                 self.collab_selection.connect("changed", self.cb_collab_selection_changed)
790                 scroll2.add(view2)
791                 
792                 self.snap_model = gtk.ListStore(gobject.TYPE_STRING)
793                 view3 = gtk.TreeView(self.snap_model)
794                 column3 = gtk.TreeViewColumn('Snapshots', gtk.CellRendererText(), text=0)
795                 view3.append_column(column3)
796                 self.snap_selection = view3.get_selection()
797                 self.snap_selection.connect("changed", self.cb_snap_selection_changed)
798                 scroll3.add(view3)
799                 
800                 main_box.pack_start(pane1, gtk.TRUE, gtk.TRUE)
801                 
802                 self.status_bar = gtk.Statusbar()
803                 main_box.pack_start(self.status_bar, gtk.FALSE)
804                 self._status_cid = self.status_bar.get_context_id('display')
805                 self._status_mid = ''
806                 
807                 self.window.show_all()
808
809 def print_help():
810         print """
811         -h, --help
812         -n, --no-server          Only act as a client
813         -p, --port <port number> Defaults to 8970
814         -d, --debug              Infers audio files.  For debugging Ardour.
815         -v, --version            Version
816         """
817         sys.exit(2)
818
819 def main():
820         try:
821                 opts, args = getopt.getopt(sys.argv[1:], "hp:ndv", ["help", "port=", "no-server", "debug", "version"])
822         except getopt.GetoptError:
823                 print_help()
824         
825         server = True
826         for o, a in opts:
827                 if o in ("-h", "--help"):
828                         print_help()
829                 if o in ("-d", "--debug"):
830                         g_display.window.set_title('Session Exchange: Debug Mode')
831                         g_data.debug_mode = True
832                 if o in ("-p", "--port"):
833                         g_data.port = int(a)
834                 if o in ("-n", "--no-server"):
835                         server = False
836                 if o in ("-v", "--version"):
837                         print VERSION
838                         sys.exit(2)
839         
840         if (server):
841                 try:
842                         reactor.listenTCP(g_data.port, ExchangeServerFactory())
843                 except twisted.internet.error.CannotListenError:
844                         print "Can not listen on a port number under 1024 unless run as root"
845                         sys.exit(2)
846
847         reactor.run()
848
849         g_data.close()
850
851 # global objects
852 g_data = Data()
853 g_display = ArdourShareWindow()
854
855 if __name__ == '__main__':
856         main()