Index: plparse/totem-pl-parser.h =================================================================== --- plparse/totem-pl-parser.h (revision 255) +++ plparse/totem-pl-parser.h (working copy) @@ -248,6 +248,8 @@ * which can be overridden by inheriting classes * @playlist_ended: the generic signal handler for the #TotemPlParser::playlist-ended signal, * which can be overridden by inheriting classes + * @parsing_finished: the generic signal handler for the #TotemPlParser::parsing-finished signal, + * which can be overridden by inheriting classes * * The class structure for the #TotemPlParser type. **/ @@ -263,6 +265,8 @@ GHashTable *metadata); void (*playlist_ended) (TotemPlParser *parser, const char *uri); + void (*parsing_finished) (TotemPlParser *parser, + TotemPlParserResult retval); }; /** @@ -349,10 +353,16 @@ TotemPlParserResult totem_pl_parser_parse (TotemPlParser *parser, const char *url, gboolean fallback); +TotemPlParserResult totem_pl_parser_parse_async (TotemPlParser *parser, + const char *url, gboolean fallback); TotemPlParserResult totem_pl_parser_parse_with_base (TotemPlParser *parser, const char *url, const char *base, gboolean fallback); +TotemPlParserResult totem_pl_parser_parse_with_base_async (TotemPlParser *parser, + const char *url, + const char *base, + gboolean fallback); TotemPlParser *totem_pl_parser_new (void); Index: plparse/totem-pl-parser-private.h =================================================================== --- plparse/totem-pl-parser-private.h (revision 255) +++ plparse/totem-pl-parser-private.h (working copy) @@ -76,7 +76,12 @@ { GList *ignore_schemes; GList *ignore_mimetypes; + GMutex *ignore_mutex; + GThread *thread; + GAsyncQueue *parse_queue; + gboolean thread_stopping; + guint recurse_level; guint fallback : 1; guint recurse : 1; Index: plparse/plparser.symbols =================================================================== --- plparse/plparser.symbols (revision 255) +++ plparse/plparser.symbols (working copy) @@ -14,9 +14,11 @@ totemplparser_marshal_VOID__STRING_STRING_STRING totem_pl_parser_new totem_pl_parser_parse +totem_pl_parser_parse_async totem_pl_parser_parse_date totem_pl_parser_parse_duration totem_pl_parser_parse_with_base +totem_pl_parser_parse_with_base_async totem_pl_parser_relative totem_pl_parser_result_get_type totem_pl_parser_type_get_type Index: plparse/totem-pl-parser.c =================================================================== --- plparse/totem-pl-parser.c (revision 255) +++ plparse/totem-pl-parser.c (working copy) @@ -31,6 +31,9 @@ * * * Reading a Playlist + * This example loads a playlist synchronously. To load a playlist asynchronously, however, you simply + * need to use totem_pl_parser_parse_async(), and optionally connect to #TotemPlParser::parsing-finished + * to receive the ultimate #TotemPlParserResult value for the operation. * * TotemPlParser *pl = totem_pl_parser_new (); * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL); @@ -234,6 +237,7 @@ ENTRY_PARSED, PLAYLIST_STARTED, PLAYLIST_ENDED, + PARSING_FINISHED, LAST_SIGNAL }; @@ -244,12 +248,15 @@ static void totem_pl_parser_class_init (TotemPlParserClass *klass); static void totem_pl_parser_base_class_finalize (TotemPlParserClass *klass); static void totem_pl_parser_init (TotemPlParser *parser); +static void totem_pl_parser_dispose (GObject *object); static void totem_pl_parser_finalize (GObject *object); static void totem_pl_parser_init (TotemPlParser *self); static void totem_pl_parser_class_init (TotemPlParserClass *klass); static gpointer totem_pl_parser_parent_class = NULL; +#define TOTEM_PL_PARSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate)) + GType totem_pl_parser_get_type (void) { @@ -279,9 +286,13 @@ GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); + if (!g_thread_supported ()) + g_thread_init (NULL); + totem_pl_parser_parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (klass, sizeof (TotemPlParserPrivate)); + object_class->dispose = totem_pl_parser_dispose; object_class->finalize = totem_pl_parser_finalize; object_class->set_property = totem_pl_parser_set_property; object_class->get_property = totem_pl_parser_get_property; @@ -360,6 +371,7 @@ NULL, NULL, totemplparser_marshal_VOID__STRING_BOXED, G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA); + /** * TotemPlParser::playlist-started: * @parser: the object which received the signal @@ -398,6 +410,24 @@ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + /** + * TotemPlParser::parsing-finished: + * @parser: the object which received the signal + * @retval: the #TotemPlParserResult return value from the parse operation + * + * The ::parsing-finished signal is emitted after the parse operation initiated + * by totem_pl_parser_parse() or totem_pl_parser_parse_with_base() has finished + * (successfully or not). + */ + totem_pl_parser_table_signals[PARSING_FINISHED] = + g_signal_new ("parsing-finished", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TotemPlParserClass, parsing_finished), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, TOTEM_TYPE_PL_PARSER_RESULT); + /* param specs */ totem_pl_parser_pspec_pool = g_param_spec_pool_new (FALSE); pspec = g_param_spec_string ("url", "url", @@ -620,6 +650,26 @@ return TOTEM_PL_PARSER (g_object_new (TOTEM_TYPE_PL_PARSER, NULL)); } +typedef struct { + TotemPlParser *parser; + char *playlist_uri; +} PlaylistEndedSignalData; + +static gboolean +emit_playlist_ended_signal (PlaylistEndedSignalData *data) +{ + g_signal_emit (data->parser, + totem_pl_parser_table_signals[PLAYLIST_ENDED], + 0, data->playlist_uri); + + /* Free the data */ + g_object_unref (data->parser); + g_free (data->playlist_uri); + g_free (data); + + return FALSE; +} + /** * totem_pl_parser_playlist_end: * @parser: a #TotemPlParser @@ -631,9 +681,13 @@ void totem_pl_parser_playlist_end (TotemPlParser *parser, const char *playlist_uri) { - g_signal_emit (G_OBJECT (parser), - totem_pl_parser_table_signals[PLAYLIST_ENDED], - 0, playlist_uri); + PlaylistEndedSignalData *data; + + data = g_new (PlaylistEndedSignalData, 1); + data->parser = g_object_ref (parser); + data->playlist_uri = g_strdup (playlist_uri); + + g_idle_add ((GSourceFunc) emit_playlist_ended_signal, data); } static char * @@ -1057,25 +1111,70 @@ totem_pl_parser_init (TotemPlParser *parser) { parser->priv = G_TYPE_INSTANCE_GET_PRIVATE (parser, TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate); + parser->priv->ignore_mutex = g_mutex_new (); } static void +totem_pl_parser_dispose (GObject *object) +{ + TotemPlParserPrivate *priv = TOTEM_PL_PARSER_GET_PRIVATE (object); + + /* Tell the thread we're stopping */ + priv->thread_stopping = TRUE; + + /* Wait for the thread to finish */ + if (priv->thread != NULL) + g_thread_join (priv->thread); + priv->thread = NULL; + + /* Free the job queue */ + if (priv->parse_queue != NULL) + g_async_queue_unref (priv->parse_queue); + priv->parse_queue = NULL; + + G_OBJECT_CLASS (totem_pl_parser_parent_class)->dispose (object); +} + +static void totem_pl_parser_finalize (GObject *object) { - TotemPlParser *parser = TOTEM_PL_PARSER (object); + TotemPlParserPrivate *priv = TOTEM_PL_PARSER_GET_PRIVATE (object); g_return_if_fail (object != NULL); - g_return_if_fail (parser->priv != NULL); + g_return_if_fail (priv != NULL); - g_list_foreach (parser->priv->ignore_schemes, (GFunc) g_free, NULL); - g_list_free (parser->priv->ignore_schemes); + g_list_foreach (priv->ignore_schemes, (GFunc) g_free, NULL); + g_list_free (priv->ignore_schemes); - g_list_foreach (parser->priv->ignore_mimetypes, (GFunc) g_free, NULL); - g_list_free (parser->priv->ignore_mimetypes); + g_list_foreach (priv->ignore_mimetypes, (GFunc) g_free, NULL); + g_list_free (priv->ignore_mimetypes); + g_mutex_free (priv->ignore_mutex); + G_OBJECT_CLASS (totem_pl_parser_parent_class)->finalize (object); } +typedef struct { + TotemPlParser *parser; + guint signal_id; + char *url; + GHashTable *metadata; +} EntryParsedSignalData; + +static gboolean +emit_entry_parsed_signal (EntryParsedSignalData *data) +{ + g_signal_emit (data->parser, data->signal_id, 0, data->url, data->metadata); + + /* Free the data */ + g_object_unref (data->parser); + g_free (data->url); + g_hash_table_unref (data->metadata); + g_free (data); + + return FALSE; +} + static void totem_pl_parser_add_url_valist (TotemPlParser *parser, const gchar *first_property_name, @@ -1188,15 +1287,21 @@ } if (g_hash_table_size (metadata) > 0 || url != NULL) { - if (is_playlist == FALSE) { - g_signal_emit (G_OBJECT (parser), - totem_pl_parser_table_signals[ENTRY_PARSED], - 0, url, metadata); - } else { - g_signal_emit (G_OBJECT (parser), - totem_pl_parser_table_signals[PLAYLIST_STARTED], - 0, url, metadata); - } + EntryParsedSignalData *data; + + /* Make sure to emit the signals asynchronously, as we could be in the main loop + * *or* a worker thread at this point. */ + data = g_new (EntryParsedSignalData, 1); + data->parser = g_object_ref (parser); + data->url = g_strdup (url); + data->metadata = g_hash_table_ref (metadata); + + if (is_playlist == FALSE) + data->signal_id = totem_pl_parser_table_signals[ENTRY_PARSED]; + else + data->signal_id = totem_pl_parser_table_signals[PLAYLIST_STARTED]; + + g_idle_add ((GSourceFunc) emit_entry_parsed_signal, data); } g_hash_table_unref (metadata); @@ -1275,15 +1380,23 @@ { GList *l; - if (parser->priv->ignore_schemes == NULL) + g_mutex_lock (parser->priv->ignore_mutex); + + if (parser->priv->ignore_schemes == NULL) { + g_mutex_unlock (parser->priv->ignore_mutex); return FALSE; + } for (l = parser->priv->ignore_schemes; l != NULL; l = l->next) { const char *scheme = l->data; - if (g_file_has_uri_scheme (file, scheme) != FALSE) + if (g_file_has_uri_scheme (file, scheme) != FALSE) { + g_mutex_unlock (parser->priv->ignore_mutex); return TRUE; + } } + g_mutex_unlock (parser->priv->ignore_mutex); + return FALSE; } @@ -1293,16 +1406,24 @@ { GList *l; - if (parser->priv->ignore_mimetypes == NULL) + g_mutex_lock (parser->priv->ignore_mutex); + + if (parser->priv->ignore_mimetypes == NULL) { + g_mutex_unlock (parser->priv->ignore_mutex); return FALSE; + } for (l = parser->priv->ignore_mimetypes; l != NULL; l = l->next) { const char *item = l->data; - if (strcmp (mimetype, item) == 0) + if (strcmp (mimetype, item) == 0) { + g_mutex_unlock (parser->priv->ignore_mutex); return TRUE; + } } + g_mutex_unlock (parser->priv->ignore_mutex); + return FALSE; } @@ -1596,6 +1717,70 @@ return ret; } +typedef struct { + char *url; + char *base; + gboolean fallback; + TotemPlParserResult retval; + TotemPlParser *parser; +} ParseQueueItem; + +static void +parse_queue_item_free (ParseQueueItem *item) +{ + g_free (item->url); + g_free (item->base); + g_object_unref (item->parser); + + g_free (item); +} + +static gboolean +totem_pl_parser_thread_job_finished (ParseQueueItem *queue_item) +{ + g_signal_emit (queue_item->parser, totem_pl_parser_table_signals[PARSING_FINISHED], 0, queue_item->retval); + parse_queue_item_free (queue_item); + return FALSE; +} + +static gpointer +totem_pl_parser_thread_main (TotemPlParser *parser) +{ + while (parser->priv->thread_stopping == FALSE) { + GFile *file, *base_file; + TotemPlParserResult retval; + ParseQueueItem *queue_item; + + /* Get a job off the queue */ + queue_item = g_async_queue_pop (parser->priv->parse_queue); + + /* Start parsing it */ + file = g_file_new_for_uri (queue_item->url); + base_file = NULL; + + if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE) { + g_object_unref (file); + retval = TOTEM_PL_PARSER_RESULT_UNHANDLED; + } else { + parser->priv->recurse_level = 0; + parser->priv->fallback = queue_item->fallback != FALSE; + if (queue_item->base != NULL) + base_file = g_file_new_for_uri (queue_item->base); + retval = totem_pl_parser_parse_internal (parser, file, base_file); + + g_object_unref (file); + if (base_file != NULL) + g_object_unref (base_file); + } + + /* Emit the "parsing-finished" signal for this job from the main thread */ + queue_item->retval = retval; + g_idle_add ((GSourceFunc) totem_pl_parser_thread_job_finished, queue_item); + } + + return NULL; +} + /** * totem_pl_parser_parse_with_base: * @parser: a #TotemPlParser @@ -1605,8 +1790,14 @@ * end of the playlist on parse failure * * Parses a playlist given by the absolute URL @url, using - * @base to resolve relative paths where appropriate. + * @base to resolve relative paths where appropriate. This method is + * synchronous, and will block on (e.g.) network requests to slow + * servers. totem_pl_parser_parse_with_base_async() is recommended instead. * + * This function will return %TOTEM_PL_PARSER_RESULT_SUCCESS on success, + * and either %TOTEM_PL_PARSER_RESULT_UNHANDLED or + * %TOTEM_PL_PARSER_RESULT_ERROR if the parameters were invalid. + * * Return value: a #TotemPlParserResult **/ TotemPlParserResult @@ -1618,8 +1809,7 @@ g_return_val_if_fail (TOTEM_IS_PL_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED); g_return_val_if_fail (url != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED); - g_return_val_if_fail (strstr (url, "://") != NULL, - TOTEM_PL_PARSER_RESULT_ERROR); + g_return_val_if_fail (strstr (url, "://") != NULL, TOTEM_PL_PARSER_RESULT_ERROR); file = g_file_new_for_uri (url); base_file = NULL; @@ -1643,14 +1833,77 @@ } /** + * totem_pl_parser_parse_with_base_async: + * @parser: a #TotemPlParser + * @url: the URL of the playlist to parse + * @base: the base path for relative filenames + * @fallback: %TRUE if the parser should add the playlist URL to the + * end of the playlist on parse failure + * + * Starts asynchronous parsing of a playlist given by the absolute URL @url, + * using @base to resolve relative paths where appropriate. + * + * This method is asynchronous, and #TotemPlParser::parsing-finished will be + * emitted once parsing is finished (successfully or not). + * + * This function will return %TOTEM_PL_PARSER_RESULT_SUCCESS if the job + * was successfully added to the parse queue, and either + * %TOTEM_PL_PARSER_RESULT_UNHANDLED or %TOTEM_PL_PARSER_RESULT_ERROR + * if the parameters were invalid. + * + * Return value: a #TotemPlParserResult + **/ +TotemPlParserResult +totem_pl_parser_parse_with_base_async (TotemPlParser *parser, const char *url, + const char *base, gboolean fallback) +{ + ParseQueueItem *queue_item; + + g_return_val_if_fail (TOTEM_IS_PL_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED); + g_return_val_if_fail (url != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED); + g_return_val_if_fail (strstr (url, "://") != NULL, TOTEM_PL_PARSER_RESULT_ERROR); + + if (parser->priv->thread == NULL) { + GError *error = NULL; + + /* Create the worker thread */ + parser->priv->parse_queue = g_async_queue_new_full ((GDestroyNotify) parse_queue_item_free); + parser->priv->thread = g_thread_create ((GThreadFunc) totem_pl_parser_thread_main, parser, TRUE, &error); + + if (error != NULL) { + g_warning ("Failed to create TotemPlParser worker thread: %s", error->message); + g_async_queue_unref (parser->priv->parse_queue); + g_error_free (error); + + return TOTEM_PL_PARSER_RESULT_ERROR; + } + } + + /* Thread already exists, so add the parse job to its queue */ + queue_item = g_new (ParseQueueItem, 1); + queue_item->url = g_strdup (url); + queue_item->base = g_strdup (base); + queue_item->fallback = fallback; + queue_item->parser = g_object_ref (parser); + + g_async_queue_push (parser->priv->parse_queue, queue_item); + + return TOTEM_PL_PARSER_RESULT_SUCCESS; +} + +/** * totem_pl_parser_parse: * @parser: a #TotemPlParser * @url: the URL of the playlist to parse * @fallback: %TRUE if the parser should add the playlist URL to the * end of the playlist on parse failure * - * Parses a playlist given by the absolute URL @url. + * Parses a playlist given by the absolute URL @url. This method is + * synchronous, and will block on (e.g.) network requests to slow + * servers. totem_pl_parser_parse_async() is recommended instead. * + * Return values are as totem_pl_parser_parse_with_base(). + * * Return value: a #TotemPlParserResult **/ TotemPlParserResult @@ -1661,6 +1914,29 @@ } /** + * totem_pl_parser_parse_async: + * @parser: a #TotemPlParser + * @url: the URL of the playlist to parse + * @fallback: %TRUE if the parser should add the playlist URL to the + * end of the playlist on parse failure + * + * Starts asynchronous parsing of a playlist given by the absolute URL @url. + * + * This method is asynchronous, and #TotemPlParser::parsing-finished will be + * emitted once parsing is finished (successfully or not). + * + * Return values are as totem_pl_parser_parse_with_base_async(). + * + * Return value: a #TotemPlParserResult + **/ +TotemPlParserResult +totem_pl_parser_parse_async (TotemPlParser *parser, const char *url, + gboolean fallback) +{ + return totem_pl_parser_parse_with_base_async (parser, url, NULL, fallback); +} + +/** * totem_pl_parser_add_ignored_scheme: * @parser: a #TotemPlParser * @scheme: the scheme to ignore @@ -1676,11 +1952,15 @@ g_return_if_fail (TOTEM_IS_PL_PARSER (parser)); + g_mutex_lock (parser->priv->ignore_mutex); + s = g_strdup (scheme); if (s[strlen (s) - 1] == ':') s[strlen (s) - 1] = '\0'; parser->priv->ignore_schemes = g_list_prepend (parser->priv->ignore_schemes, s); + + g_mutex_unlock (parser->priv->ignore_mutex); } /**