[gnome-software/wip/rancell/paid: 1/3] Add basic purchasing concepts



commit 3c7b4c99c90074cf6fad76f7927e0144c11ebfe0
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Feb 23 14:55:05 2016 +1300

    Add basic purchasing concepts

 src/gs-app.c                  |  117 +++++++++++++++++++++++++++++++++++++++++
 src/gs-app.h                  |    9 +++
 src/gs-page.c                 |   60 +++++++++++++++++++++
 src/gs-page.h                 |    3 +
 src/gs-plugin-loader.h        |    1 +
 src/gs-plugin.h               |    4 ++
 src/gs-shell-details.c        |   16 +++++-
 src/gs-utils.c                |   31 +++++++++++
 src/gs-utils.h                |    3 +
 src/plugins/gs-plugin-dummy.c |   14 +++++
 10 files changed, 257 insertions(+), 1 deletions(-)
---
diff --git a/src/gs-app.c b/src/gs-app.c
index 96499b8..3a34345 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -85,6 +85,9 @@ struct _GsApp
        AsUrgencyKind            update_urgency;
        gchar                   *management_plugin;
        guint                    match_value;
+       guint                    price;
+       gchar                   *currency;
+       gboolean                 owned;
        gint                     rating;
        GArray                  *review_ratings;
        GPtrArray               *reviews; /* of GsReview */
@@ -117,6 +120,9 @@ enum {
        PROP_VERSION,
        PROP_SUMMARY,
        PROP_DESCRIPTION,
+       PROP_PRICE,
+       PROP_CURRENCY,
+       PROP_OWNED,
        PROP_RATING,
        PROP_KIND,
        PROP_STATE,
@@ -304,6 +310,11 @@ gs_app_to_string (GsApp *app)
                gs_app_kv_lpad (str, "origin", app->origin);
        if (app->origin_ui != NULL && app->origin_ui[0] != '\0')
                gs_app_kv_lpad (str, "origin-ui", app->origin_ui);
+       if (app->price > 0) {
+               gs_app_kv_printf (str, "price", "%u", app->price);
+               if (app->currency != NULL)
+                       gs_app_kv_printf (str, "currency", "%s", app->currency);
+       }
        if (app->rating != -1)
                gs_app_kv_printf (str, "rating", "%i", app->rating);
        if (app->review_ratings != NULL) {
@@ -1749,6 +1760,69 @@ gs_app_set_management_plugin (GsApp *app, const gchar *management_plugin)
 }
 
 /**
+ * gs_app_get_price:
+ */
+guint
+gs_app_get_price (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), 0);
+       return app->price;
+}
+
+/**
+ * gs_app_set_price:
+ */
+void
+gs_app_set_price (GsApp *app, guint price)
+{
+       g_return_if_fail (GS_IS_APP (app));
+       app->price = price;
+       gs_app_queue_notify (app, "price");
+}
+
+/**
+ * gs_app_get_currency:
+ */
+const gchar *
+gs_app_get_currency (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), NULL);
+       return app->currency;
+}
+
+/**
+ * gs_app_set_currency:
+ */
+void
+gs_app_set_currency (GsApp *app, const gchar *currency)
+{
+       g_return_if_fail (GS_IS_APP (app));
+       app->currency = currency;
+       gs_app_queue_notify (app, "currency");
+}
+
+/**
+ * gs_app_get_owned:
+ */
+gboolean
+gs_app_get_owned (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), FALSE);
+       return app->owned;
+}
+
+/**
+ * gs_app_set_owned:
+ */
+void
+gs_app_set_owned (GsApp *app, gboolean owned)
+{
+       g_return_if_fail (GS_IS_APP (app));
+       app->owned = owned;
+       gs_app_queue_notify (app, "owned");
+}
+
+/**
  * gs_app_get_rating:
  */
 gint
@@ -2346,6 +2420,15 @@ gs_app_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *
        case PROP_DESCRIPTION:
                g_value_set_string (value, app->description);
                break;
