[gnome-builder: 40/139] libide-projects: add libide-projects static library
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder: 40/139] libide-projects: add libide-projects static library
- Date: Thu, 10 Jan 2019 04:20:41 +0000 (UTC)
commit 6c92f51477923f0bba54881e48e495c3a5efd708
Author: Christian Hergert <chergert redhat com>
Date: Wed Jan 9 16:26:30 2019 -0800
libide-projects: add libide-projects static library
This adds the libide-projects static library which contains modules
related to browsing, describing, and creating projects.
src/libide/doap/OVERVIEW.md | 13 -
src/libide/doap/meson.build | 25 -
src/libide/{doap => projects}/ide-doap-person.c | 2 +-
src/libide/{doap => projects}/ide-doap-person.h | 6 +-
src/libide/{doap => projects}/ide-doap.c | 15 +-
src/libide/{doap => projects}/ide-doap.h | 10 +-
src/libide/projects/ide-project-edit.c | 253 -------
src/libide/projects/ide-project-edit.h | 60 --
src/libide/projects/ide-project-file.c | 617 ++++++++++++++++++
src/libide/projects/ide-project-file.h | 103 +++
src/libide/projects/ide-project-info.c | 178 ++++-
src/libide/projects/ide-project-info.h | 94 +--
src/libide/projects/ide-project-item.c | 231 -------
src/libide/projects/ide-project-item.h | 52 --
src/libide/projects/ide-project-template.c | 188 ++++++
src/libide/projects/ide-project-template.h | 86 +++
src/libide/projects/ide-project-tree-addin.c | 2 +-
src/libide/projects/ide-project-tree-addin.h | 3 +-
src/libide/projects/ide-project.c | 334 ++--------
src/libide/projects/ide-project.h | 24 +-
src/libide/projects/ide-projects-global.c | 132 ++++
...roject-edit-private.h => ide-projects-global.h} | 19 +-
src/libide/projects/ide-recent-projects.c | 75 ++-
src/libide/projects/ide-recent-projects.h | 8 +-
src/libide/projects/ide-template-base.c | 724 +++++++++++++++++++++
src/libide/projects/ide-template-base.h | 71 ++
src/libide/projects/ide-template-provider.c | 61 ++
src/libide/projects/ide-template-provider.h | 48 ++
src/libide/projects/libide-projects.h | 40 ++
src/libide/projects/meson.build | 86 ++-
.../xml-reader.h => projects/xml-reader-private.h} | 2 +
src/libide/{doap => projects}/xml-reader.c | 4 +-
32 files changed, 2489 insertions(+), 1077 deletions(-)
---
diff --git a/src/libide/doap/ide-doap-person.c b/src/libide/projects/ide-doap-person.c
similarity index 99%
rename from src/libide/doap/ide-doap-person.c
rename to src/libide/projects/ide-doap-person.c
index 186c35e72..7f386458d 100644
--- a/src/libide/doap/ide-doap-person.c
+++ b/src/libide/projects/ide-doap-person.c
@@ -24,7 +24,7 @@
#include <glib/gi18n.h>
-#include "doap/ide-doap-person.h"
+#include "ide-doap-person.h"
struct _IdeDoapPerson
{
diff --git a/src/libide/doap/ide-doap-person.h b/src/libide/projects/ide-doap-person.h
similarity index 90%
rename from src/libide/doap/ide-doap-person.h
rename to src/libide/projects/ide-doap-person.h
index df2eec58d..2d77305ad 100644
--- a/src/libide/doap/ide-doap-person.h
+++ b/src/libide/projects/ide-doap-person.h
@@ -20,9 +20,11 @@
#pragma once
-#include <glib-object.h>
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <libide-core.h>
G_BEGIN_DECLS
diff --git a/src/libide/doap/ide-doap.c b/src/libide/projects/ide-doap.c
similarity index 98%
rename from src/libide/doap/ide-doap.c
rename to src/libide/projects/ide-doap.c
index bfa1b7845..542c80bc2 100644
--- a/src/libide/doap/ide-doap.c
+++ b/src/libide/projects/ide-doap.c
@@ -1,6 +1,6 @@
/* ide-doap.c
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-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
@@ -24,17 +24,14 @@
#include <glib/gi18n.h>
-#include "doap/ide-doap.h"
+#include "ide-doap.h"
+#include "xml-reader-private.h"
-#include "doap/xml-reader.h"
-
-/*
- * TODO: We don't do any XMLNS checking or anything here.
- */
+/* TODO: We don't do any XMLNS checking or anything here. */
struct _IdeDoap
{
- GObject parent_instance;
+ GObject parent_instance;
gchar *bug_database;
gchar *category;
@@ -249,7 +246,7 @@ ide_doap_set_shortdesc (IdeDoap *self,
*
*
*
- * Returns: (transfer none) (element-type Ide.DoapPerson): a #GList of #IdeDoapPerson.
+ * Returns: (transfer none) (element-type IdeDoapPerson): a #GList of #IdeDoapPerson.
*
* Since: 3.32
*/
diff --git a/src/libide/doap/ide-doap.h b/src/libide/projects/ide-doap.h
similarity index 90%
rename from src/libide/doap/ide-doap.h
rename to src/libide/projects/ide-doap.h
index 038e67bd2..2e65d96e0 100644
--- a/src/libide/doap/ide-doap.h
+++ b/src/libide/projects/ide-doap.h
@@ -1,6 +1,6 @@
/* ide-doap.h
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-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
@@ -20,11 +20,13 @@
#pragma once
-#include <gio/gio.h>
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <libide-core.h>
-#include "doap/ide-doap-person.h"
+#include "ide-doap-person.h"
G_BEGIN_DECLS
diff --git a/src/libide/projects/ide-project-file.c b/src/libide/projects/ide-project-file.c
new file mode 100644
index 000000000..ebe18fa10
--- /dev/null
+++ b/src/libide/projects/ide-project-file.c
@@ -0,0 +1,617 @@
+/* ide-project-file.c
+ *
+ * Copyright 2018-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-project-file"
+
+#include "config.h"
+
+#include <libide-threading.h>
+#include <string.h>
+
+#include "ide-project.h"
+#include "ide-project-file.h"
+
+typedef struct
+{
+ GFile *directory;
+ GFileInfo *info;
+ guint checked_for_icon_override : 1;
+} IdeProjectFilePrivate;
+
+enum {
+ PROP_0,
+ PROP_DIRECTORY,
+ PROP_FILE,
+ PROP_INFO,
+ N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeProjectFile, ide_project_file, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static gchar *
+ide_project_file_repr (IdeObject *object)
+{
+ IdeProjectFile *self = (IdeProjectFile *)object;
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_assert (IDE_IS_PROJECT_FILE (self));
+
+ if (priv->info && priv->directory)
+ return g_strdup_printf ("%s name=\"%s\" directory=\"%s\"",
+ G_OBJECT_TYPE_NAME (self),
+ g_file_info_get_display_name (priv->info),
+ g_file_peek_path (priv->directory));
+ else
+ return IDE_OBJECT_CLASS (ide_project_file_parent_class)->repr (object);
+}
+
+static void
+ide_project_file_dispose (GObject *object)
+{
+ IdeProjectFile *self = (IdeProjectFile *)object;
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_clear_object (&priv->directory);
+ g_clear_object (&priv->info);
+
+ G_OBJECT_CLASS (ide_project_file_parent_class)->dispose (object);
+}
+
+static void
+ide_project_file_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeProjectFile *self = IDE_PROJECT_FILE (object);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ g_value_set_object (value, ide_project_file_get_directory (self));
+ break;
+
+ case PROP_FILE:
+ g_value_take_object (value, ide_project_file_ref_file (self));
+ break;
+
+ case PROP_INFO:
+ g_value_set_object (value, ide_project_file_get_info (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_project_file_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeProjectFile *self = IDE_PROJECT_FILE (object);
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ priv->directory = g_value_dup_object (value);
+ break;
+
+ case PROP_INFO:
+ priv->info = g_value_dup_object (value);
+ if (priv->info &&
+ g_file_info_has_attribute (priv->info, G_FILE_ATTRIBUTE_STANDARD_NAME))
+ break;
+ /* Fall-through */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_project_file_class_init (IdeProjectFileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_project_file_dispose;
+ object_class->get_property = ide_project_file_get_property;
+ object_class->set_property = ide_project_file_set_property;
+
+ i_object_class->repr = ide_project_file_repr;
+
+ properties [PROP_DIRECTORY] =
+ g_param_spec_object ("directory",
+ "Directory",
+ "The directory containing the file",
+ G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_INFO] =
+ g_param_spec_object ("info",
+ "Info",
+ "The file info the file",
+ G_TYPE_FILE_INFO,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "The file",
+ G_TYPE_FILE,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_project_file_init (IdeProjectFile *self)
+{
+}
+
+/**
+ * ide_project_file_get_directory:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the project file.
+ *
+ * Returns: (transfer none): an #IdeProjectFile
+ *
+ * Since: 3.32
+ */
+GFile *
+ide_project_file_get_directory (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ return priv->directory;
+}
+
+/**
+ * ide_project_file_ref_file:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the file for the #IdeProjectFile.
+ *
+ * Returns: (transfer full): a #GFile
+ *
+ * Since: 3.32
+ */
+GFile *
+ide_project_file_ref_file (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ return g_file_get_child (priv->directory, g_file_info_get_name (priv->info));
+}
+
+/**
+ * ide_project_file_get_info:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the #GFileInfo for the file. This combined with
+ * #IdeProjectFile:directory can be used to determine the underlying
+ * file, such as via #IdeProjectFile:file.
+ *
+ * Returns: (transfer none): a #GFileInfo
+ *
+ * Since: 3.32
+ */
+GFileInfo *
+ide_project_file_get_info (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ return priv->info;
+}
+
+/**
+ * ide_project_file_get_name:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the name for the file, which matches the encoding on disk.
+ *
+ * Returns: a string containing the name
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_project_file_get_name (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ return g_file_info_get_name (priv->info);
+}
+
+/**
+ * ide_project_file_get_display_name:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the display-name for the file, which should be shown to users.
+ *
+ * Returns: a string containing the display name
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_project_file_get_display_name (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ return g_file_info_get_display_name (priv->info);
+}
+
+/**
+ * ide_project_file_is_directory:
+ * @self: a #IdeProjectFile
+ *
+ * Checks if @self represents a directory. If ide_project_file_is_symlink() is
+ * %TRUE, this may still return %TRUE.
+ *
+ * Returns: %TRUE if @self is a directory, or symlink to a directory
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_project_file_is_directory (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), FALSE);
+
+ return g_file_info_get_file_type (priv->info) == G_FILE_TYPE_DIRECTORY;
+}
+
+
+/**
+ * ide_project_file_is_symlink:
+ * @self: a #IdeProjectFile
+ *
+ * Checks if @self represents a symlink.
+ *
+ * Returns: %TRUE if @self is a symlink
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_project_file_is_symlink (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), FALSE);
+
+ return g_file_info_get_is_symlink (priv->info);
+}
+
+gint
+ide_project_file_compare (IdeProjectFile *a,
+ IdeProjectFile *b)
+{
+ GFileInfo *info_a = ide_project_file_get_info (a);
+ GFileInfo *info_b = ide_project_file_get_info (b);
+ const gchar *display_name_a = g_file_info_get_display_name (info_a);
+ const gchar *display_name_b = g_file_info_get_display_name (info_b);
+ gchar *casefold_a = NULL;
+ gchar *casefold_b = NULL;
+ gboolean ret;
+
+ casefold_a = g_utf8_collate_key_for_filename (display_name_a, -1);
+ casefold_b = g_utf8_collate_key_for_filename (display_name_b, -1);
+
+ ret = strcmp (casefold_a, casefold_b);
+
+ g_free (casefold_a);
+ g_free (casefold_b);
+
+ return ret;
+}
+
+gint
+ide_project_file_compare_directories_first (IdeProjectFile *a,
+ IdeProjectFile *b)
+{
+ GFileInfo *info_a = ide_project_file_get_info (a);
+ GFileInfo *info_b = ide_project_file_get_info (b);
+ GFileType file_type_a = g_file_info_get_file_type (info_a);
+ GFileType file_type_b = g_file_info_get_file_type (info_b);
+ gint dir_a = (file_type_a == G_FILE_TYPE_DIRECTORY);
+ gint dir_b = (file_type_b == G_FILE_TYPE_DIRECTORY);
+ gint ret;
+
+ ret = dir_b - dir_a;
+ if (ret == 0)
+ ret = ide_project_file_compare (a, b);
+
+ return ret;
+}
+
+/**
+ * ide_project_file_get_symbolic_icon:
+ * @self: a #IdeProjectFile
+ *
+ * Gets the symbolic icon to represent the file.
+ *
+ * Returns: (transfer none) (nullable): a #GIcon or %NULL
+ *
+ * Since: 3.32
+ */
+GIcon *
+ide_project_file_get_symbolic_icon (IdeProjectFile *self)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+
+ /*
+ * We might want to override the symbolic icon based on an override
+ * icon we ship with Builder.
+ */
+ if (!priv->checked_for_icon_override)
+ {
+ const gchar *content_type;
+
+ priv->checked_for_icon_override = TRUE;
+
+ if ((content_type = g_file_info_get_content_type (priv->info)))
+ {
+ g_autoptr(GIcon) override = NULL;
+
+ if ((override = ide_g_content_type_get_symbolic_icon (content_type)))
+ g_file_info_set_symbolic_icon (priv->info, override);
+ }
+ }
+
+ return g_file_info_get_symbolic_icon (priv->info);
+}
+
+static void
+ide_project_file_list_children_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *parent = (GFile *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GPtrArray) files = NULL;
+ g_autoptr(GPtrArray) ret = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_FILE (parent));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!(files = ide_g_file_get_children_finish (parent, result, &error)))
+ {
+ ide_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ IDE_PTR_ARRAY_SET_FREE_FUNC (files, g_object_unref);
+
+ ret = g_ptr_array_new_full (files->len, g_object_unref);
+
+ for (guint i = 0; i < files->len; i++)
+ {
+ GFileInfo *info = g_ptr_array_index (files, i);
+ IdeProjectFile *project_file;
+
+ project_file = g_object_new (IDE_TYPE_PROJECT_FILE,
+ "info", info,
+ "directory", parent,
+ NULL);
+ g_ptr_array_add (ret, g_steal_pointer (&project_file));
+ }
+
+ ide_task_return_pointer (task,
+ g_steal_pointer (&ret),
+ (GDestroyNotify)g_ptr_array_unref);
+}
+
+/**
+ * ide_project_file_list_children_async:
+ * @self: a #IdeProjectFile
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: callback to execute upon completion
+ * @user_data: user data for @callback
+ *
+ * List the children of @self.
+ *
+ * Call ide_project_file_list_children_finish() to get the result
+ * of this operation.
+ *
+ * Since: 3.32
+ */
+void
+ide_project_file_list_children_async (IdeProjectFile *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ g_assert (IDE_IS_PROJECT_FILE (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_project_file_list_children_async);
+
+ file = ide_project_file_ref_file (self);
+
+ ide_g_file_get_children_async (file,
+ IDE_PROJECT_FILE_ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ ide_project_file_list_children_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * ide_project_file_list_children_finish:
+ * @self: a #IdeProjectFile
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous request to
+ * ide_project_file_list_children_async().
+ *
+ * Returns: (transfer full) (element-type IdeProjectFile): a #GPtrArray
+ * of #IdeProjectFile
+ *
+ * Since: 3.32
+ */
+GPtrArray *
+ide_project_file_list_children_finish (IdeProjectFile *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GPtrArray *ret;
+
+ g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+ g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+ ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+ return IDE_PTR_ARRAY_STEAL_FULL (&ret);
+}
+
+static void
+ide_project_file_trash_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeProject *project = (IdeProject *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_PROJECT (project));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!ide_project_trash_file_finish (project, result, &error))
+ ide_task_return_error (task, g_steal_pointer (&error));
+ else
+ ide_task_return_boolean (task, TRUE);
+}
+
+void
+ide_project_file_trash_async (IdeProjectFile *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(IdeContext) context = NULL;
+ g_autoptr(IdeProject) project = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ g_return_if_fail (IDE_IS_PROJECT_FILE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, ide_project_file_trash_async);
+
+ context = ide_object_ref_context (IDE_OBJECT (self));
+ project = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_PROJECT);
+ file = ide_project_file_ref_file (self);
+
+ ide_project_trash_file_async (project,
+ file,
+ cancellable,
+ ide_project_file_trash_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+ide_project_file_trash_finish (IdeProjectFile *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+/**
+ * ide_project_file_create_child:
+ * @self: a #IdeProjectFile
+ * @info: a #GFileInfo
+ *
+ * Creates a new child project file of @self.
+ *
+ * Returns: (transfer full): an #IdeProjectFile
+ *
+ * Since: 3.32
+ */
+IdeProjectFile *
+ide_project_file_create_child (IdeProjectFile *self,
+ GFileInfo *info)
+{
+ IdeProjectFilePrivate *priv = ide_project_file_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_PROJECT_FILE (self), NULL);
+ g_return_val_if_fail (G_IS_FILE_INFO (info), NULL);
+
+ return g_object_new (IDE_TYPE_PROJECT_FILE,
+ "directory", priv->directory,
+ "info", info,
+ NULL);
+}
+
+/**
+ * ide_project_file_new:
+ * @directory: a #GFile
+ * @info: a #GFileInfo
+ *
+ * Creates a new project file for a child of @directory.
+ *
+ * Returns: (transfer full): an #IdeProjectFile
+ *
+ * Since: 3.32
+ */
+IdeProjectFile *
+ide_project_file_new (GFile *directory,
+ GFileInfo *info)
+{
+ g_return_val_if_fail (G_IS_FILE (directory), NULL);
+ g_return_val_if_fail (G_IS_FILE_INFO (info), NULL);
+
+ return g_object_new (IDE_TYPE_PROJECT_FILE,
+ "directory", directory,
+ "info", info,
+ NULL);
+}
diff --git a/src/libide/projects/ide-project-file.h b/src/libide/projects/ide-project-file.h
new file mode 100644
index 000000000..36023e831
--- /dev/null
+++ b/src/libide/projects/ide-project-file.h
@@ -0,0 +1,103 @@
+/* ide-project-file.h
+ *
+ * Copyright 2018-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
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <libide-code.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PROJECT_FILE (ide_project_file_get_type())
+
+
+#define IDE_PROJECT_FILE_ATTRIBUTES \
+ G_FILE_ATTRIBUTE_STANDARD_NAME"," \
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," \
+ G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE"," \
+ G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON"," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE"," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK"," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ"," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME"," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeProjectFile, ide_project_file, IDE, PROJECT_FILE, IdeObject)
+
+struct _IdeProjectFileClass
+{
+ IdeObjectClass parent_class;
+
+ /*< private >*/
+ gpointer _reserved[8];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeProjectFile *ide_project_file_new (GFile *directory,
+ GFileInfo *info);
+IDE_AVAILABLE_IN_3_32
+GFile *ide_project_file_get_directory (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+GFileInfo *ide_project_file_get_info (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+GFile *ide_project_file_ref_file (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_project_file_get_display_name (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_project_file_get_name (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_project_file_is_directory (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_project_file_is_symlink (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+gint ide_project_file_compare_directories_first (IdeProjectFile *a,
+ IdeProjectFile *b);
+IDE_AVAILABLE_IN_3_32
+gint ide_project_file_compare (IdeProjectFile *a,
+ IdeProjectFile *b);
+IDE_AVAILABLE_IN_3_32
+GIcon *ide_project_file_get_symbolic_icon (IdeProjectFile *self);
+IDE_AVAILABLE_IN_3_32
+IdeProjectFile *ide_project_file_create_child (IdeProjectFile *self,
+ GFileInfo *info);
+IDE_AVAILABLE_IN_3_32
+void ide_project_file_list_children_async (IdeProjectFile *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_32
+GPtrArray *ide_project_file_list_children_finish (IdeProjectFile *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_3_32
+void ide_project_file_trash_async (IdeProjectFile *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_project_file_trash_finish (IdeProjectFile *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/projects/ide-project-info.c b/src/libide/projects/ide-project-info.c
index 7c1e05844..290e2b64b 100644
--- a/src/libide/projects/ide-project-info.c
+++ b/src/libide/projects/ide-project-info.c
@@ -26,11 +26,10 @@
#include "config.h"
-#include <dazzle.h>
#include <glib/gi18n.h>
#include <string.h>
-#include "projects/ide-project-info.h"
+#include "ide-project-info.h"
/**
* SECTION:ideprojectinfo:
@@ -47,6 +46,7 @@ struct _IdeProjectInfo
{
GObject parent_instance;
+ gchar *id;
IdeDoap *doap;
GDateTime *last_modified_at;
GFile *directory;
@@ -55,7 +55,7 @@ struct _IdeProjectInfo
gchar *name;
gchar *description;
gchar **languages;
- IdeVcsUri *vcs_uri;
+ gchar *vcs_uri;
gint priority;
@@ -71,6 +71,7 @@ enum {
PROP_DIRECTORY,
PROP_DOAP,
PROP_FILE,
+ PROP_ID,
PROP_IS_RECENT,
PROP_LANGUAGES,
PROP_LAST_MODIFIED_AT,
@@ -224,7 +225,7 @@ ide_project_info_set_build_system_name (IdeProjectInfo *self,
{
g_return_if_fail (IDE_IS_PROJECT_INFO (self));
- if (!dzl_str_equal0 (self->build_system_name, build_system_name))
+ if (!ide_str_equal0 (self->build_system_name, build_system_name))
{
g_free (self->build_system_name);
self->build_system_name = g_strdup (build_system_name);
@@ -246,7 +247,7 @@ ide_project_info_set_description (IdeProjectInfo *self,
{
g_return_if_fail (IDE_IS_PROJECT_INFO (self));
- if (!dzl_str_equal0 (self->description, description))
+ if (!ide_str_equal0 (self->description, description))
{
g_free (self->description);
self->description = g_strdup (description);
@@ -268,7 +269,7 @@ ide_project_info_set_name (IdeProjectInfo *self,
{
g_return_if_fail (IDE_IS_PROJECT_INFO (self));
- if (!dzl_str_equal0 (self->name, name))
+ if (!ide_str_equal0 (self->name, name))
{
g_free (self->name);
self->name = g_strdup (name);
@@ -340,6 +341,7 @@ ide_project_info_finalize (GObject *object)
{
IdeProjectInfo *self = (IdeProjectInfo *)object;
+ g_clear_pointer (&self->id, g_free);
g_clear_pointer (&self->last_modified_at, g_date_time_unref);
g_clear_pointer (&self->build_system_name, g_free);
g_clear_pointer (&self->description, g_free);
@@ -381,6 +383,10 @@ ide_project_info_get_property (GObject *object,
g_value_set_object (value, ide_project_info_get_file (self));
break;
+ case PROP_ID:
+ g_value_set_string (value, ide_project_info_get_id (self));
+ break;
+
case PROP_IS_RECENT:
g_value_set_boolean (value, ide_project_info_get_is_recent (self));
break;
@@ -402,7 +408,7 @@ ide_project_info_get_property (GObject *object,
break;
case PROP_VCS_URI:
- g_value_set_boxed (value, ide_project_info_get_vcs_uri (self));
+ g_value_set_string (value, ide_project_info_get_vcs_uri (self));
break;
default:
@@ -440,6 +446,10 @@ ide_project_info_set_property (GObject *object,
ide_project_info_set_file (self, g_value_get_object (value));
break;
+ case PROP_ID:
+ ide_project_info_set_id (self, g_value_get_string (value));
+ break;
+
case PROP_IS_RECENT:
ide_project_info_set_is_recent (self, g_value_get_boolean (value));
break;
@@ -461,7 +471,7 @@ ide_project_info_set_property (GObject *object,
break;
case PROP_VCS_URI:
- ide_project_info_set_vcs_uri (self, g_value_get_boxed (value));
+ ide_project_info_set_vcs_uri (self, g_value_get_string (value));
break;
default:
@@ -483,63 +493,70 @@ ide_project_info_class_init (IdeProjectInfoClass *klass)
"Build System name",
"Build System name",
NULL,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_DESCRIPTION] =
g_param_spec_string ("description",
"Description",
"The project description.",
NULL,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ID] =
+ g_param_spec_string ("id",
+ "Id",
+ "The identifier for the project",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_NAME] =
g_param_spec_string ("name",
"Name",
"The project name.",
NULL,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_DIRECTORY] =
g_param_spec_object ("directory",
"Directory",
"The project directory.",
G_TYPE_FILE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_DOAP] =
g_param_spec_object ("doap",
"DOAP",
"A DOAP describing the project.",
IDE_TYPE_DOAP,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_FILE] =
g_param_spec_object ("file",
"File",
"The toplevel project file.",
G_TYPE_FILE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_IS_RECENT] =
g_param_spec_boolean ("is-recent",
"Is Recent",
"Is Recent",
FALSE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_LANGUAGES] =
g_param_spec_boxed ("languages",
"Languages",
"Languages",
G_TYPE_STRV,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_LAST_MODIFIED_AT] =
g_param_spec_boxed ("last-modified-at",
"Last Modified At",
"Last Modified At",
G_TYPE_DATE_TIME,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_PRIORITY] =
g_param_spec_int ("priority",
@@ -548,14 +565,14 @@ ide_project_info_class_init (IdeProjectInfoClass *klass)
G_MININT,
G_MAXINT,
0,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_VCS_URI] =
- g_param_spec_boxed ("vcs-uri",
- "Vcs Uri",
- "The vcs uri of the project, in case it is not local",
- IDE_TYPE_VCS_URI,
- (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+ g_param_spec_string ("vcs-uri",
+ "Vcs Uri",
+ "The VCS URI of the project, in case it is not local",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
@@ -580,6 +597,9 @@ ide_project_info_compare (IdeProjectInfo *info1,
g_assert (IDE_IS_PROJECT_INFO (info1));
g_assert (IDE_IS_PROJECT_INFO (info2));
+ if (info1 == info2)
+ return 0;
+
prio1 = ide_project_info_get_priority (info1);
prio2 = ide_project_info_get_priority (info2);
@@ -609,7 +629,7 @@ ide_project_info_compare (IdeProjectInfo *info1,
* ide_project_info_get_vcs_uri:
* @self: an #IdeProjectInfo
*
- * Gets the #IdeVcsUri for the project info. This should be set with the
+ * Gets the VCS URI for the project info. This should be set with the
* remote URI for the version control system. It can be used to clone the
* project when activated from the greeter.
*
@@ -617,7 +637,7 @@ ide_project_info_compare (IdeProjectInfo *info1,
*
* Since: 3.32
*/
-IdeVcsUri *
+const gchar *
ide_project_info_get_vcs_uri (IdeProjectInfo *self)
{
g_return_val_if_fail (IDE_IS_PROJECT_INFO (self), NULL);
@@ -627,14 +647,114 @@ ide_project_info_get_vcs_uri (IdeProjectInfo *self)
void
ide_project_info_set_vcs_uri (IdeProjectInfo *self,
- IdeVcsUri *vcs_uri)
+ const gchar *vcs_uri)
{
g_return_if_fail (IDE_IS_PROJECT_INFO (self));
- if (self->vcs_uri != vcs_uri)
+ if (!ide_str_equal0 (self->vcs_uri, vcs_uri))
{
- g_clear_pointer (&self->vcs_uri, ide_vcs_uri_unref);
- self->vcs_uri = ide_vcs_uri_ref (vcs_uri);
+ g_free (self->vcs_uri);
+ self->vcs_uri = g_strdup (vcs_uri);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VCS_URI]);
}
}
+
+IdeProjectInfo *
+ide_project_info_new (void)
+{
+ return g_object_new (IDE_TYPE_PROJECT_INFO, NULL);
+}
+
+/**
+ * ide_project_info_equal:
+ * @self: a #IdeProjectInfo
+ * @other: a #IdeProjectInfo
+ *
+ * This function will check to see if information about @self and @other are
+ * similar enough that a request to open @other would instead activate
+ * @self. This is useful when a user tries to open the same project twice.
+ *
+ * However, some case is taken to ensure that things like the build system
+ * are the same so that a project may be opened twice with two build systems
+ * as is sometimes necessary when projects are porting to a new build
+ * system.
+ *
+ * Returns: %TRUE if @self and @other are the same project and similar
+ * enough to be considered equal.
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_project_info_equal (IdeProjectInfo *self,
+ IdeProjectInfo *other)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_INFO (self), FALSE);
+ g_return_val_if_fail (IDE_IS_PROJECT_INFO (other), FALSE);
+
+ if (!self->file || !other->file ||
+ !g_file_equal (self->file, other->file))
+ {
+ if (!self->directory || !other->directory ||
+ !g_file_equal (self->directory, other->directory))
+ return FALSE;
+ }
+
+ /* build-system only set in one of the project-info?
+ * That's fine, we'll consider them the same to avoid over
+ * activating a second workbench
+ */
+ if ((!self->build_system_name && other->build_system_name) ||
+ (self->build_system_name && !other->build_system_name))
+ return TRUE;
+
+ return ide_str_equal0 (self->build_system_name, other->build_system_name);
+}
+
+const gchar *
+ide_project_info_get_id (IdeProjectInfo *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_INFO (self), NULL);
+
+ if (!self->id && self->directory)
+ self->id = g_file_get_basename (self->directory);
+
+ if (!self->id && self->file)
+ {
+ g_autoptr(GFile) parent = g_file_get_parent (self->file);
+ self->id = g_file_get_basename (parent);
+ }
+
+ if (!self->id && self->doap)
+ self->id = g_strdup (ide_doap_get_name (self->doap));
+
+ if (!self->id && self->vcs_uri)
+ {
+ const gchar *path = self->vcs_uri;
+
+ if (strstr (path, "//"))
+ path = strstr (path, "//") + 1;
+
+ if (strchr (path, '/'))
+ path = strchr (path, '/');
+ else if (strrchr (path, ':'))
+ path = strrchr (path, ':');
+
+ self->id = g_path_get_basename (path);
+ }
+
+ return self->id;
+}
+
+void
+ide_project_info_set_id (IdeProjectInfo *self,
+ const gchar *id)
+{
+ g_return_if_fail (IDE_IS_PROJECT_INFO (self));
+
+ if (!ide_str_equal0 (id, self->id))
+ {
+ g_free (self->id);
+ self->id = g_strdup (id);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+ }
+}
diff --git a/src/libide/projects/ide-project-info.h b/src/libide/projects/ide-project-info.h
index 799e51361..b47307b19 100644
--- a/src/libide/projects/ide-project-info.h
+++ b/src/libide/projects/ide-project-info.h
@@ -1,6 +1,6 @@
/* ide-project-info.h
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-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
@@ -20,12 +20,14 @@
#pragma once
-#include <gio/gio.h>
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <gio/gio.h>
+#include <libide-core.h>
-#include "doap/ide-doap.h"
-#include "vcs/ide-vcs-uri.h"
+#include "ide-doap.h"
G_BEGIN_DECLS
@@ -35,63 +37,73 @@ IDE_AVAILABLE_IN_3_32
G_DECLARE_FINAL_TYPE (IdeProjectInfo, ide_project_info, IDE, PROJECT_INFO, GObject)
IDE_AVAILABLE_IN_3_32
-gint ide_project_info_compare (IdeProjectInfo *info1,
- IdeProjectInfo *info2);
+IdeProjectInfo *ide_project_info_new (void);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_project_info_get_id (IdeProjectInfo *self);
+IDE_AVAILABLE_IN_3_32
+void ide_project_info_set_id (IdeProjectInfo *self,
+ const gchar *id);
IDE_AVAILABLE_IN_3_32
-GFile *ide_project_info_get_file (IdeProjectInfo *self);
+gint ide_project_info_compare (IdeProjectInfo *info1,
+ IdeProjectInfo *info2);
IDE_AVAILABLE_IN_3_32
-IdeDoap *ide_project_info_get_doap (IdeProjectInfo *self);
+gboolean ide_project_info_equal (IdeProjectInfo *self,
+ IdeProjectInfo *other);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_doap (IdeProjectInfo *self,
- IdeDoap *doap);
+GFile *ide_project_info_get_file (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-const gchar *ide_project_info_get_build_system_name (IdeProjectInfo *self);
+IdeDoap *ide_project_info_get_doap (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-const gchar *ide_project_info_get_description (IdeProjectInfo *self);
+void ide_project_info_set_doap (IdeProjectInfo *self,
+ IdeDoap *doap);
IDE_AVAILABLE_IN_3_32
-GFile *ide_project_info_get_directory (IdeProjectInfo *self);
+const gchar *ide_project_info_get_build_system_name (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-gboolean ide_project_info_get_is_recent (IdeProjectInfo *self);
+const gchar *ide_project_info_get_description (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-gint ide_project_info_get_priority (IdeProjectInfo *self);
+GFile *ide_project_info_get_directory (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-GDateTime *ide_project_info_get_last_modified_at (IdeProjectInfo *self);
+gboolean ide_project_info_get_is_recent (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_last_modified_at (IdeProjectInfo *self,
- GDateTime *modified_at);
+gint ide_project_info_get_priority (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-const gchar * const *
- ide_project_info_get_languages (IdeProjectInfo *self);
+GDateTime *ide_project_info_get_last_modified_at (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-const gchar *ide_project_info_get_name (IdeProjectInfo *self);
+void ide_project_info_set_last_modified_at (IdeProjectInfo *self,
+ GDateTime *modified_at);
IDE_AVAILABLE_IN_3_32
-IdeVcsUri *ide_project_info_get_vcs_uri (IdeProjectInfo *self);
+const gchar * const *ide_project_info_get_languages (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_file (IdeProjectInfo *self,
- GFile *file);
+const gchar *ide_project_info_get_name (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_build_system_name (IdeProjectInfo *self,
- const gchar *build_system_name);
+const gchar *ide_project_info_get_vcs_uri (IdeProjectInfo *self);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_description (IdeProjectInfo *self,
- const gchar *description);
+void ide_project_info_set_file (IdeProjectInfo *self,
+ GFile *file);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_directory (IdeProjectInfo *self,
- GFile *directory);
+void ide_project_info_set_build_system_name (IdeProjectInfo *self,
+ const gchar *build_system_name);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_is_recent (IdeProjectInfo *self,
- gboolean is_recent);
+void ide_project_info_set_description (IdeProjectInfo *self,
+ const gchar *description);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_languages (IdeProjectInfo *self,
- gchar **languages);
+void ide_project_info_set_directory (IdeProjectInfo *self,
+ GFile *directory);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_name (IdeProjectInfo *self,
- const gchar *name);
+void ide_project_info_set_is_recent (IdeProjectInfo *self,
+ gboolean is_recent);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_priority (IdeProjectInfo *self,
- gint priority);
+void ide_project_info_set_languages (IdeProjectInfo *self,
+ gchar **languages);
IDE_AVAILABLE_IN_3_32
-void ide_project_info_set_vcs_uri (IdeProjectInfo *self,
- IdeVcsUri *uri);
+void ide_project_info_set_name (IdeProjectInfo *self,
+ const gchar *name);
+IDE_AVAILABLE_IN_3_32
+void ide_project_info_set_priority (IdeProjectInfo *self,
+ gint priority);
+IDE_AVAILABLE_IN_3_32
+void ide_project_info_set_vcs_uri (IdeProjectInfo *self,
+ const gchar *vcs_uri);
+
G_END_DECLS
diff --git a/src/libide/projects/ide-project-template.c b/src/libide/projects/ide-project-template.c
new file mode 100644
index 000000000..ac95b5e8c
--- /dev/null
+++ b/src/libide/projects/ide-project-template.c
@@ -0,0 +1,188 @@
+/* ide-project-template.c
+ *
+ * Copyright 2015-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-project-template"
+
+#include "config.h"
+
+#include "ide-project-template.h"
+
+G_DEFINE_INTERFACE (IdeProjectTemplate, ide_project_template, G_TYPE_OBJECT)
+
+static void
+ide_project_template_default_init (IdeProjectTemplateInterface *iface)
+{
+}
+
+gchar *
+ide_project_template_get_id (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_id (self);
+}
+
+gchar *
+ide_project_template_get_name (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_name (self);
+}
+
+gchar *
+ide_project_template_get_description (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_description (self);
+}
+
+/**
+ * ide_project_template_get_widget:
+ * @self: An #IdeProjectTemplate
+ *
+ * Get's the configuration widget for the template if there is one.
+ *
+ * Returns: (transfer none): a #GtkWidget.
+ *
+ * Since: 3.32
+ */
+GtkWidget *
+ide_project_template_get_widget (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_widget (self);
+}
+
+/**
+ * ide_project_template_get_languages:
+ * @self: an #IdeProjectTemplate
+ *
+ * Gets the list of languages that this template can support when generating
+ * the project.
+ *
+ * Returns: (transfer full): A newly allocated, NULL terminated list of
+ * supported languages.
+ *
+ * Since: 3.32
+ */
+gchar **
+ide_project_template_get_languages (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_languages (self);
+}
+
+gchar *
+ide_project_template_get_icon_name (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), NULL);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_icon_name (self);
+}
+
+/**
+ * ide_project_template_expand_async:
+ * @self: an #IdeProjectTemplate
+ * @params: (element-type utf8 GLib.Variant): A hashtable of template parameters.
+ * @cancellable: (nullable): a #GCancellable or %NULL.
+ * @callback: the callback for the asynchronous operation.
+ * @user_data: user data for @callback.
+ *
+ * Asynchronously requests expansion of the template.
+ *
+ * This may involve creating files and directories on disk as well as
+ * expanding files based on the contents of @params.
+ *
+ * It is expected that this method is only called once on an #IdeProjectTemplate.
+ *
+ * Since: 3.32
+ */
+void
+ide_project_template_expand_async (IdeProjectTemplate *self,
+ GHashTable *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_PROJECT_TEMPLATE (self));
+ g_return_if_fail (params != NULL);
+ g_return_if_fail (g_hash_table_contains (params, "name"));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_async (self, params, cancellable, callback, user_data);
+}
+
+gboolean
+ide_project_template_expand_finish (IdeProjectTemplate *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->expand_finish (self, result, error);
+}
+
+/**
+ * ide_project_template_get_priority:
+ * @self: a #IdeProjectTemplate
+ *
+ * Gets the priority of the template. This can be used to sort the templates
+ * in the "new project" view.
+ *
+ * Returns: the priority of the template
+ *
+ * Since: 3.32
+ */
+gint
+ide_project_template_get_priority (IdeProjectTemplate *self)
+{
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (self), 0);
+
+ if (IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_priority)
+ return IDE_PROJECT_TEMPLATE_GET_IFACE (self)->get_priority (self);
+
+ return 0;
+}
+
+gint
+ide_project_template_compare (IdeProjectTemplate *a,
+ IdeProjectTemplate *b)
+{
+ gint ret;
+
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (a), 0);
+ g_return_val_if_fail (IDE_IS_PROJECT_TEMPLATE (b), 0);
+
+ ret = ide_project_template_get_priority (a) - ide_project_template_get_priority (b);
+
+ if (ret == 0)
+ {
+ g_autofree gchar *a_name = ide_project_template_get_name (a);
+ g_autofree gchar *b_name = ide_project_template_get_name (b);
+ ret = g_utf8_collate (a_name, b_name);
+ }
+
+ return ret;
+}
diff --git a/src/libide/projects/ide-project-template.h b/src/libide/projects/ide-project-template.h
new file mode 100644
index 000000000..91a342dcc
--- /dev/null
+++ b/src/libide/projects/ide-project-template.h
@@ -0,0 +1,86 @@
+/* ide-project-template.h
+ *
+ * Copyright 2015-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
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PROJECT_TEMPLATE (ide_project_template_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_INTERFACE (IdeProjectTemplate, ide_project_template, IDE, PROJECT_TEMPLATE, GObject)
+
+struct _IdeProjectTemplateInterface
+{
+ GTypeInterface parent;
+
+ gchar *(*get_id) (IdeProjectTemplate *self);
+ gchar *(*get_name) (IdeProjectTemplate *self);
+ gchar *(*get_description) (IdeProjectTemplate *self);
+ GtkWidget *(*get_widget) (IdeProjectTemplate *self);
+ gchar **(*get_languages) (IdeProjectTemplate *self);
+ gchar *(*get_icon_name) (IdeProjectTemplate *self);
+ void (*expand_async) (IdeProjectTemplate *self,
+ GHashTable *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*expand_finish) (IdeProjectTemplate *self,
+ GAsyncResult *result,
+ GError **error);
+ gint (*get_priority) (IdeProjectTemplate *self);
+};
+
+IDE_AVAILABLE_IN_3_32
+gchar *ide_project_template_get_id (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+gint ide_project_template_get_priority (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+gchar *ide_project_template_get_name (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+gchar *ide_project_template_get_description (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+GtkWidget *ide_project_template_get_widget (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+gchar **ide_project_template_get_languages (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+gchar *ide_project_template_get_icon_name (IdeProjectTemplate *self);
+IDE_AVAILABLE_IN_3_32
+void ide_project_template_expand_async (IdeProjectTemplate *self,
+ GHashTable *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_project_template_expand_finish (IdeProjectTemplate *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_3_32
+gint ide_project_template_compare (IdeProjectTemplate *a,
+ IdeProjectTemplate *b);
+
+G_END_DECLS
diff --git a/src/libide/projects/ide-project-tree-addin.c b/src/libide/projects/ide-project-tree-addin.c
index 6d6730277..7289dd94c 100644
--- a/src/libide/projects/ide-project-tree-addin.c
+++ b/src/libide/projects/ide-project-tree-addin.c
@@ -22,7 +22,7 @@
#include "config.h"
-#include "projects/ide-project-tree-addin.h"
+#include "ide-project-tree-addin.h"
/**
* SECTION:ide-project-tree-addin
diff --git a/src/libide/projects/ide-project-tree-addin.h b/src/libide/projects/ide-project-tree-addin.h
index 7b6a81ca5..33412fb99 100644
--- a/src/libide/projects/ide-project-tree-addin.h
+++ b/src/libide/projects/ide-project-tree-addin.h
@@ -21,8 +21,7 @@
#pragma once
#include <dazzle.h>
-
-#include "ide-version-macros.h"
+#include <libide-core.h>
G_BEGIN_DECLS
diff --git a/src/libide/projects/ide-project.c b/src/libide/projects/ide-project.c
index 1a2b28dfc..7b7b2ebfb 100644
--- a/src/libide/projects/ide-project.c
+++ b/src/libide/projects/ide-project.c
@@ -1,6 +1,6 @@
/* ide-project.c
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -23,30 +23,15 @@
#include "config.h"
#include <glib/gi18n.h>
+#include <libide-code.h>
-#include "ide-context.h"
-#include "ide-debug.h"
-
-#include "application/ide-application.h"
-#include "buffers/ide-buffer.h"
-#include "buffers/ide-buffer-manager.h"
-#include "files/ide-file.h"
-#include "projects/ide-project-item.h"
-#include "projects/ide-project.h"
-#include "subprocess/ide-subprocess.h"
-#include "subprocess/ide-subprocess-launcher.h"
-#include "util/ide-flatpak.h"
-#include "vcs/ide-vcs.h"
-#include "threading/ide-task.h"
+#include "ide-buffer-private.h"
+
+#include "ide-project.h"
struct _IdeProject
{
- IdeObject parent_instance;
-
- GRWLock rw_lock;
- IdeProjectItem *root;
- gchar *name;
- gchar *id;
+ IdeObject parent_instance;
};
typedef struct
@@ -56,250 +41,19 @@ typedef struct
IdeBuffer *buffer;
} RenameFile;
-G_DEFINE_TYPE (IdeProject, ide_project, IDE_TYPE_OBJECT)
-
-enum {
- PROP_0,
- PROP_ID,
- PROP_NAME,
- PROP_ROOT,
- LAST_PROP
-};
-
enum {
FILE_RENAMED,
FILE_TRASHED,
- LAST_SIGNAL
+ N_SIGNALS
};
-static GParamSpec *properties [LAST_PROP];
-static guint signals [LAST_SIGNAL];
-
-void
-ide_project_reader_lock (IdeProject *self)
-{
- g_return_if_fail (IDE_IS_PROJECT (self));
-
- g_rw_lock_reader_lock (&self->rw_lock);
-}
-
-void
-ide_project_reader_unlock (IdeProject *self)
-{
- g_return_if_fail (IDE_IS_PROJECT (self));
-
- g_rw_lock_reader_unlock (&self->rw_lock);
-}
-
-void
-ide_project_writer_lock (IdeProject *self)
-{
- g_return_if_fail (IDE_IS_PROJECT (self));
-
- g_rw_lock_writer_lock (&self->rw_lock);
-}
-
-void
-ide_project_writer_unlock (IdeProject *self)
-{
- g_return_if_fail (IDE_IS_PROJECT (self));
-
- g_rw_lock_writer_unlock (&self->rw_lock);
-}
-
-/**
- * ide_project_create_id:
- * @name: the name of the project
- *
- * Escapes the project name into something suitable using as an id.
- * This can be uesd to determine the directory name when the project
- * name should be used.
- *
- * Returns: (transfer full): a new string
- *
- * Since: 3.32
- */
-gchar *
-ide_project_create_id (const gchar *name)
-{
- g_return_val_if_fail (name != NULL, NULL);
-
- return g_strdelimit (g_strdup (name), " /|<>\n\t", '-');
-}
-
-const gchar *
-ide_project_get_id (IdeProject *self)
-{
- g_return_val_if_fail (IDE_IS_PROJECT (self), NULL);
-
- return self->id;
-}
-
-const gchar *
-ide_project_get_name (IdeProject *self)
-{
- g_return_val_if_fail (IDE_IS_PROJECT (self), NULL);
-
- return self->name;
-}
-
-void
-_ide_project_set_name (IdeProject *self,
- const gchar *name)
-{
- g_return_if_fail (IDE_IS_PROJECT (self));
-
- if (self->name != name)
- {
- g_free (self->name);
- self->name = g_strdup (name);
- self->id = ide_project_create_id (name);
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
- }
-}
-
-/**
- * ide_project_get_root:
- *
- * Retrieves the root item of the project tree.
- *
- * You must be holding the reader lock while calling and using the result of
- * this function. Other thread may be accessing or modifying the tree without
- * your knowledge. See ide_project_reader_lock() and ide_project_reader_unlock()
- * for more information.
- *
- * If you need to modify the tree, you must hold a writer lock that has been
- * acquired with ide_project_writer_lock() and released with
- * ide_project_writer_unlock() when you are no longer modifiying the tree.
- *
- * Returns: (transfer none): An #IdeProjectItem.
- *
- * Since: 3.32
- */
-IdeProjectItem *
-ide_project_get_root (IdeProject *self)
-{
- g_return_val_if_fail (IDE_IS_PROJECT (self), NULL);
-
- return self->root;
-}
-
-static void
-ide_project_set_root (IdeProject *self,
- IdeProjectItem *root)
-{
- g_autoptr(IdeProjectItem) allocated = NULL;
- IdeContext *context;
-
- g_return_if_fail (IDE_IS_PROJECT (self));
- g_return_if_fail (!root || IDE_IS_PROJECT_ITEM (root));
-
- context = ide_object_get_context (IDE_OBJECT (self));
-
- if (!root)
- {
- allocated = g_object_new (IDE_TYPE_PROJECT_ITEM,
- "context", context,
- NULL);
- root = allocated;
- }
-
- if (g_set_object (&self->root, root))
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ROOT]);
-}
-
-static void
-ide_project_finalize (GObject *object)
-{
- IdeProject *self = (IdeProject *)object;
-
- g_clear_object (&self->root);
- g_clear_pointer (&self->name, g_free);
- g_rw_lock_clear (&self->rw_lock);
-
- G_OBJECT_CLASS (ide_project_parent_class)->finalize (object);
-}
-
-static void
-ide_project_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- IdeProject *self = IDE_PROJECT (object);
-
- switch (prop_id)
- {
- case PROP_ID:
- g_value_set_string (value, ide_project_get_id (self));
- break;
-
- case PROP_NAME:
- g_value_set_string (value, ide_project_get_name (self));
- break;
-
- case PROP_ROOT:
- g_value_set_object (value, ide_project_get_root (self));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
-
-static void
-ide_project_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdeProject *self = IDE_PROJECT (object);
+G_DEFINE_TYPE (IdeProject, ide_project, IDE_TYPE_OBJECT)
- switch (prop_id)
- {
- case PROP_ROOT:
- ide_project_set_root (self, g_value_get_object (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
+static guint signals [N_SIGNALS];
static void
ide_project_class_init (IdeProjectClass *klass)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- object_class->finalize = ide_project_finalize;
- object_class->get_property = ide_project_get_property;
- object_class->set_property = ide_project_set_property;
-
- properties [PROP_ID] =
- g_param_spec_string ("id",
- "ID",
- "The unique project identifier.",
- NULL,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_NAME] =
- g_param_spec_string ("name",
- "Name",
- "The name of the project.",
- NULL,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_ROOT] =
- g_param_spec_object ("root",
- "Root",
- "The root object for the project.",
- IDE_TYPE_PROJECT_ITEM,
- (G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT_ONLY |
- G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_properties (object_class, LAST_PROP, properties);
-
signals [FILE_RENAMED] =
g_signal_new ("file-renamed",
G_TYPE_FROM_CLASS (klass),
@@ -318,7 +72,31 @@ ide_project_class_init (IdeProjectClass *klass)
static void
ide_project_init (IdeProject *self)
{
- g_rw_lock_init (&self->rw_lock);
+}
+
+/**
+ * ide_project_from_context:
+ * @context: #IdeContext
+ *
+ * Gets the project for an #IdeContext.
+ *
+ * Returns: (transfer none): an #IdeProject
+ *
+ * Since: 3.32
+ */
+IdeProject *
+ide_project_from_context (IdeContext *context)
+{
+ IdeProject *self;
+
+ g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+ g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+ /* Return borrowed reference */
+ self = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_PROJECT);
+ g_object_unref (self);
+
+ return self;
}
static void
@@ -372,11 +150,10 @@ ide_project_rename_file_worker (IdeTask *task,
IdeProject *self = source_object;
g_autofree gchar *path = NULL;
g_autoptr(GFile) parent = NULL;
+ g_autoptr(GFile) workdir = NULL;
g_autoptr(GError) error = NULL;
RenameFile *op = task_data;
IdeContext *context;
- IdeVcs *vcs;
- GFile *workdir;
g_assert (IDE_IS_PROJECT (self));
g_assert (op != NULL);
@@ -385,8 +162,7 @@ ide_project_rename_file_worker (IdeTask *task,
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
context = ide_object_get_context (IDE_OBJECT (self));
- vcs = ide_context_get_vcs (context);
- workdir = ide_vcs_get_working_directory (vcs);
+ workdir = ide_context_ref_workdir (context);
path = g_file_get_relative_path (workdir, op->new_file);
#ifdef IDE_ENABLE_TRACE
@@ -440,19 +216,17 @@ ide_project_rename_buffer_save_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
- IdeBufferManager *bufmgr = (IdeBufferManager *)object;
+ IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeTask) task = user_data;
- g_autoptr(IdeFile) file = NULL;
g_autoptr(GError) error = NULL;
- IdeContext *context;
RenameFile *rf;
g_assert (IDE_IS_MAIN_THREAD ());
- g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
+ g_assert (IDE_IS_BUFFER (buffer));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
- if (!ide_buffer_manager_save_file_finish (bufmgr, result, &error))
+ if (!ide_buffer_save_file_finish (buffer, result, &error))
{
ide_task_return_error (task, g_steal_pointer (&error));
return;
@@ -468,9 +242,7 @@ ide_project_rename_buffer_save_cb (GObject *object,
* Change the filename in the buffer so that the user doesn't continue
* to edit the file under the old name.
*/
- context = ide_object_get_context (IDE_OBJECT (bufmgr));
- file = ide_file_new (context, rf->new_file);
- ide_buffer_set_file (rf->buffer, file);
+ _ide_buffer_set_file (rf->buffer, rf->new_file);
ide_task_run_in_thread (task, ide_project_rename_file_worker);
}
@@ -500,7 +272,7 @@ ide_project_rename_file_async (IdeProject *self,
ide_task_set_priority (task, G_PRIORITY_LOW);
context = ide_object_get_context (IDE_OBJECT (self));
- bufmgr = ide_context_get_buffer_manager (context);
+ bufmgr = ide_buffer_manager_from_context (context);
buffer = ide_buffer_manager_find_buffer (bufmgr, orig_file);
op = g_slice_new0 (RenameFile);
@@ -515,22 +287,18 @@ ide_project_rename_file_async (IdeProject *self,
*/
if (buffer != NULL)
{
- g_autoptr(IdeFile) from = ide_file_new (context, orig_file);
- g_autoptr(IdeFile) to = ide_file_new (context, new_file);
-
if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (buffer)))
{
- ide_buffer_manager_save_file_async (bufmgr,
- buffer,
- from,
- NULL,
- NULL,
- ide_project_rename_buffer_save_cb,
- g_steal_pointer (&task));
+ ide_buffer_save_file_async (buffer,
+ orig_file,
+ NULL,
+ NULL,
+ ide_project_rename_buffer_save_cb,
+ g_steal_pointer (&task));
return;
}
- ide_buffer_set_file (buffer, to);
+ _ide_buffer_set_file (buffer, new_file);
}
ide_task_run_in_thread (task, ide_project_rename_file_worker);
@@ -626,9 +394,8 @@ ide_project_trash_file_async (IdeProject *self,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
+ g_autoptr(GFile) workdir = NULL;
IdeContext *context;
- IdeVcs *vcs;
- GFile *workdir;
IDE_ENTRY;
@@ -636,8 +403,7 @@ ide_project_trash_file_async (IdeProject *self,
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
context = ide_object_get_context (IDE_OBJECT (self));
- vcs = ide_context_get_vcs (context);
- workdir = ide_vcs_get_working_directory (vcs);
+ workdir = ide_context_ref_workdir (context);
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_project_trash_file_async);
diff --git a/src/libide/projects/ide-project.h b/src/libide/projects/ide-project.h
index f3240ac24..ebf2b28b0 100644
--- a/src/libide/projects/ide-project.h
+++ b/src/libide/projects/ide-project.h
@@ -1,6 +1,6 @@
/* ide-project.h
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,9 +20,7 @@
#pragma once
-#include "ide-version-macros.h"
-
-#include "ide-object.h"
+#include <libide-core.h>
G_BEGIN_DECLS
@@ -32,21 +30,7 @@ IDE_AVAILABLE_IN_3_32
G_DECLARE_FINAL_TYPE (IdeProject, ide_project, IDE, PROJECT, IdeObject)
IDE_AVAILABLE_IN_3_32
-gchar *ide_project_create_id (const gchar *name);
-IDE_AVAILABLE_IN_3_32
-IdeProjectItem *ide_project_get_root (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-const gchar *ide_project_get_name (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-const gchar *ide_project_get_id (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-void ide_project_reader_lock (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-void ide_project_reader_unlock (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-void ide_project_writer_lock (IdeProject *self);
-IDE_AVAILABLE_IN_3_32
-void ide_project_writer_unlock (IdeProject *self);
+IdeProject *ide_project_from_context (IdeContext *context);
IDE_AVAILABLE_IN_3_32
void ide_project_rename_file_async (IdeProject *self,
GFile *orig_file,
@@ -68,7 +52,5 @@ IDE_AVAILABLE_IN_3_32
gboolean ide_project_trash_file_finish (IdeProject *self,
GAsyncResult *result,
GError **error);
-void _ide_project_set_name (IdeProject *project,
- const gchar *name) G_GNUC_INTERNAL;
G_END_DECLS
diff --git a/src/libide/projects/ide-projects-global.c b/src/libide/projects/ide-projects-global.c
new file mode 100644
index 000000000..d762d072c
--- /dev/null
+++ b/src/libide/projects/ide-projects-global.c
@@ -0,0 +1,132 @@
+/* ide-projects-global.c
+ *
+ * Copyright 2018-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-projects-global"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-io.h>
+
+#include "ide-projects-global.h"
+
+static GSettings *g_settings;
+static gchar *projects_directory;
+
+static void
+on_projects_directory_changed_cb (GSettings *settings,
+ const gchar *key,
+ gpointer user_data)
+{
+ g_assert (G_IS_SETTINGS (settings));
+ g_assert (key != NULL);
+
+ g_clear_pointer (&projects_directory, g_free);
+}
+
+/**
+ * ide_get_projects_dir:
+ *
+ * Gets the directory to store projects within.
+ *
+ * First, this checks GSettings for a directory. If that directory exists,
+ * it is returned.
+ *
+ * If not, it then checks for the non-translated name "Projects" in the
+ * users home directory. If it exists, that is returned.
+ *
+ * If that does not exist, and a GSetting path existed, but was non-existant
+ * that is returned.
+ *
+ * If the GSetting was empty, the translated name "Projects" is returned.
+ *
+ * Returns: (not nullable) (transfer full): a #GFile
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_get_projects_dir (void)
+{
+ g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+
+ if G_UNLIKELY (g_settings == NULL)
+ {
+ g_settings = g_settings_new ("org.gnome.builder");
+ g_signal_connect (g_settings,
+ "changed::projects-directory",
+ G_CALLBACK (on_projects_directory_changed_cb),
+ NULL);
+ }
+
+ if G_UNLIKELY (projects_directory == NULL)
+ {
+ g_autofree gchar *dir = g_settings_get_string (g_settings, "projects-directory");
+ g_autofree gchar *expanded = ide_path_expand (dir);
+ g_autofree gchar *projects = NULL;
+ g_autofree gchar *translated = NULL;
+
+ if (g_file_test (expanded, G_FILE_TEST_IS_DIR))
+ {
+ projects_directory = g_steal_pointer (&expanded);
+ goto completed;
+ }
+
+ projects = g_build_filename (g_get_home_dir (), "Projects", NULL);
+
+ if (g_file_test (projects, G_FILE_TEST_IS_DIR))
+ {
+ projects_directory = g_steal_pointer (&projects);
+ goto completed;
+ }
+
+ if (!ide_str_empty0 (dir) && !ide_str_empty0 (expanded))
+ {
+ projects_directory = g_steal_pointer (&expanded);
+ goto completed;
+ }
+
+ translated = g_build_filename (g_get_home_dir (), _("Projects"), NULL);
+ projects_directory = g_steal_pointer (&translated);
+ }
+
+completed:
+
+ return projects_directory;
+}
+
+/**
+ * ide_create_project_id:
+ * @name: the name of the project
+ *
+ * Escapes the project name into something suitable using as an id.
+ * This can be uesd to determine the directory name when the project
+ * name should be used.
+ *
+ * Returns: (transfer full): a new string
+ *
+ * Since: 3.32
+ */
+gchar *
+ide_create_project_id (const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_strdelimit (g_strdup (name), " /|<>\n\t", '-');
+}
diff --git a/src/libide/projects/ide-project-edit-private.h b/src/libide/projects/ide-projects-global.h
similarity index 65%
rename from src/libide/projects/ide-project-edit-private.h
rename to src/libide/projects/ide-projects-global.h
index 0faba79b1..f32401be4 100644
--- a/src/libide/projects/ide-project-edit-private.h
+++ b/src/libide/projects/ide-projects-global.h
@@ -1,6 +1,6 @@
-/* ide-project-edit-private.h
+/* ide-projects-global.h
*
- * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-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
@@ -20,14 +20,17 @@
#pragma once
-#include "buffers/ide-buffer.h"
-#include "projects/ide-project-edit.h"
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <libide-core.h>
G_BEGIN_DECLS
-void _ide_project_edit_prepare (IdeProjectEdit *self,
- IdeBuffer *buffer);
-void _ide_project_edit_apply (IdeProjectEdit *self,
- IdeBuffer *buffer);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_get_projects_dir (void);
+IDE_AVAILABLE_IN_3_32
+gchar *ide_create_project_id (const gchar *name);
G_END_DECLS
diff --git a/src/libide/projects/ide-recent-projects.c b/src/libide/projects/ide-recent-projects.c
index 7ace9d079..53ccbfca5 100644
--- a/src/libide/projects/ide-recent-projects.c
+++ b/src/libide/projects/ide-recent-projects.c
@@ -1,6 +1,6 @@
/* ide-recent-projects.c
*
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-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
@@ -24,10 +24,9 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
+#include <libide-core.h>
-#include "ide-global.h"
-
-#include "projects/ide-recent-projects.h"
+#include "ide-recent-projects.h"
struct _IdeRecentProjects
{
@@ -53,6 +52,29 @@ ide_recent_projects_new (void)
return g_object_new (IDE_TYPE_RECENT_PROJECTS, NULL);
}
+/**
+ * ide_recent_projects_get_default:
+ *
+ * Gets a shared #IdeRecentProjects instance.
+ *
+ * If this instance is unref'd, a new instance will be created on the next
+ * request to get the default #IdeRecentProjects instance.
+ *
+ * Returns: (transfer none): an #IdeRecentProjects
+ *
+ * Since: 3.32
+ */
+IdeRecentProjects *
+ide_recent_projects_get_default (void)
+{
+ static IdeRecentProjects *instance;
+
+ if (instance == NULL)
+ g_set_weak_pointer (&instance, ide_recent_projects_new ());
+
+ return instance;
+}
+
static void
ide_recent_projects_added (IdeRecentProjects *self,
IdeProjectInfo *project_info)
@@ -87,22 +109,23 @@ static GBookmarkFile *
ide_recent_projects_get_bookmarks (IdeRecentProjects *self,
GError **error)
{
- GBookmarkFile *bookmarks;
+ g_autoptr(GBookmarkFile) bookmarks = NULL;
+ g_autoptr(GError) local_error = NULL;
g_assert (IDE_IS_RECENT_PROJECTS (self));
bookmarks = g_bookmark_file_new ();
- if (!g_bookmark_file_load_from_file (bookmarks, self->file_uri, error))
+ if (!g_bookmark_file_load_from_file (bookmarks, self->file_uri, &local_error))
{
- if (!g_error_matches (*error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ if (!g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
{
- g_object_unref (bookmarks);
+ g_propagate_error (error, g_steal_pointer (&local_error));
return NULL;
}
}
- return bookmarks;
+ return g_steal_pointer (&bookmarks);
}
static void
@@ -112,13 +135,10 @@ ide_recent_projects_load_recent (IdeRecentProjects *self)
g_autoptr(GError) error = NULL;
gboolean needs_sync = FALSE;
gchar **uris;
- gssize z;
g_assert (IDE_IS_RECENT_PROJECTS (self));
- projects_file = ide_recent_projects_get_bookmarks (self, &error);
-
- if (projects_file == NULL)
+ if (!(projects_file = ide_recent_projects_get_bookmarks (self, &error)))
{
g_warning ("Unable to open recent projects file: %s", error->message);
return;
@@ -126,7 +146,7 @@ ide_recent_projects_load_recent (IdeRecentProjects *self)
uris = g_bookmark_file_get_uris (projects_file, NULL);
- for (z = 0; uris[z]; z++)
+ for (gsize z = 0; uris[z]; z++)
{
g_autoptr(GDateTime) last_modified_at = NULL;
g_autoptr(GFile) project_file = NULL;
@@ -137,14 +157,14 @@ ide_recent_projects_load_recent (IdeRecentProjects *self)
g_autofree gchar *description = NULL;
const gchar *build_system_name = NULL;
const gchar *uri = uris[z];
+ const gchar *diruri = NULL;
time_t modified;
g_auto(GStrv) groups = NULL;
gsize len;
- gsize i;
groups = g_bookmark_file_get_groups (projects_file, uri, &len, NULL);
- for (i = 0; i < len; i++)
+ for (gsize i = 0; i < len; i++)
{
if (g_str_equal (groups [i], IDE_RECENT_PROJECTS_GROUP))
goto is_project;
@@ -166,10 +186,20 @@ ide_recent_projects_load_recent (IdeRecentProjects *self)
description = g_bookmark_file_get_description (projects_file, uri, NULL);
modified = g_bookmark_file_get_modified (projects_file, uri, NULL);
last_modified_at = g_date_time_new_from_unix_local (modified);
- directory = g_file_get_parent (project_file);
+
+ for (gsize i = 0; i < len; i++)
+ {
+ if (g_str_has_prefix (groups [i], IDE_RECENT_PROJECTS_DIRECTORY))
+ diruri = groups [i] + strlen (IDE_RECENT_PROJECTS_DIRECTORY);
+ }
+
+ if (diruri == NULL)
+ directory = g_file_get_parent (project_file);
+ else
+ directory = g_file_new_for_uri (diruri);
languages = g_ptr_array_new ();
- for (i = 0; i < len; i++)
+ for (gsize i = 0; i < len; i++)
{
if (g_str_has_prefix (groups [i], IDE_RECENT_PROJECTS_LANGUAGE_GROUP_PREFIX))
g_ptr_array_add (languages, groups [i] + strlen (IDE_RECENT_PROJECTS_LANGUAGE_GROUP_PREFIX));
@@ -282,7 +312,8 @@ ide_recent_projects_init (IdeRecentProjects *self)
/**
* ide_recent_projects_remove:
* @self: An #IdeRecentProjects
- * @project_infos: (transfer none) (element-type Ide.ProjectInfo): a #GList of #IdeProjectInfo.
+ * @project_infos: (transfer none) (element-type IdeProjectInfo): a #GList
+ * of #IdeProjectInfo.
*
* Removes the provided projects from the recent projects file.
*
@@ -298,9 +329,7 @@ ide_recent_projects_remove (IdeRecentProjects *self,
g_return_if_fail (IDE_IS_RECENT_PROJECTS (self));
- projects_file = ide_recent_projects_get_bookmarks (self, &error);
-
- if (projects_file == NULL)
+ if (!(projects_file = ide_recent_projects_get_bookmarks (self, &error)))
{
g_warning ("Failed to load bookmarks file: %s", error->message);
return;
@@ -375,7 +404,7 @@ ide_recent_projects_find_by_directory (IdeRecentProjects *self,
if (!g_file_test (directory, G_FILE_TEST_IS_DIR))
return NULL;
- if (NULL == (bookmarks = ide_recent_projects_get_bookmarks (self, NULL)))
+ if (!(bookmarks = ide_recent_projects_get_bookmarks (self, NULL)))
return NULL;
uris = g_bookmark_file_get_uris (bookmarks, &len);
diff --git a/src/libide/projects/ide-recent-projects.h b/src/libide/projects/ide-recent-projects.h
index ee3005d49..d5f4c2435 100644
--- a/src/libide/projects/ide-recent-projects.h
+++ b/src/libide/projects/ide-recent-projects.h
@@ -20,9 +20,8 @@
#pragma once
-#include "ide-version-macros.h"
-
-#include "projects/ide-project-info.h"
+#include <libide-core.h>
+#include <libide-projects.h>
G_BEGIN_DECLS
@@ -31,11 +30,14 @@ G_BEGIN_DECLS
#define IDE_RECENT_PROJECTS_GROUP "X-GNOME-Builder-Project"
#define IDE_RECENT_PROJECTS_LANGUAGE_GROUP_PREFIX "X-GNOME-Builder-Language:"
#define IDE_RECENT_PROJECTS_BUILD_SYSTEM_GROUP_PREFIX "X-GNOME-Builder-Build-System:"
+#define IDE_RECENT_PROJECTS_DIRECTORY "X-GNOME-Builder-Directory:"
#define IDE_RECENT_PROJECTS_BOOKMARK_FILENAME "recent-projects.xbel"
IDE_AVAILABLE_IN_3_32
G_DECLARE_FINAL_TYPE (IdeRecentProjects, ide_recent_projects, IDE, RECENT_PROJECTS, GObject)
+IDE_AVAILABLE_IN_3_32
+IdeRecentProjects *ide_recent_projects_get_default (void);
IDE_AVAILABLE_IN_3_32
IdeRecentProjects *ide_recent_projects_new (void);
IDE_AVAILABLE_IN_3_32
diff --git a/src/libide/projects/ide-template-base.c b/src/libide/projects/ide-template-base.c
new file mode 100644
index 000000000..81879f3da
--- /dev/null
+++ b/src/libide/projects/ide-template-base.c
@@ -0,0 +1,724 @@
+/* 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;
+}
diff --git a/src/libide/projects/ide-template-base.h b/src/libide/projects/ide-template-base.h
new file mode 100644
index 000000000..83df2affd
--- /dev/null
+++ b/src/libide/projects/ide-template-base.h
@@ -0,0 +1,71 @@
+/* ide-template-base.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+#include <tmpl-glib.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE_BASE (ide_template_base_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeTemplateBase, ide_template_base, IDE, TEMPLATE_BASE, GObject)
+
+struct _IdeTemplateBaseClass
+{
+ GObjectClass parent_class;
+};
+
+IDE_AVAILABLE_IN_3_32
+TmplTemplateLocator *ide_template_base_get_locator (IdeTemplateBase *self);
+IDE_AVAILABLE_IN_3_32
+void ide_template_base_set_locator (IdeTemplateBase *self,
+ TmplTemplateLocator *locator);
+IDE_AVAILABLE_IN_3_32
+void ide_template_base_add_resource (IdeTemplateBase *self,
+ const gchar *resource_path,
+ GFile *destination,
+ TmplScope *scope,
+ gint mode);
+IDE_AVAILABLE_IN_3_32
+void ide_template_base_add_path (IdeTemplateBase *self,
+ const gchar *path,
+ GFile *destination,
+ TmplScope *scope,
+ gint mode);
+IDE_AVAILABLE_IN_3_32
+void ide_template_base_expand_all_async (IdeTemplateBase *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_template_base_expand_all_finish (IdeTemplateBase *self,
+ GAsyncResult *result,
+ GError **error);
+IDE_AVAILABLE_IN_3_32
+void ide_template_base_reset (IdeTemplateBase *self);
+
+G_END_DECLS
diff --git a/src/libide/projects/ide-template-provider.c b/src/libide/projects/ide-template-provider.c
new file mode 100644
index 000000000..58014d433
--- /dev/null
+++ b/src/libide/projects/ide-template-provider.c
@@ -0,0 +1,61 @@
+/* ide-template-provider.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-provider"
+
+#include "config.h"
+
+#include "ide-template-provider.h"
+
+G_DEFINE_INTERFACE (IdeTemplateProvider, ide_template_provider, G_TYPE_OBJECT)
+
+static GList *
+ide_template_provider_real_get_project_templates (IdeTemplateProvider *self)
+{
+ return NULL;
+}
+
+static void
+ide_template_provider_default_init (IdeTemplateProviderInterface *iface)
+{
+ iface->get_project_templates = ide_template_provider_real_get_project_templates;
+}
+
+/**
+ * ide_template_provider_get_project_templates:
+ * @self: An #IdeTemplateProvider
+ *
+ * Gets a list of templates for this provider.
+ *
+ * Plugins should implement this interface to feed #IdeProjectTemplate's into
+ * the project creation workflow.
+ *
+ * Returns: (transfer full) (element-type Ide.ProjectTemplate): a #GList of
+ * #IdeProjectTemplate instances.
+ *
+ * Since: 3.32
+ */
+GList *
+ide_template_provider_get_project_templates (IdeTemplateProvider *self)
+{
+ g_return_val_if_fail (IDE_IS_TEMPLATE_PROVIDER (self), NULL);
+
+ return IDE_TEMPLATE_PROVIDER_GET_IFACE (self)->get_project_templates (self);
+}
diff --git a/src/libide/projects/ide-template-provider.h b/src/libide/projects/ide-template-provider.h
new file mode 100644
index 000000000..8325794ed
--- /dev/null
+++ b/src/libide/projects/ide-template-provider.h
@@ -0,0 +1,48 @@
+/* ide-template-provider.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_PROJECTS_INSIDE) && !defined (IDE_PROJECTS_COMPILATION)
+# error "Only <libide-projects.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-project-template.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEMPLATE_PROVIDER (ide_template_provider_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_INTERFACE (IdeTemplateProvider, ide_template_provider, IDE, TEMPLATE_PROVIDER, GObject)
+
+struct _IdeTemplateProviderInterface
+{
+ GTypeInterface parent_iface;
+
+ GList *(*get_project_templates) (IdeTemplateProvider *self);
+};
+
+IDE_AVAILABLE_IN_3_32
+GList *ide_template_provider_get_project_templates (IdeTemplateProvider *self);
+
+G_END_DECLS
diff --git a/src/libide/projects/libide-projects.h b/src/libide/projects/libide-projects.h
new file mode 100644
index 000000000..f1f7db00d
--- /dev/null
+++ b/src/libide/projects/libide-projects.h
@@ -0,0 +1,40 @@
+/* ide-projects.h
+ *
+ * Copyright 2018-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
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-io.h>
+
+#define IDE_PROJECTS_INSIDE
+
+#include "ide-doap.h"
+#include "ide-doap-person.h"
+#include "ide-project.h"
+#include "ide-project-info.h"
+#include "ide-project-file.h"
+#include "ide-project-template.h"
+#include "ide-project-tree-addin.h"
+#include "ide-projects-global.h"
+#include "ide-recent-projects.h"
+#include "ide-template-base.h"
+#include "ide-template-provider.h"
+
+#undef IDE_PROJECTS_INSIDE
diff --git a/src/libide/projects/meson.build b/src/libide/projects/meson.build
index ab7587693..0a68c01cb 100644
--- a/src/libide/projects/meson.build
+++ b/src/libide/projects/meson.build
@@ -1,27 +1,85 @@
-projects_headers = [
- 'ide-project-edit.h',
- 'ide-project-info.h',
- 'ide-project-item.h',
+libide_projects_header_subdir = join_paths(libide_header_subdir, 'projects')
+libide_include_directories += include_directories('.')
+
+#
+# Public API Headers
+#
+
+libide_projects_public_headers = [
+ 'ide-doap.h',
+ 'ide-doap-person.h',
'ide-project.h',
+ 'ide-project-info.h',
+ 'ide-project-file.h',
+ 'ide-projects-global.h',
+ 'ide-project-template.h',
'ide-project-tree-addin.h',
'ide-recent-projects.h',
+ 'ide-template-base.h',
+ 'ide-template-provider.h',
+ 'libide-projects.h',
]
-projects_sources = [
- 'ide-project-edit.c',
- 'ide-project-info.c',
- 'ide-project-item.c',
+install_headers(libide_projects_public_headers, subdir: libide_projects_header_subdir)
+
+#
+# Sources
+#
+
+libide_projects_private_headers = [ 'xml-reader-private.h', ]
+libide_projects_private_sources = [ 'xml-reader.c', ]
+
+libide_projects_public_sources = [
+ 'ide-doap.c',
+ 'ide-doap-person.c',
'ide-project.c',
+ 'ide-project-info.c',
+ 'ide-project-file.c',
+ 'ide-projects-global.c',
+ 'ide-project-template.c',
'ide-project-tree-addin.c',
'ide-recent-projects.c',
+ 'ide-template-base.c',
+ 'ide-template-provider.c',
]
-projects_private_sources = [
- 'ide-project-edit-private.h',
+libide_projects_sources = libide_projects_public_sources + libide_projects_private_sources
+
+#
+# Dependencies
+#
+
+libide_projects_deps = [
+ libgio_dep,
+ libgtk_dep,
+ libdazzle_dep,
+ libtemplate_glib_dep,
+ libxml2_dep,
+
+ libide_core_dep,
+ libide_io_dep,
+ libide_threading_dep,
+ libide_code_dep,
+ libide_vcs_dep,
]
-libide_public_headers += files(projects_headers)
-libide_public_sources += files(projects_sources)
-libide_private_sources += files(projects_private_sources)
+#
+# Library Definitions
+#
+
+libide_projects = static_library('ide-projects-' + libide_api_version, libide_projects_sources,
+ dependencies: libide_projects_deps,
+ c_args: libide_args + release_args + ['-DIDE_PROJECTS_COMPILATION'],
+)
+
+libide_projects_dep = declare_dependency(
+ sources: libide_projects_private_headers,
+ dependencies: libide_projects_deps,
+ link_whole: libide_projects,
+ include_directories: include_directories('.'),
+)
-install_headers(projects_headers, subdir: join_paths(libide_header_subdir, 'projects'))
+gnome_builder_public_sources += files(libide_projects_public_sources)
+gnome_builder_public_headers += files(libide_projects_public_headers)
+gnome_builder_include_subdirs += libide_projects_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-projects.h', '-DIDE_PROJECTS_COMPILATION']
diff --git a/src/libide/doap/xml-reader.h b/src/libide/projects/xml-reader-private.h
similarity index 99%
rename from src/libide/doap/xml-reader.h
rename to src/libide/projects/xml-reader-private.h
index f4be11b22..0c7e574de 100644
--- a/src/libide/doap/xml-reader.h
+++ b/src/libide/projects/xml-reader-private.h
@@ -17,6 +17,8 @@
*
* Based upon work by:
* Emmanuele Bassi
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
diff --git a/src/libide/doap/xml-reader.c b/src/libide/projects/xml-reader.c
similarity index 99%
rename from src/libide/doap/xml-reader.c
rename to src/libide/projects/xml-reader.c
index dc4b6baf8..bcd9ca042 100644
--- a/src/libide/doap/xml-reader.c
+++ b/src/libide/projects/xml-reader.c
@@ -14,13 +14,15 @@
*
* Author:
* Christian Hergert <chris dronelabs com>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include <string.h>
#include <libxml/xmlreader.h>
-#include "doap/xml-reader.h"
+#include "xml-reader-private.h"
#define XML_TO_CHAR(s) ((char *) (s))
#define CHAR_TO_XML(s) ((unsigned char *) (s))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]