725 lines
21 KiB
C
725 lines
21 KiB
C
|
/* ide-template-base.c
|
||
|
*
|
||
|
* Copyright 2016-2019 Christian Hergert <chergert@redhat.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 <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
*/
|
||
|
|
||
|
#define G_LOG_DOMAIN "ide-template-base"
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <glib/gstdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <libide-threading.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "ide-template-base.h"
|
||
|
|
||
|
#define TIMEOUT_INTERVAL_MSEC 17
|
||
|
#define TIMEOUT_DURATION_MSEC 2
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
TmplTemplateLocator *locator;
|
||
|
GArray *files;
|
||
|
|
||
|
guint has_expanded : 1;
|
||
|
} IdeTemplateBasePrivate;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
GFile *file;
|
||
|
GInputStream *stream;
|
||
|
TmplScope *scope;
|
||
|
GFile *destination;
|
||
|
TmplTemplate *template;
|
||
|
gchar *result;
|
||
|
gint mode;
|
||
|
} FileExpansion;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
GArray *files;
|
||
|
guint index;
|
||
|
guint completed;
|
||
|
} ExpansionTask;
|
||
|
|
||
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeTemplateBase, ide_template_base, G_TYPE_OBJECT)
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_LOCATOR,
|
||
|
LAST_PROP
|
||
|
};
|
||
|
|
||
|
static GParamSpec *properties [LAST_PROP];
|
||
|
|
||
|
static void
|
||
|
ide_template_base_mkdirs_worker (IdeTask *task,
|
||
|
gpointer source_object,
|
||
|
gpointer task_data,
|
||
|
GCancellable *cancellable)
|
||
|
{
|
||
|
IdeTemplateBase *self = source_object;
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
|
||
|
for (guint i = 0; i < priv->files->len; i++)
|
||
|
{
|
||
|
FileExpansion *fexp = &g_array_index (priv->files, FileExpansion, i);
|
||
|
g_autoptr(GFile) directory = NULL;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
|
||
|
directory = g_file_get_parent (fexp->destination);
|
||
|
|
||
|
if (!g_file_make_directory_with_parents (directory, cancellable, &error))
|
||
|
{
|
||
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
||
|
{
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ide_task_return_boolean (task, TRUE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_mkdirs_async (IdeTemplateBase *self,
|
||
|
GCancellable *cancellable,
|
||
|
GAsyncReadyCallback callback,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
g_autoptr(IdeTask) task = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||
|
|
||
|
task = ide_task_new (self, cancellable, callback, user_data);
|
||
|
ide_task_run_in_thread (task, ide_template_base_mkdirs_worker);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_template_base_mkdirs_finish (IdeTemplateBase *self,
|
||
|
GAsyncResult *result,
|
||
|
GError **error)
|
||
|
{
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (IDE_IS_TASK (result));
|
||
|
|
||
|
return ide_task_propagate_boolean (IDE_TASK (result), error);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_template_base_get_locator:
|
||
|
* @self: An #IdeTemplateBase
|
||
|
*
|
||
|
* Fetches the #TmplTemplateLocator used for resolving templates.
|
||
|
*
|
||
|
* Returns: (transfer none) (nullable): a #TmplTemplateLocator or %NULL.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
TmplTemplateLocator *
|
||
|
ide_template_base_get_locator (IdeTemplateBase *self)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TEMPLATE_BASE (self), NULL);
|
||
|
|
||
|
return priv->locator;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_template_base_set_locator (IdeTemplateBase *self,
|
||
|
TmplTemplateLocator *locator)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_return_if_fail (!locator || TMPL_IS_TEMPLATE_LOCATOR (locator));
|
||
|
|
||
|
if (priv->has_expanded)
|
||
|
{
|
||
|
g_warning ("Cannot change template locator after "
|
||
|
"ide_template_base_expand_all_async() has been called.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (g_set_object (&priv->locator, locator))
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LOCATOR]);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
clear_file_expansion (gpointer data)
|
||
|
{
|
||
|
FileExpansion *expansion = data;
|
||
|
|
||
|
g_clear_object (&expansion->file);
|
||
|
g_clear_object (&expansion->stream);
|
||
|
g_clear_pointer (&expansion->scope, tmpl_scope_unref);
|
||
|
g_clear_object (&expansion->destination);
|
||
|
g_clear_object (&expansion->template);
|
||
|
g_clear_pointer (&expansion->result, g_free);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeTemplateBase *self = IDE_TEMPLATE_BASE(object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_LOCATOR:
|
||
|
g_value_set_object (value, ide_template_base_get_locator (self));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_set_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeTemplateBase *self = IDE_TEMPLATE_BASE(object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_LOCATOR:
|
||
|
ide_template_base_set_locator (self, g_value_get_object (value));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_finalize (GObject *object)
|
||
|
{
|
||
|
IdeTemplateBase *self = (IdeTemplateBase *)object;
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_clear_pointer (&priv->files, g_array_unref);
|
||
|
g_clear_object (&priv->locator);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_template_base_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_class_init (IdeTemplateBaseClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->finalize = ide_template_base_finalize;
|
||
|
object_class->get_property = ide_template_base_get_property;
|
||
|
object_class->set_property = ide_template_base_set_property;
|
||
|
|
||
|
/**
|
||
|
* IdeTemplateBase:locator:
|
||
|
*
|
||
|
* The #IdeTemplateBase:locator property contains the #TmplTemplateLocator
|
||
|
* that should be used to resolve template includes. If %NULL, templates
|
||
|
* will not be allowed to include other templates.
|
||
|
* directive.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
properties [PROP_LOCATOR] =
|
||
|
g_param_spec_object ("locator",
|
||
|
"Locator",
|
||
|
"Locator",
|
||
|
TMPL_TYPE_TEMPLATE_LOCATOR,
|
||
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
g_object_class_install_properties (object_class, LAST_PROP, properties);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_init (IdeTemplateBase *self)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
priv->files = g_array_new (FALSE, TRUE, sizeof (FileExpansion));
|
||
|
g_array_set_clear_func (priv->files, clear_file_expansion);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_parse_worker (IdeTask *task,
|
||
|
gpointer source_object,
|
||
|
gpointer task_data,
|
||
|
GCancellable *cancellable)
|
||
|
{
|
||
|
IdeTemplateBase *self = source_object;
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||
|
|
||
|
for (guint i = 0; i < priv->files->len; i++)
|
||
|
{
|
||
|
FileExpansion *fexp = &g_array_index (priv->files, FileExpansion, i);
|
||
|
g_autoptr(TmplTemplate) template = NULL;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
|
||
|
if (fexp->template != NULL)
|
||
|
continue;
|
||
|
|
||
|
template = tmpl_template_new (priv->locator);
|
||
|
|
||
|
if (!tmpl_template_parse_file (template, fexp->file, cancellable, &error))
|
||
|
{
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fexp->template = g_object_ref (template);
|
||
|
}
|
||
|
|
||
|
ide_task_return_boolean (task, TRUE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_parse_async (IdeTemplateBase *self,
|
||
|
GCancellable *cancellable,
|
||
|
GAsyncReadyCallback callback,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
g_autoptr(IdeTask) task = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||
|
|
||
|
task = ide_task_new (self, cancellable, callback, user_data);
|
||
|
ide_task_run_in_thread (task, ide_template_base_parse_worker);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_template_base_parse_finish (IdeTemplateBase *self,
|
||
|
GAsyncResult *result,
|
||
|
GError **error)
|
||
|
{
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (IDE_IS_TASK (result));
|
||
|
|
||
|
return ide_task_propagate_boolean (IDE_TASK (result), error);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_replace_cb (GObject *object,
|
||
|
GAsyncResult *result,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
GFile *file = (GFile *)object;
|
||
|
g_autoptr(IdeTask) task = user_data;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
ExpansionTask *expansion;
|
||
|
FileExpansion *fexp = NULL;
|
||
|
guint i;
|
||
|
|
||
|
g_assert (G_IS_FILE (file));
|
||
|
g_assert (G_IS_ASYNC_RESULT (result));
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
|
||
|
expansion = ide_task_get_task_data (task);
|
||
|
|
||
|
g_assert (expansion != NULL);
|
||
|
g_assert (expansion->files != NULL);
|
||
|
|
||
|
expansion->completed++;
|
||
|
|
||
|
/*
|
||
|
* Complete the file replacement operation.
|
||
|
*/
|
||
|
if (!g_file_replace_contents_finish (file, result, NULL, &error))
|
||
|
{
|
||
|
if (!ide_task_get_completed (task))
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Locate the FileExpansion. We could remove this by tracking some
|
||
|
* state in the callback, but that is more complex than it's worth
|
||
|
* since we share the task between all the callbacks.
|
||
|
*/
|
||
|
for (i = 0; i < expansion->files->len; i++)
|
||
|
{
|
||
|
FileExpansion *item = &g_array_index (expansion->files, FileExpansion, i);
|
||
|
|
||
|
if (g_file_equal (item->destination, file))
|
||
|
{
|
||
|
fexp = item;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Unfortunately, we don't have a nice portable API to define modes.
|
||
|
* So we limit our ability to chmod() to the local file-system.
|
||
|
* This still works for things like FUSE, so much as they support
|
||
|
* the posix chmod() API.
|
||
|
*/
|
||
|
if ((fexp != NULL) && (fexp->mode != 0) && g_file_is_native (file))
|
||
|
{
|
||
|
g_autofree gchar *path = g_file_get_path (file);
|
||
|
|
||
|
if (0 != g_chmod (path, fexp->mode))
|
||
|
g_warning ("chmod(\"%s\", 0%o) failed with: %s",
|
||
|
path, fexp->mode, strerror (errno));
|
||
|
}
|
||
|
|
||
|
if (expansion->completed == expansion->files->len)
|
||
|
{
|
||
|
if (!ide_task_get_completed (task))
|
||
|
ide_task_return_boolean (task, TRUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_template_base_expand (IdeTask *task)
|
||
|
{
|
||
|
ExpansionTask *expansion;
|
||
|
gint64 end;
|
||
|
gint64 now;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
|
||
|
expansion = ide_task_get_task_data (task);
|
||
|
|
||
|
g_assert (expansion != NULL);
|
||
|
g_assert (expansion->files != NULL);
|
||
|
|
||
|
/*
|
||
|
* We will only run for up to 2 milliseconds before we want to yield
|
||
|
* back to the main loop and schedule future expansions as low-priority
|
||
|
* so that we do not block the frame-clock;
|
||
|
*/
|
||
|
for (end = (now = g_get_monotonic_time ()) + ((G_USEC_PER_SEC / 1000) * TIMEOUT_DURATION_MSEC);
|
||
|
now < end;
|
||
|
now = g_get_monotonic_time ())
|
||
|
{
|
||
|
FileExpansion *fexp;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
|
||
|
g_assert (expansion->index <= expansion->files->len);
|
||
|
|
||
|
if (expansion->index == expansion->files->len)
|
||
|
break;
|
||
|
|
||
|
fexp = &g_array_index (expansion->files, FileExpansion, expansion->index);
|
||
|
|
||
|
g_assert (fexp != NULL);
|
||
|
g_assert (fexp->template != NULL);
|
||
|
g_assert (fexp->scope != NULL);
|
||
|
g_assert (fexp->result == NULL);
|
||
|
|
||
|
fexp->result = tmpl_template_expand_string (fexp->template, fexp->scope, &error);
|
||
|
|
||
|
if (fexp->result == NULL)
|
||
|
{
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
expansion->index++;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If we have completed expanding all the templates, we need to start
|
||
|
* writing the results to the destination files asynchronously, and in
|
||
|
* parallel. When all of the async operations have completed, we will
|
||
|
* cleanup and complete the task.
|
||
|
*/
|
||
|
if (expansion->index == expansion->files->len)
|
||
|
{
|
||
|
guint i;
|
||
|
|
||
|
expansion->completed = 0;
|
||
|
|
||
|
//ide_template_base_make_directories (task);
|
||
|
|
||
|
for (i = 0; i < expansion->files->len; i++)
|
||
|
{
|
||
|
FileExpansion *fexp = &g_array_index (expansion->files, FileExpansion, i);
|
||
|
|
||
|
g_assert (fexp != NULL);
|
||
|
g_assert (G_IS_FILE (fexp->destination));
|
||
|
g_assert (fexp->result != NULL);
|
||
|
|
||
|
g_file_replace_contents_async (fexp->destination,
|
||
|
fexp->result,
|
||
|
strlen (fexp->result),
|
||
|
NULL,
|
||
|
FALSE,
|
||
|
G_FILE_CREATE_REPLACE_DESTINATION,
|
||
|
ide_task_get_cancellable (task),
|
||
|
ide_template_base_replace_cb,
|
||
|
g_object_ref (task));
|
||
|
}
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
return G_SOURCE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_expand_parse_cb (GObject *object,
|
||
|
GAsyncResult *result,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeTemplateBase *self = (IdeTemplateBase *)object;
|
||
|
g_autoptr(IdeTask) task = user_data;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
|
||
|
if (!ide_template_base_parse_finish (self, result, &error))
|
||
|
{
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
g_timeout_add_full (G_PRIORITY_LOW,
|
||
|
TIMEOUT_INTERVAL_MSEC,
|
||
|
(GSourceFunc)ide_template_base_expand,
|
||
|
g_object_ref (task),
|
||
|
g_object_unref);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_template_base_expand_mkdirs_cb (GObject *object,
|
||
|
GAsyncResult *result,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeTemplateBase *self = (IdeTemplateBase *)object;
|
||
|
g_autoptr(IdeTask) task = user_data;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
GCancellable *cancellable;
|
||
|
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
|
||
|
cancellable = ide_task_get_cancellable (task);
|
||
|
|
||
|
if (!ide_template_base_mkdirs_finish (self, result, &error))
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
else
|
||
|
ide_template_base_parse_async (self,
|
||
|
cancellable,
|
||
|
ide_template_base_expand_parse_cb,
|
||
|
g_steal_pointer (&task));
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_template_base_expand_all_async (IdeTemplateBase *self,
|
||
|
GCancellable *cancellable,
|
||
|
GAsyncReadyCallback callback,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
g_autoptr(IdeTask) task = NULL;
|
||
|
ExpansionTask *task_data;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||
|
|
||
|
task_data = g_new0 (ExpansionTask, 1);
|
||
|
task_data->files = priv->files;
|
||
|
task_data->index = 0;
|
||
|
task_data->completed = 0;
|
||
|
|
||
|
/*
|
||
|
* The expand process will need to call tmpl_template_expand() and we want
|
||
|
* that to happen in the main loop so that all scoped objects need not be
|
||
|
* thread-safe.
|
||
|
*
|
||
|
* Therefore, the first step is to asynchronously load all of the templates
|
||
|
* from storage. After that, we will expand the templates into memory,
|
||
|
* being careful about how long we run per-cycle in the main-loop. If we
|
||
|
* run too long, we risk adding jitter to the frame-clock and causing UI
|
||
|
* elements to feel sluggish.
|
||
|
*
|
||
|
* Once we have all of our templates expanded, we progress to asynchronously
|
||
|
* write them to the requested underlying storage.
|
||
|
*/
|
||
|
task = ide_task_new (self, cancellable, callback, user_data);
|
||
|
ide_task_set_task_data (task, task_data, g_free);
|
||
|
|
||
|
/*
|
||
|
* You can only call ide_template_base_expand_all_async() once, since we maintain
|
||
|
* a bunch of state inline.
|
||
|
*/
|
||
|
if (priv->has_expanded)
|
||
|
{
|
||
|
ide_task_return_new_error (task,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_PENDING,
|
||
|
"%s() has already been called.",
|
||
|
G_STRFUNC);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
priv->has_expanded = TRUE;
|
||
|
|
||
|
/*
|
||
|
* If we have nothing to do, we still need to preserve our "executed" state.
|
||
|
* So if there is nothing to do, short circuit now.
|
||
|
*/
|
||
|
if (priv->files->len == 0)
|
||
|
{
|
||
|
ide_task_return_boolean (task, TRUE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ide_template_base_mkdirs_async (self,
|
||
|
cancellable,
|
||
|
ide_template_base_expand_mkdirs_cb,
|
||
|
g_object_ref (task));
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
ide_template_base_expand_all_finish (IdeTemplateBase *self,
|
||
|
GAsyncResult *result,
|
||
|
GError **error)
|
||
|
{
|
||
|
g_return_val_if_fail (IDE_IS_TEMPLATE_BASE (self), FALSE);
|
||
|
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
|
||
|
|
||
|
return ide_task_propagate_boolean (IDE_TASK (result), error);
|
||
|
}
|
||
|
|
||
|
static TmplScope *
|
||
|
create_scope (IdeTemplateBase *self,
|
||
|
TmplScope *parent,
|
||
|
GFile *destination)
|
||
|
{
|
||
|
TmplScope *scope;
|
||
|
TmplSymbol *symbol;
|
||
|
g_autofree gchar *filename = NULL;
|
||
|
g_autofree gchar *year = NULL;
|
||
|
g_autoptr(GDateTime) now = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_assert (G_IS_FILE (destination));
|
||
|
|
||
|
scope = tmpl_scope_new_with_parent (parent);
|
||
|
|
||
|
symbol = tmpl_scope_get (scope, "filename");
|
||
|
filename = g_file_get_basename (destination);
|
||
|
tmpl_symbol_assign_string (symbol, filename);
|
||
|
|
||
|
now = g_date_time_new_now_local ();
|
||
|
year = g_date_time_format (now, "%Y");
|
||
|
symbol = tmpl_scope_get (scope, "year");
|
||
|
tmpl_symbol_assign_string (symbol, year);
|
||
|
|
||
|
return scope;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_template_base_add_resource (IdeTemplateBase *self,
|
||
|
const gchar *resource_path,
|
||
|
GFile *destination,
|
||
|
TmplScope *scope,
|
||
|
gint mode)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
FileExpansion expansion = { 0 };
|
||
|
g_autofree gchar *uri = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_return_if_fail (resource_path != NULL);
|
||
|
g_return_if_fail (G_IS_FILE (destination));
|
||
|
|
||
|
if (priv->has_expanded)
|
||
|
{
|
||
|
g_warning ("%s() called after ide_template_base_expand_all_async(). "
|
||
|
"Ignoring request to add resource.",
|
||
|
G_STRFUNC);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
uri = g_strdup_printf ("resource://%s", resource_path);
|
||
|
|
||
|
expansion.file = g_file_new_for_uri (uri);
|
||
|
expansion.stream = NULL;
|
||
|
expansion.scope = create_scope (self, scope, destination);
|
||
|
expansion.destination = g_object_ref (destination);
|
||
|
expansion.result = NULL;
|
||
|
expansion.mode = mode;
|
||
|
|
||
|
g_array_append_val (priv->files, expansion);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_template_base_add_path (IdeTemplateBase *self,
|
||
|
const gchar *path,
|
||
|
GFile *destination,
|
||
|
TmplScope *scope,
|
||
|
gint mode)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
FileExpansion expansion = { 0 };
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
g_return_if_fail (path != NULL);
|
||
|
g_return_if_fail (G_IS_FILE (destination));
|
||
|
|
||
|
if (priv->has_expanded)
|
||
|
{
|
||
|
g_warning ("%s() called after ide_template_base_expand_all_async(). "
|
||
|
"Ignoring request to add resource.",
|
||
|
G_STRFUNC);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
expansion.file = g_file_new_for_path (path);
|
||
|
expansion.stream = NULL;
|
||
|
expansion.scope = create_scope (self, scope, destination);
|
||
|
expansion.destination = g_object_ref (destination);
|
||
|
expansion.result = NULL;
|
||
|
expansion.mode = mode;
|
||
|
|
||
|
g_array_append_val (priv->files, expansion);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_template_base_reset (IdeTemplateBase *self)
|
||
|
{
|
||
|
IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
|
||
|
|
||
|
g_clear_pointer (&priv->files, g_array_unref);
|
||
|
priv->files = g_array_new (FALSE, TRUE, sizeof (FileExpansion));
|
||
|
|
||
|
priv->has_expanded = FALSE;
|
||
|
}
|