+       case PROP_PRICE:
+               g_value_set_uint (value, app->price);
+               break;
+       case PROP_CURRENCY:
+               g_value_set_string (value, app->currency);
+               break;
+       case PROP_OWNED:
+               g_value_set_boolean (value, app->owned);
+               break;
        case PROP_RATING:
                g_value_set_int (value, app->rating);
                break;
@@ -2400,6 +2483,15 @@ gs_app_set_property (GObject *object, guint prop_id, const GValue *value, GParam
                                        GS_APP_QUALITY_UNKNOWN,
                                        g_value_get_string (value));
                break;
+       case PROP_PRICE:
+               gs_app_set_price (app, g_value_get_uint (value));
+               break;
+       case PROP_CURRENCY:
+               gs_app_set_currency (app, g_value_get_string (value));
+               break;
+       case PROP_OWNED:
+               gs_app_set_owned (app, g_value_get_boolean (value));
+               break;
        case PROP_RATING:
                gs_app_set_rating (app, g_value_get_int (value));
                break;
@@ -2474,6 +2566,7 @@ gs_app_finalize (GObject *object)
        g_free (app->update_version_ui);
        g_free (app->update_details);
        g_free (app->management_plugin);
+       g_free (app->currency);
        g_hash_table_unref (app->metadata);
        g_hash_table_unref (app->addons_hash);
        g_hash_table_unref (app->related_hash);
@@ -2541,6 +2634,30 @@ gs_app_class_init (GsAppClass *klass)
        g_object_class_install_property (object_class, PROP_DESCRIPTION, pspec);
 
        /**
+        * GsApp:price:
+        */
+       pspec = g_param_spec_uint ("price", NULL, NULL,
+                                  0, G_MAXUINT, 0,
+                                  G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_PRICE, pspec);
+
+       /**
+        * GsApp:currency:
+        */
+       pspec = g_param_spec_string ("currency", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_CURRENCY, pspec);
+
+       /**
+        * GsApp:owned:
+        */
+       pspec = g_param_spec_boolean ("owned", NULL, NULL,
+                                     FALSE,
+                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_OWNED, pspec);
+
+       /**
         * GsApp:rating:
         */
        pspec = g_param_spec_int ("rating", NULL, NULL,
diff --git a/src/gs-app.h b/src/gs-app.h
index 0e476d7..d87d7cf 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -195,6 +195,15 @@ const gchar        *gs_app_get_metadata_item       (GsApp          *app,
 void            gs_app_set_metadata            (GsApp          *app,
                                                 const gchar    *key,
                                                 const gchar    *value);
+guint           gs_app_get_price               (GsApp          *app);
+void            gs_app_set_price               (GsApp          *app,
+                                                guint           price);
+const gchar    *gs_app_get_currency            (GsApp          *app);
+void            gs_app_set_currency            (GsApp          *app,
+                                                const gchar    *currency);
+gboolean        gs_app_get_owned               (GsApp          *app);
+void            gs_app_set_owned               (GsApp          *app,
+                                                gboolean        owned);
 gint            gs_app_get_rating              (GsApp          *app);
 void            gs_app_set_rating              (GsApp          *app,
                                                 gint            rating);
diff --git a/src/gs-page.c b/src/gs-page.c
index 5e8d2dc..1051051 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -58,6 +58,41 @@ gs_page_helper_free (GsPageHelper *helper)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
 
 static void
+gs_page_app_bought_cb (GObject *source,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+       g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       GsPage *page = helper->page;
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+
+       ret = gs_plugin_loader_app_action_finish (plugin_loader,
+                                                 res,
+                                                 &error);
+       if (!ret) {
+               g_warning ("failed to purchase %s: %s",
+                          gs_app_get_id (helper->app),
+                          error->message);
+               gs_app_notify_failed_modal (helper->app,
+                                           gs_shell_get_window (priv->shell),
+                                           GS_PLUGIN_LOADER_ACTION_PURCHASE,
+                                           error);
+               return;
+       }
+
+       /* only show this if the window is not active */
+       if (gs_app_get_state (helper->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
+           !gs_shell_is_active (priv->shell))
+               gs_app_notify_installed (helper->app);
+
+       if (GS_PAGE_GET_CLASS (page)->app_installed != NULL)
+               GS_PAGE_GET_CLASS (page)->app_installed (page, helper->app);
+}
+
+static void
 gs_page_app_installed_cb (GObject *source,
                           GAsyncResult *res,
                           gpointer user_data)
@@ -181,6 +216,31 @@ gs_page_set_header_end_widget (GsPage *page, GtkWidget *widget)
 }
 
 void
+gs_page_purchase_app (GsPage *page, GsApp *app)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (page);
+       GsPageHelper *helper;
+       GtkResponseType response;
+
+       /* probably non-free */
+       if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
+               response = gs_app_notify_unavailable (app, gs_shell_get_window (priv->shell));
+               if (response != GTK_RESPONSE_OK)
+                       return;
+       }
+
+       helper = g_slice_new0 (GsPageHelper);
+       helper->app = g_object_ref (app);
+       helper->page = g_object_ref (page);
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          app,
+                                          GS_PLUGIN_LOADER_ACTION_PURCHASE,
+                                          priv->cancellable,
+                                          gs_page_app_bought_cb,
+                                          helper);
+}
+
+void
 gs_page_install_app (GsPage *page, GsApp *app)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
