mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
719 lines
17 KiB
C
719 lines
17 KiB
C
/* Clapper Application
|
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
|
*
|
|
* 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 3 of the License, or
|
|
* (at your option) any later 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "clapper-app-utils.h"
|
|
#include "clapper-app-media-item-box.h"
|
|
|
|
#ifdef HAVE_GRAPHVIZ
|
|
#include <graphviz/cgraph.h>
|
|
#include <graphviz/gvc.h>
|
|
#endif
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include <windows.h>
|
|
#ifdef HAVE_WIN_PROCESS_THREADS_API
|
|
#include <processthreadsapi.h>
|
|
#endif
|
|
#ifdef HAVE_WIN_TIME_API
|
|
#include <timeapi.h>
|
|
#endif
|
|
#endif
|
|
|
|
#define GST_CAT_DEFAULT clapper_app_utils_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
void
|
|
clapper_app_utils_debug_init (void)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperapputils", 0,
|
|
"Clapper App Utils");
|
|
}
|
|
|
|
/* Windows specific functions */
|
|
#ifdef G_OS_WIN32
|
|
|
|
/*
|
|
* clapper_app_utils_win_enforce_hi_res_clock:
|
|
*
|
|
* Enforce high resolution clock by explicitly disabling Windows
|
|
* timer resolution power throttling. When disabled, system remembers
|
|
* and honors any previous timer resolution request by the process.
|
|
*
|
|
* By default, Windows 11 may automatically ignore the timer
|
|
* resolution requests in certain scenarios.
|
|
*/
|
|
void
|
|
clapper_app_utils_win_enforce_hi_res_clock (void)
|
|
{
|
|
#ifdef HAVE_WIN_PROCESS_THREADS_API
|
|
PROCESS_POWER_THROTTLING_STATE PowerThrottling;
|
|
gboolean success;
|
|
|
|
RtlZeroMemory (&PowerThrottling, sizeof (PowerThrottling));
|
|
|
|
PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
|
|
PowerThrottling.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
|
|
PowerThrottling.StateMask = 0; // Always honor timer resolution requests
|
|
|
|
success = (gboolean) SetProcessInformation(
|
|
GetCurrentProcess (),
|
|
ProcessPowerThrottling,
|
|
&PowerThrottling,
|
|
sizeof (PowerThrottling));
|
|
|
|
/* Not an error. Older Windows does not have this functionality, but
|
|
* also honor hi-res clock by default anyway, so do not print then. */
|
|
GST_INFO ("Windows hi-res clock support is %senforced",
|
|
(success) ? "" : "NOT ");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* clapper_app_utils_win_hi_res_clock_start:
|
|
*
|
|
* Start Windows high resolution clock which will improve
|
|
* accuracy of various Windows timer APIs and precision
|
|
* of #GstSystemClock during playback.
|
|
*
|
|
* On Windows 10 version 2004 (and older), this function affects
|
|
* a global Windows setting. On any other (newer) version this
|
|
* will only affect a single process.
|
|
*
|
|
* Returns: Timer resolution period value.
|
|
*/
|
|
guint
|
|
clapper_app_utils_win_hi_res_clock_start (void)
|
|
{
|
|
guint resolution = 0;
|
|
|
|
#ifdef HAVE_WIN_TIME_API
|
|
TIMECAPS time_caps;
|
|
MMRESULT res;
|
|
|
|
if ((res = timeGetDevCaps (&time_caps, sizeof (TIMECAPS))) != TIMERR_NOERROR) {
|
|
GST_WARNING ("Could not query timer resolution, code: %u", res);
|
|
return 0;
|
|
}
|
|
|
|
resolution = MIN (MAX (time_caps.wPeriodMin, 1), time_caps.wPeriodMax);
|
|
|
|
if ((res = timeBeginPeriod (resolution)) != TIMERR_NOERROR) {
|
|
GST_WARNING ("Could not request timer resolution, code: %u", res);
|
|
return 0;
|
|
}
|
|
|
|
GST_INFO ("Started Windows hi-res clock, precision: %ums", resolution);
|
|
#endif
|
|
|
|
return resolution;
|
|
}
|
|
|
|
/*
|
|
* clapper_app_utils_win_hi_res_clock_stop:
|
|
* @resolution: started resolution value (non-zero)
|
|
*
|
|
* Stop previously started Microsoft Windows high resolution clock.
|
|
*/
|
|
void
|
|
clapper_app_utils_win_hi_res_clock_stop (guint resolution)
|
|
{
|
|
#ifdef HAVE_WIN_TIME_API
|
|
MMRESULT res;
|
|
|
|
if ((res = timeEndPeriod (resolution)) == TIMERR_NOERROR)
|
|
GST_INFO ("Stopped Windows hi-res clock");
|
|
else
|
|
GST_ERROR ("Could not stop hi-res clock, code: %u", res);
|
|
#endif
|
|
}
|
|
|
|
/* Extensions are used only on Windows */
|
|
const gchar *const *
|
|
clapper_app_utils_get_extensions (void)
|
|
{
|
|
static const gchar *const all_extensions[] = {
|
|
"avi", "claps", "m2ts", "mkv", "mov",
|
|
"mp4", "webm", "wmv", NULL
|
|
};
|
|
|
|
return all_extensions;
|
|
}
|
|
|
|
const gchar *const *
|
|
clapper_app_utils_get_subtitles_extensions (void)
|
|
{
|
|
static const gchar *const subs_extensions[] = {
|
|
"srt", "vtt", NULL
|
|
};
|
|
|
|
return subs_extensions;
|
|
}
|
|
#endif // G_OS_WIN32
|
|
|
|
const gchar *const *
|
|
clapper_app_utils_get_mime_types (void)
|
|
{
|
|
static const gchar *const all_mime_types[] = {
|
|
"video/*",
|
|
"audio/*",
|
|
"application/claps",
|
|
"application/x-subrip",
|
|
"text/x-ssa",
|
|
NULL
|
|
};
|
|
|
|
return all_mime_types;
|
|
}
|
|
|
|
const gchar *const *
|
|
clapper_app_utils_get_subtitles_mime_types (void)
|
|
{
|
|
static const gchar *const subs_mime_types[] = {
|
|
"application/x-subrip",
|
|
"text/x-ssa",
|
|
NULL
|
|
};
|
|
|
|
return subs_mime_types;
|
|
}
|
|
|
|
void
|
|
clapper_app_utils_parse_progression (ClapperQueueProgressionMode mode,
|
|
const gchar **icon, const gchar **label)
|
|
{
|
|
const gchar *const icon_names[] = {
|
|
"action-unavailable-symbolic",
|
|
"media-playlist-consecutive-symbolic",
|
|
"media-playlist-repeat-song-symbolic",
|
|
"media-playlist-repeat-symbolic",
|
|
"media-playlist-shuffle-symbolic",
|
|
NULL
|
|
};
|
|
const gchar *const labels[] = {
|
|
_("No progression"),
|
|
_("Consecutive"),
|
|
_("Repeat item"),
|
|
_("Carousel"),
|
|
_("Shuffle"),
|
|
NULL
|
|
};
|
|
|
|
*icon = icon_names[mode];
|
|
*label = labels[mode];
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_is_subtitles_file (GFile *file)
|
|
{
|
|
GFileInfo *info;
|
|
gboolean is_subs = FALSE;
|
|
|
|
if ((info = g_file_query_info (file,
|
|
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
|
|
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL, NULL))) {
|
|
const gchar *content_type = NULL;
|
|
|
|
if (g_file_info_has_attribute (info,
|
|
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)) {
|
|
content_type = g_file_info_get_content_type (info);
|
|
} else if (g_file_info_has_attribute (info,
|
|
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE)) {
|
|
content_type = g_file_info_get_attribute_string (info,
|
|
G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
|
|
}
|
|
|
|
is_subs = (content_type && g_strv_contains (
|
|
clapper_app_utils_get_subtitles_mime_types (),
|
|
content_type));
|
|
|
|
g_object_unref (info);
|
|
}
|
|
|
|
return is_subs;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_value_for_item_is_valid (const GValue *value)
|
|
{
|
|
if (G_VALUE_HOLDS (value, GTK_TYPE_WIDGET))
|
|
return CLAPPER_APP_IS_MEDIA_ITEM_BOX (g_value_get_object (value));
|
|
|
|
if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)
|
|
|| G_VALUE_HOLDS (value, G_TYPE_FILE))
|
|
return TRUE;
|
|
|
|
if (G_VALUE_HOLDS (value, G_TYPE_STRING))
|
|
return gst_uri_is_valid (g_value_get_string (value));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_files_from_list_model (GListModel *files_model, GFile ***files, gint *n_files)
|
|
{
|
|
guint i, len = g_list_model_get_n_items (files_model);
|
|
|
|
if (G_UNLIKELY (len == 0 || len > G_MAXINT))
|
|
return FALSE;
|
|
|
|
*files = g_new (GFile *, len + 1);
|
|
|
|
if (n_files)
|
|
*n_files = (gint) len;
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
(*files)[i] = g_list_model_get_item (files_model, i);
|
|
}
|
|
(*files)[i] = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_files_from_slist (GSList *file_list, GFile ***files, gint *n_files)
|
|
{
|
|
GSList *fl;
|
|
guint len, i = 0;
|
|
|
|
len = g_slist_length (file_list);
|
|
|
|
if (G_UNLIKELY (len == 0 || len > G_MAXINT))
|
|
return FALSE;
|
|
|
|
*files = g_new (GFile *, len + 1);
|
|
|
|
if (n_files)
|
|
*n_files = (gint) len;
|
|
|
|
for (fl = file_list; fl != NULL; fl = fl->next) {
|
|
(*files)[i] = (GFile *) g_object_ref (fl->data);
|
|
i++;
|
|
}
|
|
(*files)[i] = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_files_from_string (const gchar *string, GFile ***files, gint *n_files)
|
|
{
|
|
GSList *slist = NULL;
|
|
gchar **uris = g_strsplit (string, "\n", 0);
|
|
guint i;
|
|
gboolean success;
|
|
|
|
for (i = 0; uris[i]; ++i) {
|
|
const gchar *uri = uris[i];
|
|
|
|
if (!gst_uri_is_valid (uri))
|
|
continue;
|
|
|
|
slist = g_slist_append (slist, g_file_new_for_uri (uri));
|
|
}
|
|
|
|
g_strfreev (uris);
|
|
|
|
if (!slist)
|
|
return FALSE;
|
|
|
|
success = clapper_app_utils_files_from_slist (slist, files, n_files);
|
|
g_slist_free_full (slist, g_object_unref);
|
|
|
|
return success;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_files_from_command_line (GApplicationCommandLine *cmd_line, GFile ***files, gint *n_files)
|
|
{
|
|
GSList *slist = NULL;
|
|
gchar **argv;
|
|
gint i, argc = 0;
|
|
gboolean success;
|
|
|
|
argv = g_application_command_line_get_arguments (cmd_line, &argc);
|
|
|
|
for (i = 1; i < argc; ++i)
|
|
slist = g_slist_append (slist, g_application_command_line_create_file_for_arg (cmd_line, argv[i]));
|
|
|
|
g_strfreev (argv);
|
|
|
|
if (!slist)
|
|
return FALSE;
|
|
|
|
success = clapper_app_utils_files_from_slist (slist, files, n_files);
|
|
g_slist_free_full (slist, g_object_unref);
|
|
|
|
return success;
|
|
}
|
|
|
|
static inline gboolean
|
|
_files_from_file (GFile *file, GFile ***files, gint *n_files)
|
|
{
|
|
*files = g_new (GFile *, 2);
|
|
|
|
(*files)[0] = g_object_ref (file);
|
|
(*files)[1] = NULL;
|
|
|
|
if (n_files)
|
|
*n_files = 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clapper_app_utils_files_from_value (const GValue *value, GFile ***files, gint *n_files)
|
|
{
|
|
if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) {
|
|
return clapper_app_utils_files_from_slist (
|
|
(GSList *) g_value_get_boxed (value), files, n_files);
|
|
} else if (G_VALUE_HOLDS (value, G_TYPE_FILE)) {
|
|
return _files_from_file (
|
|
(GFile *) g_value_get_object (value), files, n_files);
|
|
} else if (G_VALUE_HOLDS (value, G_TYPE_STRING)) {
|
|
return clapper_app_utils_files_from_string (
|
|
g_value_get_string (value), files, n_files);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
clapper_app_utils_files_free (GFile **files)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; files[i]; ++i)
|
|
g_object_unref (files[i]);
|
|
|
|
g_free (files);
|
|
}
|
|
|
|
static inline gboolean
|
|
_parse_feature_name (gchar *str, const gchar **feature_name)
|
|
{
|
|
if (!str)
|
|
return FALSE;
|
|
|
|
g_strstrip (str);
|
|
|
|
if (str[0] == '\0')
|
|
return FALSE;
|
|
|
|
*feature_name = str;
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gboolean
|
|
_parse_feature_rank (gchar *str, GstRank *rank)
|
|
{
|
|
if (!str)
|
|
return FALSE;
|
|
|
|
g_strstrip (str);
|
|
|
|
if (str[0] == '\0')
|
|
return FALSE;
|
|
|
|
if (g_ascii_isdigit (str[0])) {
|
|
gulong l;
|
|
gchar *endptr;
|
|
|
|
l = strtoul (str, &endptr, 10);
|
|
if (endptr > str && endptr[0] == 0) {
|
|
*rank = (GstRank) l;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
} else if (g_ascii_strcasecmp (str, "NONE") == 0) {
|
|
*rank = GST_RANK_NONE;
|
|
} else if (g_ascii_strcasecmp (str, "MARGINAL") == 0) {
|
|
*rank = GST_RANK_MARGINAL;
|
|
} else if (g_ascii_strcasecmp (str, "SECONDARY") == 0) {
|
|
*rank = GST_RANK_SECONDARY;
|
|
} else if (g_ascii_strcasecmp (str, "PRIMARY") == 0) {
|
|
*rank = GST_RANK_PRIMARY;
|
|
} else if (g_ascii_strcasecmp (str, "MAX") == 0) {
|
|
*rank = (GstRank) G_MAXINT;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
clapper_app_utils_iterate_plugin_feature_ranks (GSettings *settings,
|
|
ClapperAppUtilsIterRanks callback, gpointer user_data)
|
|
{
|
|
gchar **split, **walk, *stored_overrides;
|
|
const gchar *env_overrides;
|
|
gboolean from_env = FALSE;
|
|
|
|
stored_overrides = g_settings_get_string (settings, "plugin-feature-ranks");
|
|
env_overrides = g_getenv ("GST_PLUGIN_FEATURE_RANK");
|
|
|
|
/* Iterate from GSettings, then from ENV */
|
|
parse_overrides:
|
|
split = g_strsplit ((from_env) ? env_overrides : stored_overrides, ",", 0);
|
|
|
|
for (walk = split; *walk; walk++) {
|
|
gchar **values;
|
|
|
|
if (!strchr (*walk, ':'))
|
|
continue;
|
|
|
|
values = g_strsplit (*walk, ":", 2);
|
|
|
|
if (g_strv_length (values) == 2) {
|
|
GstRank rank;
|
|
const gchar *feature_name;
|
|
|
|
if (_parse_feature_name (values[0], &feature_name)
|
|
&& _parse_feature_rank (values[1], &rank))
|
|
callback (feature_name, rank, from_env, user_data);
|
|
}
|
|
|
|
g_strfreev (values);
|
|
}
|
|
|
|
g_strfreev (split);
|
|
|
|
if (!from_env && env_overrides) {
|
|
from_env = TRUE;
|
|
goto parse_overrides;
|
|
}
|
|
|
|
g_free (stored_overrides);
|
|
}
|
|
|
|
GstElement *
|
|
clapper_app_utils_make_element (const gchar *string)
|
|
{
|
|
gchar *char_loc;
|
|
|
|
if (strcmp (string, "none") == 0)
|
|
return NULL;
|
|
|
|
char_loc = strchr (string, ' ');
|
|
|
|
if (char_loc) {
|
|
GstElement *element;
|
|
GError *error = NULL;
|
|
|
|
element = gst_parse_bin_from_description (string, TRUE, &error);
|
|
if (error) {
|
|
GST_ERROR ("Bin parse error: \"%s\", reason: %s", string, error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
return gst_element_factory_make (string, NULL);
|
|
}
|
|
|
|
/*
|
|
* _get_tmp_dir:
|
|
* @subdir: (nullable): an optional subdirectory
|
|
*
|
|
* Returns: (transfer full): a newly constructed #GFile
|
|
*/
|
|
static inline GFile *
|
|
_get_tmp_dir (const gchar *subdir)
|
|
{
|
|
/* XXX: System tmp directory does not work within containers such as Flatpak
|
|
* for our usage with file launcher, so make our own temp in app data dir */
|
|
return g_file_new_build_filename (
|
|
g_get_user_data_dir (), CLAPPER_APP_ID, "tmp", subdir, NULL);
|
|
}
|
|
|
|
#ifdef HAVE_GRAPHVIZ
|
|
static GFile *
|
|
_create_tmp_subdir (const gchar *subdir, GCancellable *cancellable, GError **error)
|
|
{
|
|
GFile *tmp_dir;
|
|
GError *my_error = NULL;
|
|
|
|
tmp_dir = _get_tmp_dir (subdir);
|
|
|
|
if (!g_file_make_directory_with_parents (tmp_dir, cancellable, &my_error)) {
|
|
if (my_error->domain != G_IO_ERROR || my_error->code != G_IO_ERROR_EXISTS) {
|
|
*error = g_error_copy (my_error);
|
|
g_clear_object (&tmp_dir); // return NULL
|
|
}
|
|
g_error_free (my_error);
|
|
}
|
|
|
|
return tmp_dir;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
_create_pipeline_svg_file_in_thread (GTask *task, GObject *source G_GNUC_UNUSED,
|
|
ClapperPlayer *player, GCancellable *cancellable)
|
|
{
|
|
GFile *tmp_file = NULL;
|
|
GError *error = NULL;
|
|
|
|
#ifdef HAVE_GRAPHVIZ
|
|
GFile *tmp_subdir;
|
|
Agraph_t *graph;
|
|
GVC_t *gvc;
|
|
gchar *path, *template = NULL, *dot_data = NULL, *img_data = NULL;
|
|
gint fd;
|
|
gsize size = 0;
|
|
|
|
if (!(tmp_subdir = _create_tmp_subdir ("pipelines", cancellable, &error)))
|
|
goto finish;
|
|
|
|
path = g_file_get_path (tmp_subdir);
|
|
g_object_unref (tmp_subdir);
|
|
|
|
template = g_build_filename (path, "pipeline-XXXXXX.svg", NULL);
|
|
g_free (path);
|
|
|
|
fd = g_mkstemp (template); // Modifies template to actual filename
|
|
|
|
if (G_UNLIKELY (fd == -1)) {
|
|
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Could not open temp file for writing");
|
|
goto finish;
|
|
}
|
|
|
|
dot_data = clapper_player_make_pipeline_graph (player, GST_DEBUG_GRAPH_SHOW_ALL);
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
goto close_and_finish;
|
|
|
|
graph = agmemread (dot_data);
|
|
|
|
gvc = gvContext ();
|
|
gvLayout (gvc, graph, "dot");
|
|
|
|
#ifdef HAVE_GVC_13
|
|
gvRenderData (gvc, graph, "svg", &img_data, &size);
|
|
#else
|
|
{
|
|
guint tmp_size = 0; // Temporary uint to satisfy older API
|
|
gvRenderData (gvc, graph, "svg", &img_data, &tmp_size);
|
|
size = tmp_size;
|
|
}
|
|
#endif
|
|
|
|
agclose (graph);
|
|
gvFreeContext (gvc);
|
|
|
|
if (g_cancellable_is_cancelled (cancellable))
|
|
goto close_and_finish;
|
|
|
|
if (write (fd, img_data, size) != -1) {
|
|
tmp_file = g_file_new_for_path (template);
|
|
} else {
|
|
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Could not write data to temp file");
|
|
}
|
|
|
|
close_and_finish:
|
|
/* Always close the file IO */
|
|
if (G_UNLIKELY (close (fd) == -1))
|
|
GST_ERROR ("Could not close temp file!");
|
|
|
|
finish:
|
|
g_free (template);
|
|
g_free (dot_data);
|
|
g_free (img_data);
|
|
#else
|
|
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Cannot create graph file when compiled without Graphviz");
|
|
#endif
|
|
|
|
if (tmp_file)
|
|
g_task_return_pointer (task, tmp_file, (GDestroyNotify) g_object_unref);
|
|
else
|
|
g_task_return_error (task, error);
|
|
}
|
|
|
|
void
|
|
clapper_app_utils_create_pipeline_svg_file_async (ClapperPlayer *player,
|
|
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (NULL, cancellable, callback, user_data);
|
|
g_task_set_task_data (task, gst_object_ref (player), (GDestroyNotify) gst_object_unref);
|
|
g_task_run_in_thread (task, (GTaskThreadFunc) _create_pipeline_svg_file_in_thread);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
_delete_dir_recursive (GFile *dir, GError **error)
|
|
{
|
|
GFileEnumerator *dir_enum;
|
|
|
|
if ((dir_enum = g_file_enumerate_children (dir,
|
|
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error))) {
|
|
while (TRUE) {
|
|
GFileInfo *info = NULL;
|
|
GFile *child = NULL;
|
|
|
|
if (!g_file_enumerator_iterate (dir_enum, &info,
|
|
&child, NULL, error) || !info)
|
|
break;
|
|
|
|
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
|
|
if (!_delete_dir_recursive (child, error))
|
|
break;
|
|
} else if (!g_file_delete (child, NULL, error)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_object_unref (dir_enum);
|
|
}
|
|
|
|
if (*error != NULL)
|
|
return FALSE;
|
|
|
|
return g_file_delete (dir, NULL, error);
|
|
}
|
|
|
|
void
|
|
clapper_app_utils_delete_tmp_dir (void)
|
|
{
|
|
GFile *tmp_dir = _get_tmp_dir (NULL);
|
|
GError *error = NULL;
|
|
|
|
if (!_delete_dir_recursive (tmp_dir, &error)) {
|
|
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_NOT_FOUND) {
|
|
GST_ERROR ("Could not remove temp dir, reason: %s",
|
|
GST_STR_NULL (error->message));
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_object_unref (tmp_dir);
|
|
}
|