riffle is simpler and works better
[riffle.git] / sync
blobcfef0cfb6ada23cb63da0f9ce9385ea41007f9b2
1 #!/usr/bin/env python
2 import gtk, gtk.glade, gobject
3 import os, sys
4 import conf
6 from datetime import datetime
8 os.environ['DJANGO_SETTINGS_MODULE'] = 'riffle.settings'
9 from riffle.catcher.models import *
10 from util.riffle import riffle
12 try: import mutagen
13 except ImportError:
14 sys.path.append(os.path.expanduser("~/src/ql/trunk/mutagen"))
15 import mutagen
17 SUPPORTED_EXTENSIONS = '.mp3'
19 def list_files(top, ext):
20 if isinstance(ext,list):
21 accept = lambda e: e in ext
22 else:
23 accept = lambda e: e == ext
24 for r,ds,fs in os.walk(top):
25 for f in fs:
26 if accept( os.path.splitext(f)[1] ):
27 yield os.path.join(r,f)
29 def get_device_tracks():
30 return list_files(conf.device_dir, SUPPORTED_EXTENSIONS)
32 def get_incoming_tracks():
33 return list_files(conf.media_dir, SUPPORTED_EXTENSIONS)
35 class Sync:
36 def __init__(self):
37 self.window = gtk.Window()
38 self.window.set_title('sync')
39 self.window.connect('destroy', gtk.main_quit)
41 vbox = gtk.VBox()
43 scroll = gtk.ScrolledWindow()
44 scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
45 vbox.add(scroll)
47 self.main_list = gtk.TreeView()
48 self.main_list.set_reorderable(True)
49 self.main_list.set_headers_clickable(True)
50 self.main_list.connect("key-press-event", self.on_key_press)
51 self.main_list.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
53 text_renderer = gtk.CellRendererText()
54 column = gtk.TreeViewColumn("date", text_renderer)
55 text_renderer.set_property('xalign', 1.0)
56 column.set_cell_data_func(text_renderer, self.render_date)
57 self.main_list.append_column( column )
59 text_renderer = gtk.CellRendererText()
60 column = gtk.TreeViewColumn("riffle", text_renderer)
61 column.set_attributes(text_renderer, text=6)
62 column.set_sort_column_id(6)
63 column.set_cell_data_func(text_renderer, self.set_row_bg)
64 self.main_list.append_column( column )
66 text_renderer = gtk.CellRendererText()
67 column = gtk.TreeViewColumn("feed", text_renderer)
68 column.set_attributes(text_renderer, text=3)
69 column.set_sort_column_id(3)
70 column.set_cell_data_func(text_renderer, self.set_row_bg)
71 self.main_list.append_column( column )
73 text_renderer = gtk.CellRendererText()
74 column = gtk.TreeViewColumn("title", text_renderer)
75 column.set_cell_data_func(text_renderer, self.set_row_bg)
76 column.set_attributes(text_renderer, text=4)
77 self.main_list.append_column( column )
79 scroll.add(self.main_list)
81 go_button = gtk.Button('Go!')
82 go_button.connect("clicked", self.do_sync)
83 vbox.add_with_properties(go_button,'expand',False)
85 self.window.add(vbox)
87 self.window.resize(600,800)
88 self.window.show_all()
90 def set_row_bg(self, column, cell, model, iter, user_data = None):
91 p = model.get_value(iter, 0)
92 cell.set_property('background',
93 '#d0d0f0' if p.startswith(conf.device_dir) else '#90ff90')
95 def render_date(self, column, cell, model, iter, user_data = None):
96 dt = model.get_value(iter, 5)
97 cell.set_property('text', dt.strftime('%A, %d %B %Y'))
99 def add_entries(self, lst):
100 if self.main_list.get_model() is None:
101 self.main_list.set_model(
102 gtk.ListStore(str,object,object,str,str,object,int) )
103 for i in lst:
104 self.main_list.get_model().append( i )
106 def do_sync(self,*argv):
107 to_copy = [row for row in self.main_list.get_model()
108 if row[0].startswith(conf.media_dir)]
109 CopyProgress(to_copy, conf.device_dir)
111 def on_key_press(self,widget,event,*argv):
112 if event.keyval in (gtk.keysyms.Delete , gtk.keysyms.KP_Delete):
113 self.delete_selected()
115 def want_file_gone(self, title):
116 print "Do you also want to delete track '%s' from incoming directory?" % title
117 return False
119 def delete_selected(self):
120 model,indexes = self.main_list.get_selection().get_selected_rows()
121 iters = [model.get_iter(i) for i in indexes]
122 for i in iters:
123 if model[i][0].startswith(conf.media_dir) \
124 or self.want_file_gone(model[i][0]):
125 os.remove(model[i][0])
126 model.remove( i )
128 class CopyProgress:
129 xml = gtk.glade.XML('sync.glade', root='copy_progress_dialog')
130 dialog = xml.get_widget('copy_progress_dialog')
131 total_progress = xml.get_widget('total_progress')
132 file_progress = xml.get_widget('file_progress')
133 dialog.hide()
135 buf_len = 128 * 1024
137 def __init__(self, sources, dest):
138 if len(sources) == 0: return
139 self.total_progress.set_fraction(0)
140 self.file_progress.set_fraction(0)
141 self.dialog.show_all()
143 self.sources = sources
144 self.total_sources_count = len(sources)
145 self.dest = dest
146 self.source_file = None
147 self.dest_file = None
149 gobject.idle_add(self.copy_task)
151 def copy_task(self):
152 if self.source_file is None:
153 if len(self.sources) == 0:
154 self.dialog.hide() # done
155 return False
156 self.row = self.sources[0]
157 self.source = self.row[0]
158 self.sources = self.sources[1:]
159 self.source_file = open(self.source, 'r')
160 self.new_path = os.path.join( self.dest,
161 os.path.basename( self.source ))
162 self.dest_file = open(self.new_path, 'w')
163 self.current_offset = 0
164 self.current_size = os.stat( self.source ).st_size
165 self.file_progress.set_fraction( 0.0 )
166 chunk_size = min( self.buf_len,
167 self.current_size - self.current_offset )
168 self.dest_file.write( self.source_file.read( chunk_size ))
169 self.current_offset += chunk_size
170 self.file_progress.set_fraction(
171 float(self.current_offset) / self.current_size )
172 if self.current_offset == self.current_size:
173 self.dest_file.close()
174 self.dest_file = None
175 self.source_file.close()
176 self.source_file = None
177 os.remove( self.source )
178 self.total_progress.set_fraction( 1.0 -
179 (float(len(self.sources)) / self.total_sources_count))
180 self.row[0] = self.new_path
181 return True
183 def track_columns(path):
184 feed = None
185 title = os.path.basename(path)
186 timestamp = datetime.fromtimestamp( os.stat(path).st_mtime )
188 try:
189 tags = mutagen.File(path).tags
190 if 'TALB' in tags:
191 feed = str(tags['TALB'])
192 if 'TIT2' in tags:
193 title = str(tags['TIT2'])
194 except:
195 tags = None
196 try:
197 ep = Episode.objects.get(local_path = os.path.basename(path))
198 feed = str(ep.feed)
199 timestamp = ep.timestamp
200 except Episode.DoesNotExist:
201 ep = None
202 return [path, tags, ep, feed, title, timestamp]
204 app = Sync()
206 from itertools import chain
208 deck_dict = {}
210 for x in [track_columns(t)
211 for t in chain( get_device_tracks(),get_incoming_tracks()) ]:
212 feed = x[3]
213 if feed not in deck_dict:
214 deck_dict[feed] = []
215 deck_dict[feed].append(x)
217 deck = [deck_dict[f] for f in deck_dict]
218 map(lambda lst: lst.sort(key=lambda x: x[5]), deck)
219 riffled = riffle(deck)
220 for i in xrange(len(riffled)):
221 riffled[i].append(i+1)
223 app.add_entries( riffled )
225 gtk.main()