diff --git a/src/gs-page.h b/src/gs-page.h
index b63bd8e..e305ea4 100644
--- a/src/gs-page.h
+++ b/src/gs-page.h
@@ -47,12 +47,15 @@ struct _GsPageClass
 };
 
 GsPage         *gs_page_new                            (void);
+
 GtkWidget      *gs_page_get_header_start_widget        (GsPage         *page);
 void            gs_page_set_header_start_widget        (GsPage         *page,
                                                         GtkWidget      *widget);
 GtkWidget      *gs_page_get_header_end_widget          (GsPage         *page);
 void            gs_page_set_header_end_widget          (GsPage         *page,
                                                         GtkWidget      *widget);
+void            gs_page_purchase_app                   (GsPage         *page,
+                                                        GsApp          *app);
 void            gs_page_install_app                    (GsPage         *page,
                                                         GsApp          *app);
 void            gs_page_remove_app                     (GsPage         *page,
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 010db62..26ee54d 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -53,6 +53,7 @@ typedef enum
 } GsPluginLoaderError;
 
 typedef enum {
+       GS_PLUGIN_LOADER_ACTION_PURCHASE,
        GS_PLUGIN_LOADER_ACTION_INSTALL,
        GS_PLUGIN_LOADER_ACTION_REMOVE,
        GS_PLUGIN_LOADER_ACTION_UPDATE,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index e48a9af..5ad92fb 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -270,6 +270,10 @@ gboolean    gs_plugin_update_cancel                (GsPlugin       *plugin,
                                                         GsApp          *app,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_app_purchase                 (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_plugin_app_install                  (GsPlugin       *plugin,
                                                         GsApp          *app,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 6969862..2cbe276 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -254,7 +254,13 @@ gs_shell_details_switch_to (GsPage *page, gboolean scroll_up)
                gtk_style_context_add_class (gtk_widget_get_style_context (self->button_install), 
"suggested-action");
                /* TRANSLATORS: button text in the header when an application
                 * can be installed */
-               gtk_button_set_label (GTK_BUTTON (self->button_install), _("_Install"));
+               if (gs_app_get_price (self->app) > 0) {
+                       g_autofree gchar *price = NULL;
+                       price = gs_format_price (gs_app_get_currency (self->app), gs_app_get_price 
(self->app));
+                       gtk_button_set_label (GTK_BUTTON (self->button_install), price);
+               } else {
+                       gtk_button_set_label (GTK_BUTTON (self->button_install), _("_Install"));
+               }
                break;
        case AS_APP_STATE_QUEUED_FOR_INSTALL:
                gtk_widget_set_visible (self->button_install, FALSE);
@@ -1307,6 +1313,9 @@ gs_shell_details_file_to_app_cb (GObject *source,
        g_signal_connect_object (self->app, "notify::license",
                                 G_CALLBACK (gs_shell_details_notify_state_changed_cb),
                                 self, 0);
+       g_signal_connect_object (self->app, "notify::owned",
+                                G_CALLBACK (gs_shell_details_notify_state_changed_cb),
+                                self, 0);
        g_signal_connect_object (self->app, "notify::progress",
                                 G_CALLBACK (gs_shell_details_progress_changed_cb),
                                 self, 0);
@@ -1446,6 +1455,11 @@ gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *self)
        GList *l;
        g_autoptr(GList) addons = NULL;
 
+       if (gs_app_get_price (self->app) > 0 && !gs_app_get_owned (self->app)) {
+               gs_page_purchase_app (GS_PAGE (self), self->app);
+               return;
+       }
+
        /* Mark ticked addons to be installed together with the app */
        addons = gtk_container_get_children (GTK_CONTAINER (self->list_box_addons));
        for (l = addons; l; l = l->next) {
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 730afab..a027c8e 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -185,6 +185,11 @@ gs_app_notify_failed_modal (GsApp *app,
                g_string_append_printf (msg, _("Upgrade to %s failed."),
                                        gs_app_get_name (app));
                break;
+       case GS_PLUGIN_LOADER_ACTION_PURCHASE:
+               /* TRANSLATORS: this is when the purchase fails */
+               g_string_append_printf (msg, _("Purchase of %s failed."),
+                                       gs_app_get_name (app));
+               break;
        default:
                g_assert_not_reached ();
                break;
@@ -893,4 +898,30 @@ gs_utils_widget_set_custom_css (GsApp *app, GtkWidget *widget, const gchar *meta
                                g_object_unref);
 }
 
+gchar *
+gs_format_price (const gchar *currency, guint price)
+{
+       if (strcmp (currency, "AUD") == 0) {
+               return g_strdup_printf (_("A$%u"), price);
+       } else if (strcmp (currency, "CAD") == 0) {
+               return g_strdup_printf (_("C$%u"), price);
+       } else if (strcmp (currency, "CNY") == 0) {
+               return g_strdup_printf (_("CN¥%u"), price);
+       } else if (strcmp (currency, "EUR") == 0) {
+               return g_strdup_printf (_("€%u"), price);
+       } else if (strcmp (currency, "GBP") == 0) {
+               return g_strdup_printf (_("£%u"), price);
+       } else if (strcmp (currency, "JPY") == 0) {
+               return g_strdup_printf (_("¥%u"), price);
+       } else if (strcmp (currency, "NZD") == 0) {
+               return g_strdup_printf (_("NZ$%u"), price);
+       } else if (strcmp (currency, "RUB") == 0) {
+               return g_strdup_printf (_("₽%u"), price);
+       } else if (strcmp (currency, "USD") == 0) {
+               return g_strdup_printf (_("US$%u"), price);
+       } else {
+               return g_strdup_printf (_("%s %u"), currency, price);
+       }
+}
+
 /* vim: set noexpandtab: */
diff --git a/src/gs-utils.h b/src/gs-utils.h
index e407bd0..1143d16 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -73,6 +73,9 @@ void           gs_utils_widget_set_custom_css (GsApp          *app,
                                                 GtkWidget      *widget,
                                                 const gchar    *metadata_css);
 
+gchar          *gs_format_price                (const gchar    *currency,
+                                                guint           price);
+
 G_END_DECLS
 
 #endif /* __GS_UTILS_H */
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index e5c0115..3639cd2 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -603,6 +603,20 @@ gs_plugin_update_cancel (GsPlugin *plugin, GsApp *app,
 }
 
 /**
+ * gs_plugin_app_purchase:
+ */
+gboolean
+gs_plugin_app_purchase (GsPlugin *plugin,
+                       GsApp *app,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+       g_debug ("Purchasing app");
+       gs_app_set_owned (app, TRUE);
+       return TRUE;
+}
+
+/**
  * gs_plugin_review_submit:
  */
 gboolean


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]