[Rhythmbox-devel] [RFC] Multiple library locations



Hi all,

currently, if you use multiple library locations, browser state for
additional library sources is lost when rhythmbox is restarted.
I made a patch making additional library sources preserve state across
restarts[1].

Until now, additional library sources can only be added by editing the
gconf key "/apps/rhythmbox/library_locations" using e.g. gconf-editor -
so I am planing to edit the preferences dialog to allow selection of
multiple library locations (see attached python demo).

There are some implementation details that have to be decided upon:

* If you have configured multiple library locations, and you choose to
remove one of them, what should happen to the songs from the removed
location? Should they be removed from the library or should they be
kept? If they are removed, what about the metadata (play counts,
ratings, etc.)?
Currently, if you remove a song from the library (by right-clicking a
song and selecting "Remove"), the removal time is recorded in RhythmDB
and metadata is kept for a certain time (configurable via the gconf key
"/apps/rhythmbox/grace_period"). So if you add the song again to your
library during grace_time, metadata will still be there. Should this
apply to songs from a removed library location as well?

* If you rip a CD using rb, where should the files be saved? Currently,
they are put in the first library location. Should there be some ui to
choose where to put them?

* Should there be drag and drop support, allowing to move files between
different library sources within rb?

Please try the patch from [1] and the attached python demo of the new
preferences dialog I have in mind, and tell me your thoughts.


Regards,

Chris

[1] http://bugzilla.gnome.org/show_bug.cgi?id=523162
#!/usr/bin/python

import gobject
import gtk
import gtk.glade
import urlparse

LIBRARY_PREFS_GLADE = "library-prefs-example.glade"

COL_LIBRARY_LOCATION = 0
COL_NEW_FILES_LOCATION = 1

class UI:

	def __init__ (self):
		self.gladeXML = gtk.glade.XML (LIBRARY_PREFS_GLADE)
		self.gladeXML.signal_autoconnect (self)
		locations_tv = self.gladeXML.get_widget ("library_locations_treeview")
		self.library_locations_filechooserdialog = self.gladeXML.get_widget ("library_locations_filechooserdialog")
		self.remove_library_location_messagedialog = self.gladeXML.get_widget ("remove_library_location_messagedialog")
		self.keep_metadata_checkbutton = self.gladeXML.get_widget ("keep_metadata_checkbutton")
		
		self.locations = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
		self.locations_tree_selection = locations_tv.get_selection ()
		self.locations_tree_selection.set_mode (gtk.SELECTION_MULTIPLE)
		locations_tv.set_model (self.locations)
		locations_column = gtk.TreeViewColumn ("Library Locations")
		locations_column.set_expand (True)
		locations_tv.append_column (locations_column)
		cr = gtk.CellRendererText ()
		locations_column.pack_start (cr, True)
		locations_column.add_attribute (cr, 'text', COL_LIBRARY_LOCATION)

		new_files_column = gtk.TreeViewColumn ("Put new files here")
		new_files_column.set_expand (False)
		locations_tv.append_column (new_files_column)
		cr = gtk.CellRendererToggle ()
		cr.set_radio (True)
		cr.connect ("toggled", self.on_new_files_location_toggled)
		new_files_column.pack_start (cr, True)
		new_files_column.add_attribute (cr, 'active', COL_NEW_FILES_LOCATION)

	def run (self):
		gtk.main ()

	def run_library_locations_filechooserdialog (self):
		locations = []
		resp = self.library_locations_filechooserdialog.run ()
		if resp == gtk.RESPONSE_OK:
			locations = self.library_locations_filechooserdialog.get_uris ()
		self.library_locations_filechooserdialog.hide ()
		return locations

	def new_files_location_toggled_foreach_func (self, model, path, it, toggled):
		if int (path[0]) == int (toggled):
			model.set_value (it, COL_NEW_FILES_LOCATION, True)
		else:
			model.set_value (it, COL_NEW_FILES_LOCATION, False)
		return False

	def on_new_files_location_toggled (self, cr, path):
		self.locations.foreach (self.new_files_location_toggled_foreach_func, path)
		return False

	def on_quit (self, *args, **kwargs):
		gtk.main_quit ()
	
	def location_is_valid_foreach_func (self, model, path, it, data):
		loc = urlparse.urlsplit (model.get_value (it, COL_LIBRARY_LOCATION))
		new = data[0]
		if new.scheme == loc.scheme:
			if new.path == loc.path or new.path.startswith (loc.path) or loc.path.startswith (new.path):
				data[1] = False
				return True
	
	def location_is_valid (self, location):
		data = [urlparse.urlsplit (location), True]
		self.locations.foreach (self.location_is_valid_foreach_func, data)
		return data[1]
		
	def update_new_files_location_foreach_func (self, model, path, it, found):
		if model.get_value (it, COL_NEW_FILES_LOCATION):
			found[0] = it
			return False
	
	def update_new_files_location (self):
		found = [None]
		self.locations.foreach (self.update_new_files_location_foreach_func, found)
		if not found[0]:
			gobject.idle_add (self.on_new_files_location_toggled, None, 0)
		return False

	def on_button_add_clicked (self, button):
		locations = self.run_library_locations_filechooserdialog ()
		location_added = False
		invalid_locations = []
		for loc in locations:
			if self.location_is_valid (loc):
				self.locations.append ([loc, False])
				location_added = True
			else:
				invalid_locations.append (loc)

		if location_added:
			gobject.idle_add (self.update_new_files_location)

		if invalid_locations:
			dlg = gtk.MessageDialog (parent=self.gladeXML.get_widget ("window1"),
					flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
					type=gtk.MESSAGE_ERROR,
					buttons=gtk.BUTTONS_OK,
					message_format="Some location(s) could not be added")
			dlg.format_secondary_text ("The following location(s) could not be added to the list of library locations:\n\n"
					"%s\n\n"
					"Possible reasons: either the location itself, "
					"one of its subfolders, or one of its parent folders is already added.\n"
					"Nested folders can not be added as library locations." % 
					"\n".join (invalid_locations))
			dlg.run ()
			dlg.destroy ()
	
	def on_button_remove_clicked (self, button):
		resp = self.remove_library_location_messagedialog.run ()
		if resp == gtk.RESPONSE_OK:
			keep_metadata = self.keep_metadata_checkbutton.get_active ()
			if keep_metadata:
				print "Would remove songs from library, keeping metadata in rhythmdb."
			else:
				print "Would remove songs from library, removing metadata from rhythmdb."
			(model, pathlist) = self.locations_tree_selection.get_selected_rows ()
			pathlist.reverse ()
			new_files_location_removed = False
			for path in pathlist:
				it = model.get_iter (path)
				new_files_location_removed = (new_files_location_removed
						or model.get_value (it, COL_NEW_FILES_LOCATION))
				model.remove (it)
			if new_files_location_removed:
				gobject.idle_add (self.update_new_files_location)
		self.remove_library_location_messagedialog.hide ()


if __name__ == '__main__':
	ui = UI ()
	ui.run ()

Attachment: library-prefs-example.glade
Description: application/glade



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]