/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2003,2004 Colin Walters * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * The Rhythmbox authors hereby grant permission for non-GPL compatible * GStreamer plugins to be used and distributed together with GStreamer * and Rhythmbox. This permission is above and beyond the permissions granted * by the GPL license by which Rhythmbox is covered. If you modify this code * you may extend this exception to your version of the code, but you are not * obligated to do so. If you do not wish to do so, delete this exception * statement from your version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * */ /** * SECTION:rb-playlist-manager * @short_description: Playlist management object * * The playlist manager loads and saves the on-disk playlist file, provides * UI actions and a DBus interface for dealing with playlists, and internal * interfaces for creating playlists. */ #include "config.h" #include #include /* rename() */ #include /* unlink() */ #include #include #include #include "rb-playlist-manager.h" #include "rb-playlist-source.h" #include "rb-static-playlist-source.h" #include "rb-auto-playlist-source.h" #include "rb-play-queue-source.h" #include "rb-sourcelist.h" #include "rb-sourcelist-model.h" #include "rb-query-creator.h" #include "totem-pl-parser.h" #include "rb-file-helpers.h" #include "rb-debug.h" #include "rb-dialog.h" #include "rhythmdb.h" #include "rb-stock-icons.h" #include "eel-gconf-extensions.h" #include "rb-builder-helpers.h" #include "rb-util.h" #define RB_PLAYLIST_MGR_VERSION (xmlChar *) "1.0" #define RB_PLAYLIST_MGR_PL (xmlChar *) "rhythmdb-playlists" static void rb_playlist_manager_class_init (RBPlaylistManagerClass *klass); static void rb_playlist_manager_init (RBPlaylistManager *mgr); static void rb_playlist_manager_dispose (GObject *object); static void rb_playlist_manager_finalize (GObject *object); static void rb_playlist_manager_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void rb_playlist_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void rb_playlist_manager_cmd_load_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_save_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_save_queue (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_new_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_new_automatic_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_shuffle_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_rename_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_delete_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_edit_automatic_playlist (GtkAction *action, RBPlaylistManager *mgr); static void rb_playlist_manager_cmd_queue_playlist (GtkAction *action, RBPlaylistManager *mgr); struct RBPlaylistManagerPrivate { RhythmDB *db; RBShell *shell; RBSource *selected_source; char *playlists_file; RBSourceList *sourcelist; GtkActionGroup *actiongroup; GtkUIManager *uimanager; GtkWindow *window; RBStaticPlaylistSource *loading_playlist; gint dirty; gint saving; GMutex *saving_mutex; }; enum { PROP_0, PROP_PLAYLIST_NAME, PROP_SHELL, PROP_SOURCE, PROP_SOURCELIST, }; enum { PLAYLIST_ADDED, PLAYLIST_CREATED, PLAYLIST_LOAD_START, PLAYLIST_LOAD_FINISH, LAST_SIGNAL, }; static guint rb_playlist_manager_signals[LAST_SIGNAL] = { 0 }; typedef struct { const gchar *description; /* NULL terminated array of extensions for this file format. The first * one is the prefered extension for files of this type. */ const gchar **extensions; const char *mimetype; const RBPlaylistExportType type; } RBPlaylistExportFilter; static const char *m3u_extensions [] = {"m3u", NULL}; static const char *pls_extensions [] = {"pls", NULL}; static const char *xspf_extensions[] = {"xspf", NULL}; static RBPlaylistExportFilter playlist_formats[] = { {N_("MPEG Version 3.0 URL"), m3u_extensions, "audio/x-mpegurl", RB_PLAYLIST_EXPORT_TYPE_M3U}, {N_("Shoutcast playlist"), pls_extensions, "audio/x-scpls", RB_PLAYLIST_EXPORT_TYPE_PLS}, {N_("XML Shareable Playlist Format"), xspf_extensions, "application/xspf+xml", RB_PLAYLIST_EXPORT_TYPE_XSPF}, }; static GtkActionEntry rb_playlist_manager_actions [] = { /* Submenu of Music */ { "Playlist", NULL, N_("_Playlist") }, { "MusicPlaylistNewPlaylist", RB_STOCK_PLAYLIST_NEW, N_("_New Playlist..."), "N", N_("Create a new playlist"), G_CALLBACK (rb_playlist_manager_cmd_new_playlist) }, { "MusicPlaylistNewAutomaticPlaylist", RB_STOCK_AUTO_PLAYLIST_NEW, N_("New _Automatic Playlist..."), NULL, N_("Create a new automatically updating playlist"), G_CALLBACK (rb_playlist_manager_cmd_new_automatic_playlist) }, { "MusicPlaylistLoadPlaylist", NULL, N_("_Load from File..."), NULL, N_("Choose a playlist to be loaded"), G_CALLBACK (rb_playlist_manager_cmd_load_playlist) }, { "MusicPlaylistSavePlaylist", GTK_STOCK_SAVE_AS, N_("_Save to File..."), NULL, N_("Save a playlist to a file"), G_CALLBACK (rb_playlist_manager_cmd_save_playlist) }, { "MusicPlaylistRenamePlaylist", NULL, N_("_Rename"), NULL, N_("Rename playlist"), G_CALLBACK (rb_playlist_manager_cmd_rename_playlist) }, { "MusicPlaylistDeletePlaylist", GTK_STOCK_REMOVE, N_("_Delete"), NULL, N_("Delete playlist"), G_CALLBACK (rb_playlist_manager_cmd_delete_playlist) }, { "EditAutomaticPlaylist", GTK_STOCK_PROPERTIES, N_("_Edit..."), NULL, N_("Change this automatic playlist"), G_CALLBACK (rb_playlist_manager_cmd_edit_automatic_playlist) }, { "QueuePlaylist", NULL, N_("_Queue All Tracks"), NULL, N_("Add all tracks in this playlist to the queue"), G_CALLBACK (rb_playlist_manager_cmd_queue_playlist) }, { "ShufflePlaylist", NULL, N_("_Shuffle Playlist"), NULL, N_("Shuffle the tracks in this playlist"), G_CALLBACK (rb_playlist_manager_cmd_shuffle_playlist) }, { "MusicPlaylistSaveQueue", GTK_STOCK_SAVE_AS, N_("_Save to File..."), NULL, N_("Save the play queue to a file"), G_CALLBACK (rb_playlist_manager_cmd_save_queue) }, }; static guint rb_playlist_manager_n_actions = G_N_ELEMENTS (rb_playlist_manager_actions); G_DEFINE_TYPE (RBPlaylistManager, rb_playlist_manager, G_TYPE_OBJECT) static void rb_playlist_manager_class_init (RBPlaylistManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = rb_playlist_manager_dispose; object_class->finalize = rb_playlist_manager_finalize; object_class->set_property = rb_playlist_manager_set_property; object_class->get_property = rb_playlist_manager_get_property; g_object_class_install_property (object_class, PROP_PLAYLIST_NAME, g_param_spec_string ("playlists_file", "name", "playlists file", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SOURCE, g_param_spec_object ("source", "RBSource", "RBSource object", RB_TYPE_SOURCE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SHELL, g_param_spec_object ("shell", "RBShell", "RBShell object", RB_TYPE_SHELL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SOURCELIST, g_param_spec_object ("sourcelist", "RBSourceList", "RBSourceList", RB_TYPE_SOURCELIST, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * RBPlaylistManager::playlist-added: * @manager: the #RBPlaylistManager * @source: the new #RBSource * * Emitted when a playlist is added, including when being loaded * from the user's playlist file. */ rb_playlist_manager_signals[PLAYLIST_ADDED] = g_signal_new ("playlist_added", RB_TYPE_PLAYLIST_MANAGER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBPlaylistManagerClass, playlist_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * RBPlaylistManager::playlist-created: * @manager: the #RBPlaylistManager * @source: the newly created playlist #RBSource * * Emitted when a new playlist is created. */ rb_playlist_manager_signals[PLAYLIST_CREATED] = g_signal_new ("playlist_created", RB_TYPE_PLAYLIST_MANAGER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBPlaylistManagerClass, playlist_created), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); /** * RBPlaylistManager::load-start: * @manager: the #RBPlaylistManager * * Emitted when the playlist manager starts loading the user's * playlist file. */ rb_playlist_manager_signals[PLAYLIST_LOAD_START] = g_signal_new ("load_start", RB_TYPE_PLAYLIST_MANAGER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBPlaylistManagerClass, load_start), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); /** * RBPlaylistManager::load-finish * @manager: the #RBPlaylistManager * * Emitted when the playlist manager finishes loading the user's * playlist file. */ rb_playlist_manager_signals[PLAYLIST_LOAD_FINISH] = g_signal_new ("load_finish", RB_TYPE_PLAYLIST_MANAGER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (RBPlaylistManagerClass, load_finish), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); g_type_class_add_private (klass, sizeof (RBPlaylistManagerPrivate)); } static void rb_playlist_manager_init (RBPlaylistManager *mgr) { mgr->priv = G_TYPE_INSTANCE_GET_PRIVATE (mgr, RB_TYPE_PLAYLIST_MANAGER, RBPlaylistManagerPrivate); mgr->priv->saving_mutex = g_mutex_new (); mgr->priv->dirty = 0; mgr->priv->saving = 0; } /** * rb_playlist_manager_shutdown: * @mgr: the #RBPlaylistManager * * Shuts down the playlist manager, making sure any outstanding playlist save * operation finishes. */ void rb_playlist_manager_shutdown (RBPlaylistManager *mgr) { g_return_if_fail (RB_IS_PLAYLIST_MANAGER (mgr)); g_mutex_lock (mgr->priv->saving_mutex); g_mutex_unlock (mgr->priv->saving_mutex); } static void rb_playlist_manager_dispose (GObject *object) { RBPlaylistManager *mgr; g_return_if_fail (object != NULL); g_return_if_fail (RB_IS_PLAYLIST_MANAGER (object)); rb_debug ("Disposing playlist manager"); mgr = RB_PLAYLIST_MANAGER (object); g_return_if_fail (mgr->priv != NULL); if (mgr->priv->db != NULL) { g_object_unref (mgr->priv->db); mgr->priv->db = NULL; } if (mgr->priv->uimanager != NULL) { g_object_unref (mgr->priv->uimanager); mgr->priv->uimanager = NULL; } if (mgr->priv->sourcelist != NULL) { g_object_unref (mgr->priv->sourcelist); mgr->priv->sourcelist = NULL; } if (mgr->priv->selected_source != NULL) { g_object_unref (mgr->priv->selected_source); mgr->priv->selected_source = NULL; } G_OBJECT_CLASS (rb_playlist_manager_parent_class)->dispose (object); } static void rb_playlist_manager_finalize (GObject *object) { RBPlaylistManager *mgr; g_return_if_fail (object != NULL); g_return_if_fail (RB_IS_PLAYLIST_MANAGER (object)); rb_debug ("Finalizing playlist manager"); mgr = RB_PLAYLIST_MANAGER (object); g_return_if_fail (mgr->priv != NULL); g_mutex_free (mgr->priv->saving_mutex); g_free (mgr->priv->playlists_file); G_OBJECT_CLASS (rb_playlist_manager_parent_class)->finalize (object); } static void rb_playlist_manager_set_uimanager (RBPlaylistManager *mgr, GtkUIManager *uimanager) { if (mgr->priv->uimanager != NULL) { if (mgr->priv->actiongroup != NULL) { gtk_ui_manager_remove_action_group (mgr->priv->uimanager, mgr->priv->actiongroup); } g_object_unref (mgr->priv->uimanager); } mgr->priv->uimanager = uimanager; if (mgr->priv->actiongroup == NULL) { mgr->priv->actiongroup = gtk_action_group_new ("PlaylistManagerActions"); gtk_action_group_set_translation_domain (mgr->priv->actiongroup, GETTEXT_PACKAGE); gtk_action_group_add_actions (mgr->priv->actiongroup, rb_playlist_manager_actions, rb_playlist_manager_n_actions, mgr); } gtk_ui_manager_insert_action_group (mgr->priv->uimanager, mgr->priv->actiongroup, 0); } static void rb_playlist_manager_set_source (RBPlaylistManager *mgr, RBSource *source) { gboolean playlist_active; gboolean playlist_local = FALSE; gboolean party_mode; gboolean can_save; gboolean can_delete; gboolean can_edit; gboolean can_rename; gboolean can_shuffle; GtkAction *action; party_mode = rb_shell_get_party_mode (mgr->priv->shell); if (mgr->priv->selected_source != NULL) { g_object_unref (mgr->priv->selected_source); } mgr->priv->selected_source = g_object_ref (source); playlist_active = RB_IS_PLAYLIST_SOURCE (mgr->priv->selected_source); if (playlist_active) { g_object_get (mgr->priv->selected_source, "is-local", &playlist_local, NULL); } can_save = playlist_local && !party_mode; action = gtk_action_group_get_action (mgr->priv->actiongroup, "MusicPlaylistSavePlaylist"); gtk_action_set_visible (action, can_save); can_delete = (playlist_local && !party_mode && !RB_IS_PLAY_QUEUE_SOURCE (mgr->priv->selected_source)); action = gtk_action_group_get_action (mgr->priv->actiongroup, "MusicPlaylistDeletePlaylist"); gtk_action_set_visible (action, can_delete); can_edit = (playlist_local && RB_IS_AUTO_PLAYLIST_SOURCE (mgr->priv->selected_source) && !party_mode); action = gtk_action_group_get_action (mgr->priv->actiongroup, "EditAutomaticPlaylist"); gtk_action_set_visible (action, can_edit); can_rename = playlist_local && rb_source_can_rename (mgr->priv->selected_source); action = gtk_action_group_get_action (mgr->priv->actiongroup, "MusicPlaylistRenamePlaylist"); gtk_action_set_visible (action, can_rename); can_shuffle = RB_IS_STATIC_PLAYLIST_SOURCE (mgr->priv->selected_source); action = gtk_action_group_get_action (mgr->priv->actiongroup, "ShufflePlaylist"); gtk_action_set_sensitive (action, can_shuffle); } static void rb_playlist_manager_set_shell_internal (RBPlaylistManager *mgr, RBShell *shell) { GtkUIManager *uimanager = NULL; RhythmDB *db = NULL; if (mgr->priv->db != NULL) { g_object_unref (mgr->priv->db); } mgr->priv->shell = shell; if (mgr->priv->shell != NULL) { g_object_get (mgr->priv->shell, "ui-manager", &uimanager, "db", &db, NULL); } mgr->priv->db = db; rb_playlist_manager_set_uimanager (mgr, uimanager); } static void rb_playlist_manager_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (object); switch (prop_id) { case PROP_PLAYLIST_NAME: g_free (mgr->priv->playlists_file); mgr->priv->playlists_file = g_strdup (g_value_get_string (value)); break; case PROP_SOURCE: rb_playlist_manager_set_source (mgr, g_value_get_object (value)); break; case PROP_SHELL: rb_playlist_manager_set_shell_internal (mgr, g_value_get_object (value)); break; case PROP_SOURCELIST: mgr->priv->sourcelist = g_value_get_object (value); g_object_ref (mgr->priv->sourcelist); mgr->priv->window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (mgr->priv->sourcelist))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void rb_playlist_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (object); switch (prop_id) { case PROP_PLAYLIST_NAME: g_value_set_string (value, mgr->priv->playlists_file); break; case PROP_SOURCE: g_value_set_object (value, mgr->priv->selected_source); break; case PROP_SHELL: g_value_set_object (value, mgr->priv->shell); break; case PROP_SOURCELIST: g_value_set_object (value, mgr->priv->sourcelist); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * rb_playlist_manager_new: * @shell: the #RBShell * @sourcelist: the #RBSourceList * @playlists_file: the full path to the playlist file to load * * Creates the #RBPlaylistManager instance * * Return value: the #RBPlaylistManager */ RBPlaylistManager * rb_playlist_manager_new (RBShell *shell, RBSourceList *sourcelist, const char *playlists_file) { return g_object_new (RB_TYPE_PLAYLIST_MANAGER, "shell", shell, "sourcelist", sourcelist, "playlists_file", playlists_file, NULL); } GQuark rb_playlist_manager_error_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("rb_playlist_manager_error"); return quark; } static void handle_playlist_entry_cb (TotemPlParser *playlist, const char *uri_maybe, GHashTable *metadata, RBPlaylistManager *mgr) { char *uri; const char *title, *genre; title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE); genre = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_GENRE); uri = rb_canonicalise_uri (uri_maybe); g_return_if_fail (uri != NULL); rb_debug ("adding uri %s (title %s, genre %s) from playlist", uri, title, genre); if (!rb_shell_add_uri (mgr->priv->shell, uri, title, genre, NULL)) return; if (!mgr->priv->loading_playlist) { mgr->priv->loading_playlist = RB_STATIC_PLAYLIST_SOURCE (rb_playlist_manager_new_playlist (mgr, NULL, FALSE)); } if (rb_source_want_uri (RB_SOURCE (mgr->priv->loading_playlist), uri) > 0) { rb_debug ("adding uri %s to playlist", uri); rb_static_playlist_source_add_location (mgr->priv->loading_playlist, uri, -1); } g_free (uri); } static void playlist_load_started_cb (TotemPlParser *parser, const char *uri, GHashTable *metadata, RBPlaylistManager *mgr) { const char *title; rb_debug ("loading new playlist %s", uri); title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE); if (title == NULL) title = _("Unnamed playlist"); mgr->priv->loading_playlist = RB_STATIC_PLAYLIST_SOURCE (rb_playlist_manager_new_playlist (mgr, title, FALSE)); } /** * rb_playlist_manager_parse_file: * @mgr: the #RBPlaylistManager * @uri: URI of the playlist to load * @error: returns a GError in case of error * * Parses a playlist file, adding entries to the database and to a new * static playlist. If the playlist file includes a title, the static * playlist created will have the same title. * * Return value: TRUE on success **/ gboolean rb_playlist_manager_parse_file (RBPlaylistManager *mgr, const char *uri, GError **error) { rb_debug ("loading playlist from %s", uri); g_signal_emit (mgr, rb_playlist_manager_signals[PLAYLIST_LOAD_START], 0); { TotemPlParser *parser = totem_pl_parser_new (); g_signal_connect_object (parser, "entry-parsed", G_CALLBACK (handle_playlist_entry_cb), mgr, 0); g_signal_connect_object (parser, "playlist-started", G_CALLBACK (playlist_load_started_cb), mgr, 0); g_object_set (parser, "recurse", FALSE, NULL); if (totem_pl_parser_parse (parser, uri, TRUE) != TOTEM_PL_PARSER_RESULT_SUCCESS) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PARSE, "%s", _("The playlist file may be in an unknown format or corrupted.")); return FALSE; } if (mgr->priv->loading_playlist != NULL) { char *name = NULL; /* totem-plparser may not have given us the playlist name */ g_object_get (mgr->priv->loading_playlist, "name", &name, NULL); if (name == NULL || name[0] == '\0') { char *path; rb_debug ("setting playlist name from file name"); path = g_filename_from_uri (uri, NULL, NULL); if (path) { name = g_path_get_basename (path); g_object_set (mgr->priv->loading_playlist, "name", name, NULL); g_free (path); } } g_free (name); mgr->priv->loading_playlist = NULL; } g_object_unref (parser); } g_signal_emit (mgr, rb_playlist_manager_signals[PLAYLIST_LOAD_FINISH], 0); return TRUE; } static void append_new_playlist_source (RBPlaylistManager *mgr, RBPlaylistSource *source) { g_signal_emit (mgr, rb_playlist_manager_signals[PLAYLIST_ADDED], 0, source); } /** * rb_playlist_manager_load_playlists * @mgr: the #RBPlaylistManager * * Loads the user's playlists, or if the playlist file does not exists, * reads the default playlist file. Should be called only once on startup. **/ void rb_playlist_manager_load_playlists (RBPlaylistManager *mgr) { char *file; xmlDocPtr doc; xmlNodePtr root; xmlNodePtr child; gboolean exists; exists = FALSE; file = g_strdup (mgr->priv->playlists_file); /* block saves until the playlists have loaded */ g_mutex_lock (mgr->priv->saving_mutex); exists = g_file_test (file, G_FILE_TEST_EXISTS); if (! exists) { rb_debug ("personal playlists not found, loading defaults"); /* try global playlists */ g_free (file); file = g_strdup (rb_file ("playlists.xml")); exists = g_file_test (file, G_FILE_TEST_EXISTS); } if (! exists) { rb_debug ("default playlists file not found"); goto out; } doc = xmlParseFile (file); if (doc == NULL) goto out; root = xmlDocGetRootElement (doc); for (child = root->children; child; child = child->next) { RBSource *playlist; if (xmlNodeIsText (child)) continue; playlist = rb_playlist_source_new_from_xml (mgr->priv->shell, child); if (playlist) append_new_playlist_source (mgr, RB_PLAYLIST_SOURCE (playlist)); } xmlFreeDoc (doc); out: g_mutex_unlock (mgr->priv->saving_mutex); g_free (file); } static void rb_playlist_manager_set_dirty (RBPlaylistManager *mgr, gboolean dirty) { g_atomic_int_compare_and_exchange (&mgr->priv->dirty, dirty == FALSE, dirty == TRUE); } static gboolean _is_dirty_playlist (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gboolean *dirty) { RBSource *source; gboolean local; gboolean ret; gtk_tree_model_get (model, iter, RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1); if (source == NULL) { return FALSE; } if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) { g_object_unref (source); return FALSE; } g_object_get (source, "is-local", &local, NULL); ret = FALSE; if (local) { gboolean pdirty; g_object_get (source, "dirty", &pdirty, NULL); if (pdirty) { *dirty = TRUE; ret = TRUE; } } g_object_unref (source); return ret; } /* returns TRUE if a playlist has been created, modified, or deleted since last save */ static gboolean rb_playlist_manager_is_dirty (RBPlaylistManager *mgr) { gboolean dirty = FALSE; GtkTreeModel *fmodel; GtkTreeModel *model; g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL); model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel)); g_object_unref (fmodel); gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) _is_dirty_playlist, &dirty); if (!dirty) dirty = g_atomic_int_get (&mgr->priv->dirty); return dirty; } struct RBPlaylistManagerSaveData { RBPlaylistManager *mgr; xmlDocPtr doc; }; static gpointer rb_playlist_manager_save_data (struct RBPlaylistManagerSaveData *data) { char *file; char *tmpname; g_mutex_lock (data->mgr->priv->saving_mutex); file = g_strdup (data->mgr->priv->playlists_file); tmpname = g_strconcat (file, ".tmp", NULL); if (xmlSaveFormatFile (tmpname, data->doc, 1) != -1) { rename (tmpname, file); } else { rb_debug ("error in xmlSaveFormatFile(), not saving"); unlink (tmpname); rb_playlist_manager_set_dirty (data->mgr, TRUE); } xmlFreeDoc (data->doc); g_free (tmpname); g_free (file); g_atomic_int_compare_and_exchange (&data->mgr->priv->saving, 1, 0); g_mutex_unlock (data->mgr->priv->saving_mutex); g_object_unref (data->mgr); g_free (data); return NULL; } static gboolean save_playlist_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, xmlNodePtr root) { RBSource *source; gboolean local; gtk_tree_model_get (model, iter, RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1); if (source == NULL) { goto out; } if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) { goto out; } g_object_get (source, "is-local", &local, NULL); if (local) { rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (source), root); } out: if (source != NULL) { g_object_unref (source); } return FALSE; } /** * rb_playlist_manager_save_playlists * @mgr: the #RBPlaylistManager * @force: if TRUE, save playlists synchronously and unconditionally * * Saves the user's playlists. If the force flag is * TRUE, the playlists will always be saved. Otherwise, the playlists * will only be saved if a playlist has been created, modified, or deleted * since the last time the playlists were saved, and no save operation is * currently taking place. * * Return value: TRUE if a playlist save operation has been started **/ gboolean rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force) { xmlNodePtr root; struct RBPlaylistManagerSaveData *data; GtkTreeModel *fmodel; GtkTreeModel *model; if (!force && !rb_playlist_manager_is_dirty (mgr)) { /* playlists already in sync, so don't bother */ return FALSE; } if (!g_atomic_int_compare_and_exchange (&mgr->priv->saving, 0, 1) && !force) { /* already saving, so don't bother */ return FALSE; } data = g_new0 (struct RBPlaylistManagerSaveData, 1); data->mgr = mgr; data->doc = xmlNewDoc (RB_PLAYLIST_MGR_VERSION); g_object_ref (mgr); root = xmlNewDocNode (data->doc, NULL, RB_PLAYLIST_MGR_PL, NULL); xmlDocSetRootElement (data->doc, root); g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL); model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel)); g_object_unref (fmodel); gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)save_playlist_cb, root); /* mark clean here. if the save fails, we'll mark it dirty again */ rb_playlist_manager_set_dirty (data->mgr, FALSE); if (force) rb_playlist_manager_save_data (data); else g_thread_create ((GThreadFunc) rb_playlist_manager_save_data, data, FALSE, NULL); return TRUE; } /** * rb_playlist_manager_new_playlist * @mgr: the #RBPlaylistManager * @suggested_name: optional name to use for the new playlist * @automatic: if TRUE, create an auto playlist * * Creates a new playlist and adds it to the source list. * * Return value: the new playlist object. **/ RBSource * rb_playlist_manager_new_playlist (RBPlaylistManager *mgr, const char *suggested_name, gboolean automatic) { RBSource *playlist; if (automatic) playlist = rb_auto_playlist_source_new (mgr->priv->shell, suggested_name, TRUE); else playlist = rb_static_playlist_source_new (mgr->priv->shell, suggested_name, NULL, TRUE, RHYTHMDB_ENTRY_TYPE_SONG); append_new_playlist_source (mgr, RB_PLAYLIST_SOURCE (playlist)); rb_sourcelist_edit_source_name (mgr->priv->sourcelist, playlist); rb_playlist_manager_set_dirty (mgr, TRUE); g_signal_emit (mgr, rb_playlist_manager_signals[PLAYLIST_CREATED], 0, playlist); return playlist; } static char * create_name_from_selection_data (RBPlaylistManager *mgr, GtkSelectionData *data) { GdkAtom type; char *name = NULL; const guchar *selection_data_data; GList *list; type = gtk_selection_data_get_data_type (data); selection_data_data = gtk_selection_data_get_data (data); if (type == gdk_atom_intern ("text/uri-list", TRUE) || type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) { gboolean is_id; list = rb_uri_list_parse ((const char *) selection_data_data); is_id = (type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)); if (list != NULL) { GList *l; char *artist; char *album; gboolean mixed_artists; gboolean mixed_albums; artist = NULL; album = NULL; mixed_artists = FALSE; mixed_albums = FALSE; for (l = list; l != NULL; l = g_list_next (l)) { RhythmDBEntry *entry; const char *e_artist; const char *e_album; entry = rhythmdb_entry_lookup_from_string (mgr->priv->db, (const char *)l->data, is_id); if (entry == NULL) { continue; } e_artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST); e_album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM); /* get value of first non-NULL artist */ if (e_artist != NULL && artist == NULL) { artist = g_strdup (e_artist); } /* get value of first non-NULL album */ if (e_album != NULL && album == NULL) { album = g_strdup (e_album); } /* pretend that NULL fields always match */ if (artist != NULL && e_artist != NULL && strcmp (artist, e_artist) != 0) { mixed_artists = TRUE; } /* pretend that NULL fields always match */ if (album != NULL && e_album != NULL && strcmp (album, e_album) != 0) { mixed_albums = TRUE; } /* if there is a mix of both then stop */ if (mixed_artists && mixed_albums) { break; } } if (! mixed_artists && ! mixed_albums) { name = g_strdup_printf ("%s - %s", artist, album); } else if (! mixed_artists) { name = g_strdup_printf ("%s", artist); } else if (! mixed_albums) { name = g_strdup_printf ("%s", album); } g_free (artist); g_free (album); rb_list_deep_free (list); } } else { char **names; names = g_strsplit ((char *) selection_data_data, "\r\n", 0); name = g_strjoinv (", ", names); g_strfreev (names); } if (name == NULL) { name = g_strdup (_("Untitled Playlist")); } return name; } /** * rb_playlist_manager_new_playlist_from_selection_data * @mgr: the #RBPlaylistManager * @data: the #GtkSelectionData from which to create a playlist * * Creates a new playlist based on selection data from gtk. * Used to implement playlist creation through drag and drop * to the source list. * * Return value: the new playlist. **/ RBSource * rb_playlist_manager_new_playlist_from_selection_data (RBPlaylistManager *mgr, GtkSelectionData *data) { RBSource *playlist; GdkAtom type; gboolean automatic = TRUE; char *suggested_name; type = gtk_selection_data_get_data_type (data); if (type == gdk_atom_intern ("text/uri-list", TRUE) || type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) automatic = FALSE; suggested_name = create_name_from_selection_data (mgr, data); playlist = rb_playlist_manager_new_playlist (mgr, suggested_name, automatic); g_free (suggested_name); return playlist; } static void rb_playlist_manager_cmd_new_playlist (GtkAction *action, RBPlaylistManager *mgr) { rb_playlist_manager_new_playlist (mgr, _("New Playlist"), FALSE); } static void rb_playlist_manager_set_automatic_playlist (RBPlaylistManager *mgr, RBAutoPlaylistSource *playlist, RBQueryCreator *creator) { RhythmDBQueryModelLimitType limit_type; GValueArray *limit_value = NULL; const char *sort_key; gint sort_direction; GPtrArray *query; rb_query_creator_get_limit (creator, &limit_type, &limit_value); rb_query_creator_get_sort_order (creator, &sort_key, &sort_direction); query = rb_query_creator_get_query (creator); rb_auto_playlist_source_set_query (RB_AUTO_PLAYLIST_SOURCE (playlist), query, limit_type, limit_value, sort_key, sort_direction); rhythmdb_query_free (query); } static void rb_playlist_manager_cmd_new_automatic_playlist (GtkAction *action, RBPlaylistManager *mgr) { RBQueryCreator *creator = RB_QUERY_CREATOR (rb_query_creator_new (mgr->priv->db)); RBSource *playlist; switch (gtk_dialog_run (GTK_DIALOG (creator))) { case GTK_RESPONSE_NONE: case GTK_RESPONSE_CLOSE: gtk_widget_destroy (GTK_WIDGET (creator)); return; } playlist = rb_playlist_manager_new_playlist (mgr, _("New Playlist"), TRUE); rb_playlist_manager_set_automatic_playlist (mgr, RB_AUTO_PLAYLIST_SOURCE (playlist), creator); rb_playlist_manager_set_dirty (mgr, TRUE); gtk_widget_destroy (GTK_WIDGET (creator)); } typedef struct { RBAutoPlaylistSource *playlist; RBPlaylistManager *mgr; RBQueryCreator *creator; gint playlist_deleted_id; gint creator_response_id; } EditAutoPlaylistData; static void cleanup_edit_data (EditAutoPlaylistData *data) { g_signal_handler_disconnect (data->playlist, data->playlist_deleted_id); g_signal_handler_disconnect (data->creator, data->creator_response_id); gtk_widget_destroy (GTK_WIDGET (data->creator)); g_free (data); } static void edit_auto_playlist_response_cb (RBQueryCreator *dialog, gint response, EditAutoPlaylistData *data) { rb_playlist_manager_set_automatic_playlist (data->mgr, data->playlist, dialog); g_object_set_data (G_OBJECT (data->playlist), "rhythmbox-playlist-editor", NULL); cleanup_edit_data (data); } static void edit_auto_playlist_deleted_cb (RBAutoPlaylistSource *playlist, EditAutoPlaylistData *data) { g_object_set_data (G_OBJECT (playlist), "rhythmbox-playlist-editor", NULL); cleanup_edit_data (data); } static void rb_playlist_manager_cmd_edit_automatic_playlist (GtkAction *action, RBPlaylistManager *mgr) { RBQueryCreator *creator; RBAutoPlaylistSource *playlist; playlist = RB_AUTO_PLAYLIST_SOURCE (mgr->priv->selected_source); creator = g_object_get_data (G_OBJECT (playlist), "rhythmbox-playlist-editor"); if (creator == NULL) { RhythmDBQueryModelLimitType limit_type; GValueArray *limit_value = NULL; GPtrArray *query; char *sort_key; gint sort_direction; EditAutoPlaylistData *data; sort_key = NULL; rb_auto_playlist_source_get_query (playlist, &query, &limit_type, &limit_value, &sort_key, &sort_direction); creator = RB_QUERY_CREATOR (rb_query_creator_new_from_query (mgr->priv->db, query, limit_type, limit_value, sort_key, sort_direction)); if (limit_value != NULL) { g_value_array_free (limit_value); } rhythmdb_query_free (query); g_free (sort_key); data = g_new0 (EditAutoPlaylistData, 1); data->mgr = mgr; data->playlist = playlist; data->creator = creator; data->creator_response_id = g_signal_connect (creator, "response", G_CALLBACK (edit_auto_playlist_response_cb), data); g_object_set_data (G_OBJECT (playlist), "rhythmbox-playlist-editor", creator); data->playlist_deleted_id = g_signal_connect (playlist, "deleted", G_CALLBACK (edit_auto_playlist_deleted_cb), data); } gtk_window_present (GTK_WINDOW (creator)); } static gboolean _queue_track_cb (RhythmDBQueryModel *model, GtkTreePath *path, GtkTreeIter *iter, RBStaticPlaylistSource *queue_source) { RhythmDBEntry *entry; entry = rhythmdb_query_model_iter_to_entry (model, iter); rb_static_playlist_source_add_entry (queue_source, entry, -1); rhythmdb_entry_unref (entry); return FALSE; } static void rb_playlist_manager_cmd_queue_playlist (GtkAction *action, RBPlaylistManager *mgr) { RBSource *queue_source; RhythmDBQueryModel *model; g_object_get (mgr->priv->shell, "queue-source", &queue_source, NULL); g_object_get (mgr->priv->selected_source, "query-model", &model, NULL); gtk_tree_model_foreach (GTK_TREE_MODEL (model), (GtkTreeModelForeachFunc) _queue_track_cb, queue_source); g_object_unref (queue_source); g_object_unref (model); } static void rb_playlist_manager_cmd_shuffle_playlist (GtkAction *action, RBPlaylistManager *mgr) { RhythmDBQueryModel *base_model; g_object_get (mgr->priv->selected_source, "base-query-model", &base_model, NULL); rhythmdb_query_model_shuffle_entries (base_model); g_object_unref (base_model); } static void rb_playlist_manager_cmd_rename_playlist (GtkAction *action, RBPlaylistManager *mgr) { rb_debug ("Renaming playlist %p", mgr->priv->selected_source); rb_sourcelist_edit_source_name (mgr->priv->sourcelist, mgr->priv->selected_source); rb_playlist_manager_set_dirty (mgr, TRUE); } static void rb_playlist_manager_cmd_delete_playlist (GtkAction *action, RBPlaylistManager *mgr) { rb_debug ("Deleting playlist %p", mgr->priv->selected_source); rb_source_delete_thyself (mgr->priv->selected_source); rb_playlist_manager_set_dirty (mgr, TRUE); } static void load_playlist_response_cb (GtkDialog *dialog, int response_id, RBPlaylistManager *mgr) { char *escaped_file = NULL; GError *error = NULL; if (response_id != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy (GTK_WIDGET (dialog)); return; } escaped_file = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); gtk_widget_destroy (GTK_WIDGET (dialog)); if (escaped_file == NULL) return; if (!rb_playlist_manager_parse_file (mgr, escaped_file, &error)) { rb_error_dialog (NULL, _("Couldn't read playlist"), "%s", error->message); g_error_free (error); } g_free (escaped_file); rb_playlist_manager_set_dirty (mgr, TRUE); } static void rb_playlist_manager_cmd_load_playlist (GtkAction *action, RBPlaylistManager *mgr) { GtkWidget *dialog; GtkFileFilter *filter; GtkFileFilter *filter_all; int i; filter = gtk_file_filter_new (); gtk_file_filter_set_name (filter, _("Playlists")); for (i = 0; i < G_N_ELEMENTS (playlist_formats); i++) { gtk_file_filter_add_mime_type (filter, playlist_formats[i].mimetype); } filter_all = gtk_file_filter_new (); gtk_file_filter_set_name (filter_all, _("All Files")); gtk_file_filter_add_pattern (filter_all, "*"); dialog = rb_file_chooser_new (_("Load Playlist"), GTK_WINDOW (mgr->priv->window), GTK_FILE_CHOOSER_ACTION_OPEN, FALSE); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter_all); gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); g_signal_connect_object (dialog, "response", G_CALLBACK (load_playlist_response_cb), mgr, 0); } static void save_playlist_response_cb (GtkDialog *dialog, int response_id, RBSource *source) { char *file = NULL; GtkWidget *menu; gint index; RBPlaylistExportType export_type = RB_PLAYLIST_EXPORT_TYPE_UNKNOWN; if (response_id != GTK_RESPONSE_OK) { gtk_widget_destroy (GTK_WIDGET (dialog)); return; } file = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); if (file == NULL || file[0] == '\0') return; menu = g_object_get_data (G_OBJECT(dialog), "export-menu"); index = gtk_combo_box_get_active (GTK_COMBO_BOX (menu)); /* by extension selected */ if (index <= 0) { int i; for (i = 0; i < G_N_ELEMENTS (playlist_formats); i++) { int j; /* determine the playlist type from the extension */ for (j = 0; playlist_formats[i].extensions[j] != NULL; j++) { if (g_str_has_suffix (file, playlist_formats[i].extensions[j])) { export_type = playlist_formats[i].type; break; } } } } else { export_type = playlist_formats[index-1].type; } if (export_type == RB_PLAYLIST_EXPORT_TYPE_UNKNOWN) { rb_error_dialog (NULL, _("Couldn't save playlist"), _("Unsupported file extension given.")); } else { rb_playlist_source_save_playlist (RB_PLAYLIST_SOURCE (source), file, export_type); gtk_widget_destroy (GTK_WIDGET (dialog)); } g_free (file); } static void export_set_extension_cb (GtkWidget* widget, GtkDialog *dialog) { gint index; gchar *text; gchar *last_dot; const char *extension; gchar *basename; GString *basename_str; index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget)); if (index <= 0) return; extension = playlist_formats[index-1].extensions[0]; if (extension == NULL) return; text = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); if (text == NULL || text[0] == '\0') { g_free (text); return; } basename = g_path_get_basename (text); basename_str = g_string_new (basename); last_dot = g_utf8_strrchr (basename, -1, '.'); if (last_dot) g_string_truncate (basename_str, (last_dot-basename)); g_free (basename); g_free (text); g_string_append_printf (basename_str, ".%s", extension); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), basename_str->str); g_string_free (basename_str, TRUE); } static gchar * filter_get_export_filter_label (RBPlaylistExportFilter *efilter) { GString *str; gint ext; str = g_string_new (_(efilter->description)); for (ext = 0; efilter->extensions[ext] != NULL; ext++) { if (ext == 0) g_string_append (str, " (*."); else g_string_append (str, ", *."); g_string_append (str, efilter->extensions[ext]); } if (ext > 0) g_string_append (str, ")"); return g_string_free (str, FALSE); } static void setup_format_menu (GtkWidget* menu, GtkWidget *dialog) { GtkTreeModel *model; int i; model = gtk_combo_box_get_model (GTK_COMBO_BOX (menu)); gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (menu), rb_combo_box_hyphen_separator_func, NULL, NULL); for (i = 0; i < G_N_ELEMENTS (playlist_formats); i++) { gchar *filter_label; GtkTreeIter iter; filter_label = filter_get_export_filter_label (&playlist_formats[i]); gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, -1, 0, filter_label, -1); g_free (filter_label); } g_signal_connect_object (menu, "changed", G_CALLBACK (export_set_extension_cb), dialog, 0); } static void save_playlist (RBPlaylistManager *mgr, RBSource *source) { GtkBuilder *builder; GtkWidget *dialog; GtkWidget *menu; char *name; char *tmp; builder = rb_builder_load ("playlist-save.ui", mgr); dialog = GTK_WIDGET (gtk_builder_get_object (builder, "playlist_save_dialog")); menu = GTK_WIDGET (gtk_builder_get_object (builder, "playlist_format_menu")); setup_format_menu (menu, dialog); g_object_set_data (G_OBJECT (dialog), "export-menu", menu); g_object_get (source, "name", &name, NULL); tmp = g_strconcat (name, ".pls", NULL); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), tmp); g_free (tmp); g_free (name); /* FIXME: always has "by extension" as default (it should probably remember the last selection) */ gtk_combo_box_set_active (GTK_COMBO_BOX (menu), 0); g_signal_connect_object (dialog, "response", G_CALLBACK (save_playlist_response_cb), source, 0); g_object_unref (builder); } static void rb_playlist_manager_cmd_save_playlist (GtkAction *action, RBPlaylistManager *mgr) { save_playlist (mgr, mgr->priv->selected_source); } static void rb_playlist_manager_cmd_save_queue (GtkAction *action, RBPlaylistManager *mgr) { RBSource *queue; g_object_get (mgr->priv->shell, "queue-source", &queue, NULL); save_playlist (mgr, queue); g_object_unref (queue); } static gboolean list_playlists_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **playlists) { RBSource *source; gboolean local; gtk_tree_model_get (model, iter, RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1); if (source != NULL) { if (RB_IS_PLAYLIST_SOURCE (source) && !RB_IS_PLAY_QUEUE_SOURCE (source)) { g_object_get (source, "is-local", &local, NULL); if (local) { *playlists = g_list_prepend (*playlists, source); } } g_object_unref (source); } return FALSE; } /** * rb_playlist_manager_get_playlists * @mgr: the #RBPlaylistManager * * Return value: a #GList containing all local playlist source objects. **/ GList * rb_playlist_manager_get_playlists (RBPlaylistManager *mgr) { GList *playlists = NULL; GtkTreeModel *fmodel; GtkTreeModel *model; g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL); model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel)); g_object_unref (fmodel); gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)list_playlists_cb, &playlists); return g_list_reverse (playlists); } /** * rb_playlist_manager_get_playlist_names * @mgr: the #RBPlaylistManager * @playlists: holds the array of playlist names on reutrn * @error: holds a #GError on return on failure * * Allocates and returns an array containing the names of all local * playlists. This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. **/ gboolean rb_playlist_manager_get_playlist_names (RBPlaylistManager *mgr, gchar ***playlists, GError **error) { GList *pl; GList *t; int i; pl = rb_playlist_manager_get_playlists (mgr); *playlists = g_new0 (char *, g_list_length (pl) + 1); if (!*playlists) return FALSE; i = 0; for (t = pl; t != NULL; t = t->next, i++) { const char *name; RBSource *source = (RBSource *)t->data; g_object_get (source, "name", &name, NULL); (*playlists)[i] = g_strdup (name); } return TRUE; } typedef struct { const char *name; RBSource *source; } FindPlaylistData; static gboolean find_playlist_by_name_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, FindPlaylistData *data) { RBSource *source; gtk_tree_model_get (model, iter, RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1); if (source != NULL) { if (RB_IS_PLAYLIST_SOURCE (source) && !RB_IS_PLAY_QUEUE_SOURCE (source)) { char *name; g_object_get (source, "name", &name, NULL); if (strcmp (name, data->name) == 0) { data->source = source; } g_free (name); } g_object_unref (source); } return (data->source != NULL); } static RBSource * _get_playlist_by_name (RBPlaylistManager *mgr, const char *name) { GtkTreeModel *fmodel; GtkTreeModel *model; FindPlaylistData d; g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL); model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel)); g_object_unref (fmodel); d.name = name; d.source = NULL; gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)find_playlist_by_name_cb, &d); return d.source; } /** * rb_playlist_manager_create_static_playlist * @mgr: the #RBPlaylistManager * @name: name of the new playlist * @error: holds a #GError on return on failure * * Creates a new static playlist source with the given name. * Will fail if a playlist with that name already exists. * This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. **/ gboolean rb_playlist_manager_create_static_playlist (RBPlaylistManager *mgr, const gchar *name, GError **error) { if (_get_playlist_by_name (mgr, name)) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_EXISTS, _("Playlist %s already exists"), name); return FALSE; } rb_playlist_manager_new_playlist (mgr, name, FALSE); return TRUE; } /** * rb_playlist_manager_delete_playlist * @mgr: the #RBPlaylistManager * @name: name of the playlist to delete * @error: holds a #GError on return on failure * * Deletes the specified playlist. Will fail if no playlist with * that name exists. This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. */ gboolean rb_playlist_manager_delete_playlist (RBPlaylistManager *mgr, const gchar *name, GError **error) { RBSource *playlist = _get_playlist_by_name (mgr, name); if (!playlist) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Unknown playlist: %s"), name); return FALSE; } rb_source_delete_thyself (playlist); rb_playlist_manager_set_dirty (mgr, TRUE); return TRUE; } /** * rb_playlist_manager_add_to_playlist * @mgr: the #RBPlaylistManager * @name: name of the playlist to add to * @uri: URI of the entry to add to the playlist * @error: holds a #GError on return on failure * * Adds an entry to the specified playlist. * Fails if no playlist with that name exists. * This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. **/ gboolean rb_playlist_manager_add_to_playlist (RBPlaylistManager *mgr, const gchar *playlist, const gchar *uri, GError **error) { RBSource *source = _get_playlist_by_name (mgr, playlist);; if (!source) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Unknown playlist: %s"), playlist); return FALSE; } if (RB_IS_AUTO_PLAYLIST_SOURCE (source)) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Playlist %s is an automatic playlist"), playlist); return FALSE; } rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (source), uri, -1); return TRUE; } /** * rb_playlist_manager_remove_from_playlist * @mgr: the #RBPlaylistManager * @name: name of the playlist to remove from * @uri: URI of the entry to remove from the playlist * @error: holds a #GError on return on failure * * Removes an entry from the specified playlist. * Fails if no playlist with that name exists. * This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. **/ gboolean rb_playlist_manager_remove_from_playlist (RBPlaylistManager *mgr, const gchar *playlist, const gchar *uri, GError **error) { RBSource *source = _get_playlist_by_name (mgr, playlist);; if (!source) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Unknown playlist: %s"), playlist); return FALSE; } if (RB_IS_AUTO_PLAYLIST_SOURCE (source)) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Playlist %s is an automatic playlist"), playlist); return FALSE; } if (rb_playlist_source_location_in_map (RB_PLAYLIST_SOURCE (source), uri)) rb_static_playlist_source_remove_location (RB_STATIC_PLAYLIST_SOURCE (source), uri); return TRUE; } /** * rb_playlist_manager_export_playlist * @mgr: the #RBPlaylistManager * @name: name of the playlist to export * @uri: playlist save location * @m3u_format: if TRUE, save in M3U format, otherwise save in PLS format * @error: holds a #GError on return on failure * * Saves the specified playlist to a file in either M3U or PLS format. * This is part of the playlist manager dbus interface. * * Return value: TRUE if successful. **/ gboolean rb_playlist_manager_export_playlist (RBPlaylistManager *mgr, const gchar *playlist, const gchar *uri, gboolean m3u_format, GError **error) { RBSource *source = _get_playlist_by_name (mgr, playlist); if (!source) { g_set_error (error, RB_PLAYLIST_MANAGER_ERROR, RB_PLAYLIST_MANAGER_ERROR_PLAYLIST_NOT_FOUND, _("Unknown playlist: %s"), playlist); return FALSE; } rb_playlist_source_save_playlist (RB_PLAYLIST_SOURCE (source), uri, m3u_format); return TRUE; }