[gnome-software] Show some UI to remove external sources



commit cb78f6254970104d9e1d88bf4790dbb4a36ce57d
Author: Richard Hughes <richard hughsie com>
Date:   Fri Feb 14 12:12:55 2014 +0000

    Show some UI to remove external sources

 src/app-menu.ui                    |    4 +
 src/gnome-software.ui              |  411 ++++++++++++++++++++++++++++++++++++
 src/gs-app.c                       |    2 +
 src/gs-application.c               |    9 +
 src/gs-cmd.c                       |   10 +-
 src/gs-plugin-loader-sync.c        |   45 ++++
 src/gs-plugin-loader-sync.h        |    4 +
 src/gs-plugin-loader.c             |  117 ++++++++++
 src/gs-plugin-loader.h             |    8 +
 src/gs-plugin.h                    |    4 +
 src/gs-shell.c                     |  362 +++++++++++++++++++++++++++++++
 src/gs-shell.h                     |    1 +
 src/plugins/gs-plugin-packagekit.c |  217 +++++++++++++++++++
 13 files changed, 1193 insertions(+), 1 deletions(-)
---
diff --git a/src/app-menu.ui b/src/app-menu.ui
index 7011906..b8955f8 100644
--- a/src/app-menu.ui
+++ b/src/app-menu.ui
@@ -8,6 +8,10 @@
         <attribute name="action">app.about</attribute>
       </item>
       <item>
+        <attribute name="label" translatable="yes">_Software Sources</attribute>
+        <attribute name="action">app.sources</attribute>
+      </item>
+      <item>
         <attribute name="label" translatable="yes">_Quit</attribute>
         <attribute name="action">app.quit</attribute>
         <attribute name="accel"><![CDATA[<Ctrl>Q]]></attribute>
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index bd8fcb5..d0487a0 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -2353,4 +2353,415 @@
       <widget name="button_folder_fake"/>
     </widgets>
   </object>
+
+  <object class="GtkWindow" id="window_sources">
+    <property name="can_focus">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="transient_for">window_software</property>
+    <property name="default-width">600</property>
+    <property name="default-height">400</property>
+    <property name="title" translatable="yes">Software Sources</property>
+    <child>
+      <object class="GtkBox" id="box_sources_main">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkHeaderBar" id="header_sources">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="show_close_button">True</property>
+
+            <child>
+              <object class="GtkButton" id="button_sources_back">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="button_sources_back_accessible">
+                    <property name="accessible-name" translatable="yes">Go back</property>
+                  </object>
+                </child>
+                <style>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage" id="image_sources_back">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">go-previous-symbolic</property>
+                    <property name="icon_size">1</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child type="title">
+              <object class="GtkLabel" id="label_sources_header">
+                <property name="can_focus">False</property>
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Software Sources</property>
+                <property name="selectable">False</property>
+                <style>
+                  <class name="title"/>
+                </style>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="stack_sources">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkSpinner" id="spinner_sources">
+                <property name="visible">True</property>
+                <property name="width_request">128</property>
+                <property name="height_request">128</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+              </object>
+              <packing>
+                <property name="name">sources-waiting</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box_sources_empty">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="label_sources_empty">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="justify">center</property>
+                    <property name="label" translatable="yes">No sources found.</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="name">sources-empty</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box_sources1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">16</property>
+                <property name="margin_right">16</property>
+                <property name="margin_top">16</property>
+                <property name="margin_bottom">32</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="label_sources1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Software sources give you access to additional 
software. Removing a source will also remove any software you have installed from it.</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">30</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow_sources">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">none</property>
+                    <child>
+                      <object class="GtkListBox" id="listbox_sources">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="selection_mode">none</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="name">sources</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box_sources2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_left">16</property>
+                <property name="margin_right">16</property>
+                <property name="margin_top">16</property>
+                <property name="margin_bottom">16</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="label_sources_none">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">No applications or addons have been installed 
from this source.</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">30</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_sources2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Installed from this Source</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">30</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow_sources_apps">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">none</property>
+                    <child>
+                      <object class="GtkListBox" id="listbox_sources_apps">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="selection_mode">none</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_sources_details">
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Source Details</property>
+                    <property name="wrap">True</property>
+                    <property name="max_width_chars">30</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkGrid" id="grid_sources_details">
+                    <property name="can_focus">False</property>
+                    <property name="halign">center</property>
+                    <property name="valign">start</property>
+                    <property name="row_spacing">3</property>
+                    <property name="column_spacing">24</property>
+                    <property name="row_homogeneous">True</property>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_header_version">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Version</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_version">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label">0.12.3</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_header_lastchecked">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Last Checked</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_header_added">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Added</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_header_website">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Website</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_added">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label">May 12, 2012</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_website">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label">superrepo.com</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">3</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label_sources_lastchecked">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">January 30, 2014</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="button_sources_remove">
+                    <property name="label" translatable="yes">Remove Source</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <style>
+                      <class name="destructive-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">4</property>
+                    <property name="pack_type">end</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="name">source-details</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/src/gs-app.c b/src/gs-app.c
index b17f02d..7d0bf8d 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -523,6 +523,7 @@ gs_app_set_kind (GsApp *app, GsAppKind kind)
                if (kind == GS_APP_KIND_NORMAL ||
                    kind == GS_APP_KIND_SYSTEM ||
                    kind == GS_APP_KIND_CORE ||
+                   kind == GS_APP_KIND_SOURCE ||
                    kind == GS_APP_KIND_UNKNOWN)
                        state_change_ok = TRUE;
                break;
@@ -534,6 +535,7 @@ gs_app_set_kind (GsApp *app, GsAppKind kind)
                break;
        case GS_APP_KIND_SYSTEM:
        case GS_APP_KIND_OS_UPDATE:
+       case GS_APP_KIND_SOURCE:
        case GS_APP_KIND_MISSING:
                /* this can never change state */
                break;
diff --git a/src/gs-application.c b/src/gs-application.c
index bf771f3..496e33d 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -198,6 +198,14 @@ gs_application_initialize_ui (GsApplication *app)
 }
 
 static void
+sources_activated (GSimpleAction *action,
+                  GVariant      *parameter,
+                  gpointer       app)
+{
+       gs_shell_show_sources (GS_APPLICATION (app)->shell);
+}
+
+static void
 about_activated (GSimpleAction *action,
                 GVariant      *parameter,
                 gpointer       app)
@@ -402,6 +410,7 @@ show_offline_updates_error (GSimpleAction *action,
 
 static GActionEntry actions[] = {
        { "about", about_activated, NULL, NULL, NULL },
+       { "sources", sources_activated, NULL, NULL, NULL },
        { "quit", quit_activated, NULL, NULL, NULL },
        { "profile", profile_activated, NULL, NULL, NULL },
        { "set-mode", set_mode_activated, "s", NULL, NULL },
diff --git a/src/gs-cmd.c b/src/gs-cmd.c
index d4c7aa8..6fad84f 100644
--- a/src/gs-cmd.c
+++ b/src/gs-cmd.c
@@ -305,6 +305,13 @@ main (int argc, char **argv)
                                break;
                        }
                }
+       } else if (argc == 2 && g_strcmp0 (argv[1], "sources") == 0) {
+               list = gs_plugin_loader_get_sources (plugin_loader,
+                                                    refine_flags,
+                                                    NULL,
+                                                    &error);
+               if (list == NULL)
+                       ret = FALSE;
        } else if (argc == 2 && g_strcmp0 (argv[1], "popular") == 0) {
                for (i = 0; i < repeat; i++) {
                        if (list != NULL)
@@ -373,7 +380,8 @@ main (int argc, char **argv)
                                     GS_PLUGIN_ERROR_FAILED,
                                     "Did not recognise option, use 'installed', "
                                     "'updates', 'popular', 'get-categories', "
-                                    "'get-category-apps', 'filename-to-app', or 'search'");
+                                    "'get-category-apps', 'filename-to-app', "
+                                    "'sources', or 'search'");
        }
        if (!ret) {
                g_print ("Failed: %s\n", error->message);
diff --git a/src/gs-plugin-loader-sync.c b/src/gs-plugin-loader-sync.c
index 144e7ae..462a2ba 100644
--- a/src/gs-plugin-loader-sync.c
+++ b/src/gs-plugin-loader-sync.c
@@ -193,6 +193,51 @@ gs_plugin_loader_get_updates (GsPluginLoader *plugin_loader,
 }
 
 static void
+gs_plugin_loader_get_sources_finish_sync (GsPluginLoader *plugin_loader,
+                                         GAsyncResult *res,
+                                         GsPluginLoaderHelper *helper)
+{
+       helper->list = gs_plugin_loader_get_sources_finish (plugin_loader,
+                                                           res,
+                                                           helper->error);
+       g_main_loop_quit (helper->loop);
+}
+
+/**
+ * gs_plugin_loader_get_sources:
+ **/
+GList *
+gs_plugin_loader_get_sources (GsPluginLoader *plugin_loader,
+                             GsPluginRefineFlags flags,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GsPluginLoaderHelper helper;
+
+       /* create temp object */
+       helper.context = g_main_context_new ();
+       helper.loop = g_main_loop_new (helper.context, FALSE);
+       helper.error = error;
+
+       g_main_context_push_thread_default (helper.context);
+
+       /* run async method */
+       gs_plugin_loader_get_sources_async (plugin_loader,
+                                           flags,
+                                           cancellable,
+                                           (GAsyncReadyCallback) gs_plugin_loader_get_sources_finish_sync,
+                                           &helper);
+       g_main_loop_run (helper.loop);
+
+       g_main_context_pop_thread_default (helper.context);
+
+       g_main_loop_unref (helper.loop);
+       g_main_context_unref (helper.context);
+
+       return helper.list;
+}
+
+static void
 gs_plugin_loader_get_popular_finish_sync (GsPluginLoader *plugin_loader,
                                          GAsyncResult *res,
                                          GsPluginLoaderHelper *helper)
diff --git a/src/gs-plugin-loader-sync.h b/src/gs-plugin-loader-sync.h
index ec509bf..c600545 100644
--- a/src/gs-plugin-loader-sync.h
+++ b/src/gs-plugin-loader-sync.h
@@ -41,6 +41,10 @@ GList                *gs_plugin_loader_get_updates           (GsPluginLoader 
*plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+GList          *gs_plugin_loader_get_sources           (GsPluginLoader *plugin_loader,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 GList          *gs_plugin_loader_get_popular           (GsPluginLoader *plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 2ff3976..560bcee 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -904,6 +904,123 @@ gs_plugin_loader_get_updates_finish (GsPluginLoader *plugin_loader,
 /******************************************************************************/
 
 /**
+ * gs_plugin_loader_get_sources_thread_cb:
+ **/
+static void
+gs_plugin_loader_get_sources_thread_cb (GSimpleAsyncResult *res,
+                                       GObject *object,
+                                       GCancellable *cancellable)
+{
+       GError *error = NULL;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) g_object_get_data (G_OBJECT 
(cancellable), "state");
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+
+       state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   "gs_plugin_add_sources",
+                                                   state->flags,
+                                                   cancellable,
+                                                   &error);
+       if (state->list == NULL) {
+               gs_plugin_loader_get_all_state_finish (state, error);
+               g_error_free (error);
+               goto out;
+       }
+
+       /* filter package list */
+       gs_plugin_list_filter_duplicates (&state->list);
+
+       /* dedupe applications we already know about */
+       gs_plugin_loader_list_dedupe (plugin_loader, state->list);
+
+       /* none left? */
+       if (state->list == NULL) {
+               g_set_error_literal (&error,
+                                    GS_PLUGIN_LOADER_ERROR,
+                                    GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                    "no sources to show");
+               gs_plugin_loader_get_all_state_finish (state, error);
+               g_error_free (error);
+               goto out;
+       }
+
+       /* success */
+       state->ret = TRUE;
+       gs_plugin_loader_get_all_state_finish (state, NULL);
+out:
+       return;
+}
+
+/**
+ * gs_plugin_loader_get_sources_async:
+ *
+ * This method calls all plugins that implement the gs_plugin_add_sources()
+ * function. The plugins return #GsApp objects of kind %GS_APP_KIND_SOURCE..
+ *
+ * The *applications* installed from each source can be obtained using
+ * gs_app_get_related() if this information is available.
+ **/
+void
+gs_plugin_loader_get_sources_async (GsPluginLoader *plugin_loader,
+                                   GsPluginRefineFlags flags,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+       GCancellable *tmp;
+       GsPluginLoaderAsyncState *state;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->res = g_simple_async_result_new (G_OBJECT (plugin_loader),
+                                               callback,
+                                               user_data,
+                                               gs_plugin_loader_get_sources_async);
+       state->plugin_loader = g_object_ref (plugin_loader);
+       state->flags = flags;
+       if (cancellable != NULL)
+               state->cancellable = g_object_ref (cancellable);
+
+       /* run in a thread */
+       tmp = g_cancellable_new ();
+       g_object_set_data (G_OBJECT (tmp), "state", state);
+       g_simple_async_result_run_in_thread (G_SIMPLE_ASYNC_RESULT (state->res),
+                                            gs_plugin_loader_get_sources_thread_cb,
+                                            0,
+                                            (GCancellable *) tmp);
+       g_object_unref (tmp);
+}
+
+/**
+ * gs_plugin_loader_get_sources_finish:
+ *
+ * Return value: (element-type GsApp) (transfer full): A list of applications
+ **/
+GList *
+gs_plugin_loader_get_sources_finish (GsPluginLoader *plugin_loader,
+                                      GAsyncResult *res,
+                                      GError **error)
+{
+       GSimpleAsyncResult *simple;
+
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+       g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (res), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       /* failed */
+       simple = G_SIMPLE_ASYNC_RESULT (res);
+       if (g_simple_async_result_propagate_error (simple, error))
+               return NULL;
+
+       /* grab detail */
+       return gs_plugin_list_copy (g_simple_async_result_get_op_res_gpointer (simple));
+}
+
+/******************************************************************************/
+
+/**
  * gs_plugin_loader_get_installed_thread_cb:
  **/
 static void
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 302043e..c51f33e 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -94,6 +94,14 @@ void          gs_plugin_loader_get_updates_async     (GsPluginLoader *plugin_loader,
 GList          *gs_plugin_loader_get_updates_finish    (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
+void            gs_plugin_loader_get_sources_async     (GsPluginLoader *plugin_loader,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+GList          *gs_plugin_loader_get_sources_finish    (GsPluginLoader *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
 void            gs_plugin_loader_get_popular_async     (GsPluginLoader *plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 0da5d00..9c23ac7 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -179,6 +179,10 @@ gboolean    gs_plugin_add_updates                  (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_add_sources                  (GsPlugin       *plugin,
+                                                        GList          **list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_plugin_add_updates_historical       (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 8a0b795..53c4dad 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -25,6 +25,7 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
+#include "gs-utils.h"
 #include "gs-shell.h"
 #include "gs-shell-details.h"
 #include "gs-shell-installed.h"
@@ -453,6 +454,176 @@ window_key_press_event (GtkWidget *win, GdkEventKey *event, GsShell *shell)
 }
 
 /**
+ * gs_shell_sources_list_header_func
+ **/
+static void
+gs_shell_sources_list_header_func (GtkListBoxRow *row,
+                                  GtkListBoxRow *before,
+                                  gpointer user_data)
+{
+       GtkWidget *header = NULL;
+       if (before != NULL)
+               header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+       gtk_list_box_row_set_header (row, header);
+}
+
+/**
+ * gs_shell_sources_list_sort_func:
+ **/
+static gint
+gs_shell_sources_list_sort_func (GtkListBoxRow *a,
+                                GtkListBoxRow *b,
+                                gpointer user_data)
+{
+       return a < b;
+}
+
+/**
+ * gs_shell_sources_add_app:
+ **/
+static void
+gs_shell_sources_add_app (GtkListBox *listbox, GsApp *app)
+{
+       GtkWidget *box;
+       GtkWidget *widget;
+
+       box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+       gtk_widget_set_margin_top (box, 12);
+       gtk_widget_set_margin_start (box, 12);
+       gtk_widget_set_margin_bottom (box, 12);
+       gtk_widget_set_margin_end (box, 12);
+
+       widget = gtk_label_new (gs_app_get_name (app));
+       gtk_widget_set_halign (widget, GTK_ALIGN_START);
+       gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+
+       gtk_list_box_prepend (listbox, box);
+       gtk_widget_show (widget);
+       gtk_widget_show (box);
+}
+
+/**
+ * gs_shell_sources_list_row_activated_cb:
+ **/
+static void
+gs_shell_sources_list_row_activated_cb (GtkListBox *list_box,
+                                       GtkListBoxRow *row,
+                                       GsShell *shell)
+{
+       GPtrArray *related;
+       GsApp *app;
+       GsShellPrivate *priv = shell->priv;
+       GtkWidget *widget;
+       guint cnt_apps = 0;
+       guint i;
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       gtk_stack_set_visible_child_name (GTK_STACK (widget), "source-details");
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_back"));
+       gtk_widget_show (widget);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "listbox_sources_apps"));
+       gs_container_remove_all (GTK_CONTAINER (widget));
+       app = GS_APP (g_object_get_data (G_OBJECT (gtk_bin_get_child (GTK_BIN (row))), 
+                                        "GsShell::app"));
+       related = gs_app_get_related (app);
+       for (i = 0; i < related->len; i++) {
+               app = g_ptr_array_index (related, i);
+               switch (gs_app_get_kind (app)) {
+               case GS_APP_KIND_NORMAL:
+               case GS_APP_KIND_SYSTEM:
+                       gs_shell_sources_add_app (GTK_LIST_BOX (widget), app);
+                       cnt_apps++;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       /* save this */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       g_object_set_data_full (G_OBJECT (widget), "GsShell::app",
+                               g_object_ref (app),
+                               (GDestroyNotify) g_object_unref);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "scrolledwindow_sources_apps"));
+       gtk_widget_set_visible (widget, cnt_apps != 0);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "label_sources2"));
+       gtk_widget_set_visible (widget, cnt_apps != 0);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "label_sources_none"));
+       gtk_widget_set_visible (widget, cnt_apps == 0);
+}
+
+/**
+ * gs_shell_sources_back_button_cb:
+ **/
+static void
+gs_shell_sources_back_button_cb (GtkWidget *widget, GsShell *shell)
+{
+       GsShellPrivate *priv = shell->priv;
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_back"));
+       gtk_widget_hide (widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       gtk_stack_set_visible_child_name (GTK_STACK (widget), "sources");
+}
+
+
+/**
+ * gs_shell_sources_app_removed_cb:
+ **/
+static void
+gs_shell_sources_app_removed_cb (GObject *source,
+                                GAsyncResult *res,
+                                gpointer user_data)
+{
+       GError *error = NULL;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       GsShell *shell = GS_SHELL (user_data);
+       GsShellPrivate *priv = shell->priv;
+       GtkWidget *widget;
+       gboolean ret;
+
+       ret = gs_plugin_loader_app_action_finish (plugin_loader,
+                                                 res,
+                                                 &error);
+       if (!ret) {
+               g_warning ("failed to remove: %s", error->message);
+               g_error_free (error);
+       } else {
+               gs_shell_show_sources (shell);
+       }
+
+       /* enable button */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_remove"));
+       gtk_widget_set_sensitive (widget, TRUE);
+}
+
+/**
+ * gs_shell_sources_remove_button_cb:
+ **/
+static void
+gs_shell_sources_remove_button_cb (GtkWidget *widget, GsShell *shell)
+{
+       GsApp *app;
+       GsShellPrivate *priv = shell->priv;
+
+       /* disable button */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_remove"));
+       gtk_widget_set_sensitive (widget, FALSE);
+
+       /* remove source */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       app = GS_APP (g_object_get_data (G_OBJECT (widget), "GsShell::app"));
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          app,
+                                          GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                          priv->cancellable,
+                                          gs_shell_sources_app_removed_cb,
+                                          shell);
+}
+
+/**
  * gs_shell_setup:
  */
 GtkWindow *
@@ -519,6 +690,9 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
                           GINT_TO_POINTER (GS_SHELL_MODE_UPDATES));
        g_signal_connect (widget, "clicked",
                          G_CALLBACK (gs_shell_overview_button_cb), shell);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_remove"));
+       g_signal_connect (widget, "clicked",
+                         G_CALLBACK (gs_shell_sources_remove_button_cb), shell);
 
        gs_shell_overview_setup (priv->shell_overview,
                                 shell,
@@ -564,10 +738,45 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
        g_signal_connect (widget, "notify::text",
                          G_CALLBACK (text_changed_handler), shell);
 
+       /* set up sources */
+       main_window = GTK_WIDGET (gtk_builder_get_object (priv->builder, "window_sources"));
+       g_signal_connect (main_window, "delete-event",
+                         G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "header_sources"));
+       g_object_ref (widget);
+       gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget);
+       gtk_window_set_titlebar (GTK_WINDOW (main_window), widget);
+       g_object_unref (widget);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "listbox_sources"));
+       gtk_list_box_set_header_func (GTK_LIST_BOX (widget),
+                                     gs_shell_sources_list_header_func,
+                                     shell,
+                                     NULL);
+       gtk_list_box_set_sort_func (GTK_LIST_BOX (widget),
+                                   gs_shell_sources_list_sort_func,
+                                   shell, NULL);
+       g_signal_connect (widget, "row-activated",
+                         G_CALLBACK (gs_shell_sources_list_row_activated_cb), shell);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "listbox_sources_apps"));
+       gtk_list_box_set_header_func (GTK_LIST_BOX (widget),
+                                     gs_shell_sources_list_header_func,
+                                     shell,
+                                     NULL);
+       gtk_list_box_set_sort_func (GTK_LIST_BOX (widget),
+                                   gs_shell_sources_list_sort_func,
+                                   shell, NULL);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_back"));
+       g_signal_connect (widget, "clicked",
+                         G_CALLBACK (gs_shell_sources_back_button_cb), shell);
+
        /* load content */
        g_signal_connect (priv->shell_overview, "refreshed",
                          G_CALLBACK (initial_overview_load_done), shell);
 
+       main_window = GTK_WIDGET (gtk_builder_get_object (priv->builder, "window_software"));
        return GTK_WINDOW (main_window);
 }
 
@@ -599,6 +808,159 @@ gs_shell_get_mode (GsShell *shell)
        return priv->mode;
 }
 
+/**
+ * gs_shell_sources_add_source:
+ **/
+static void
+gs_shell_sources_add_source (GtkListBox *listbox, GsApp *app)
+{
+       GsApp *app_tmp;
+       GtkWidget *widget;
+       GtkWidget *box;
+       GtkStyleContext *context;
+       GPtrArray *related;
+       gchar *text;
+       guint cnt_addon = 0;
+       guint cnt_apps = 0;
+       guint i;
+
+       box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+       gtk_widget_set_margin_top (box, 12);
+       gtk_widget_set_margin_start (box, 12);
+       gtk_widget_set_margin_bottom (box, 12);
+       gtk_widget_set_margin_end (box, 12);
+
+       widget = gtk_label_new (gs_app_get_name (app));
+       gtk_widget_set_halign (widget, GTK_ALIGN_START);
+       gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+       related = gs_app_get_related (app);
+
+       /* split up the types */
+       for (i = 0; i < related->len; i++) {
+               app_tmp = g_ptr_array_index (related, i);
+               switch (gs_app_get_id_kind (app_tmp)) {
+               case GS_APP_ID_KIND_WEBAPP:
+               case GS_APP_ID_KIND_DESKTOP:
+                       cnt_apps++;
+                       break;
+               case GS_APP_ID_KIND_FONT:
+               case GS_APP_ID_KIND_CODEC:
+               case GS_APP_ID_KIND_INPUT_METHOD:
+                       cnt_addon++;
+                       break;
+               default:
+                       break;
+               }
+       }
+       if (cnt_apps == 0 && cnt_addon == 0) {
+               /* TRANSLATORS: this source has no apps installed from it */
+               text = g_strdup (_("No applications or addons installed"));
+       } else if (cnt_addon == 0) {
+               /* TRANSLATORS: this source has some apps installed from it */
+               text = g_strdup_printf (ngettext ("%i application installed",
+                                                 "%i applications installed",
+                                                 cnt_apps), cnt_apps);
+       } else if (cnt_apps == 0) {
+               /* TRANSLATORS: this source has some apps installed from it */
+               text = g_strdup_printf (ngettext ("%i addons installed",
+                                                 "%i addons installed",
+                                                 cnt_addon), cnt_addon);
+       } else {
+               /* TRANSLATORS: this source has some apps and addons installed from it */
+               text = g_strdup_printf (ngettext ("%i application installed (and %i addons)",
+                                                 "%i applications installed (and %i addons)",
+                                                 cnt_apps),
+                                       cnt_apps, cnt_addon);
+       }
+       widget = gtk_label_new (text);
+       g_free (text);
+       gtk_widget_set_halign (widget, GTK_ALIGN_START);
+       gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+
+       context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_class (context, "dim-label");
+       g_object_set_data_full (G_OBJECT (box), "GsShell::app",
+                               g_object_ref (app),
+                               (GDestroyNotify) g_object_unref);
+
+       gtk_list_box_prepend (listbox, box);
+       gtk_widget_show_all (box);
+}
+
+/**
+ * gs_shell_sources_get_sources_cb:
+ **/
+static void
+gs_shell_sources_get_sources_cb (GsPluginLoader *plugin_loader,
+                                GAsyncResult *res,
+                                GsShell *shell)
+{
+       GError *error = NULL;
+       GList *l;
+       GList *list;
+       GsApp *app;
+       GtkWidget *widget;
+       GsShellPrivate *priv = shell->priv;
+
+       /* show results */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "spinner_sources"));
+       gtk_spinner_stop (GTK_SPINNER (widget));
+
+       /* get the results */
+       list = gs_plugin_loader_get_updates_finish (plugin_loader, res, &error);
+       if (list == NULL) {
+               if (g_error_matches (error,
+                                    GS_PLUGIN_LOADER_ERROR,
+                                    GS_PLUGIN_LOADER_ERROR_NO_RESULTS)) {
+                       g_debug ("no sources to show");
+               } else {
+                       g_warning ("failed to get sources: %s", error->message);
+               }
+               g_error_free (error);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+               gtk_stack_set_visible_child_name (GTK_STACK (widget), "sources-empty");
+               goto out;
+       }
+
+       /* add each */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       gtk_stack_set_visible_child_name (GTK_STACK (widget), "sources");
+       for (l = list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "listbox_sources"));
+               gs_shell_sources_add_source (GTK_LIST_BOX (widget), app);
+       }
+out:
+       if (list != NULL)
+               gs_plugin_list_free (list);
+}
+
+void
+gs_shell_show_sources (GsShell *shell)
+{
+       GsShellPrivate *priv = shell->priv;
+       GtkWidget *widget;
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "stack_sources"));
+       gtk_stack_set_visible_child_name (GTK_STACK (widget), "sources-waiting");
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "spinner_sources"));
+       gtk_spinner_start (GTK_SPINNER (widget));
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_sources_back"));
+       gtk_widget_hide (widget);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "listbox_sources"));
+       gs_container_remove_all (GTK_CONTAINER (widget));
+
+       /* get the list of non-core software sources */
+       gs_plugin_loader_get_sources_async (priv->plugin_loader,
+                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                           priv->cancellable,
+                                           (GAsyncReadyCallback) gs_shell_sources_get_sources_cb,
+                                           shell);
+
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "window_sources"));
+       gtk_window_present (GTK_WINDOW (widget));
+}
+
 void
 gs_shell_show_app (GsShell *shell, GsApp *app)
 {
diff --git a/src/gs-shell.h b/src/gs-shell.h
index 6d48e01..e9d71ba 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -73,6 +73,7 @@ void           gs_shell_refresh               (GsShell        *shell,
 void            gs_shell_set_mode              (GsShell        *shell,
                                                 GsShellMode     mode);
 GsShellMode     gs_shell_get_mode              (GsShell        *shell);
+void            gs_shell_show_sources          (GsShell        *shell);
 void            gs_shell_show_app              (GsShell        *shell,
                                                 GsApp          *app);
 void            gs_shell_show_category         (GsShell        *shell,
diff --git a/src/plugins/gs-plugin-packagekit.c b/src/plugins/gs-plugin-packagekit.c
index f7ea96a..1ef58b1 100644
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@ -141,6 +141,141 @@ out:
 }
 
 /**
+ * gs_plugin_add_sources_related:
+ */
+static gboolean
+gs_plugin_add_sources_related (GsPlugin *plugin,
+                              GHashTable *hash,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       GList *installed = NULL;
+       GList *l;
+       GsApp *app;
+       GsApp *app_tmp;
+       PkBitfield filter;
+       PkResults *results = NULL;
+       const gchar *id;
+       gboolean ret = TRUE;
+       gchar **split;
+
+       gs_profile_start (plugin->profile, "packagekit::add-sources-related");
+       filter = pk_bitfield_from_enums (PK_FILTER_ENUM_INSTALLED,
+                                        PK_FILTER_ENUM_NEWEST,
+                                        PK_FILTER_ENUM_ARCH,
+                                        PK_FILTER_ENUM_NOT_COLLECTIONS,
+                                        -1);
+       results = pk_client_get_packages (PK_CLIENT(plugin->priv->task),
+                                          filter,
+                                          cancellable,
+                                          gs_plugin_packagekit_progress_cb, plugin,
+                                          error);
+       if (results == NULL) {
+               ret = FALSE;
+               goto out;
+       }
+       ret = gs_plugin_packagekit_add_results (plugin,
+                                               &installed,
+                                               results,
+                                               error);
+       if (!ret)
+               goto out;
+       for (l = installed; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               split = pk_package_id_split (gs_app_get_source_id_default (app));
+               if (g_str_has_prefix (split[PK_PACKAGE_ID_DATA], "installed:")) {
+                       id = split[PK_PACKAGE_ID_DATA] + 10;
+                       app_tmp = g_hash_table_lookup (hash, id);
+                       if (app_tmp != NULL) {
+                               g_debug ("found package %s from %s",
+                                        gs_app_get_source_default (app), id);
+                               gs_app_add_related (app_tmp, app);
+                       }
+               }
+               g_strfreev (split);
+       }
+out:
+       gs_profile_stop (plugin->profile, "packagekit::add-sources-related");
+       gs_plugin_list_free (installed);
+       if (results != NULL)
+               g_object_unref (results);
+       return ret;
+}
+
+/**
+ * gs_plugin_add_sources:
+ */
+gboolean
+gs_plugin_add_sources (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GPtrArray *array = NULL;
+       GsApp *app;
+       PkBitfield filter;
+       PkRepoDetail *rd;
+       PkResults *results;
+       const gchar *id;
+       gboolean ret = TRUE;
+       guint i;
+       GHashTable *hash = NULL;
+
+       /* ask PK for the repo details */
+       filter = pk_bitfield_from_enums (PK_FILTER_ENUM_NOT_SOURCE,
+                                        PK_FILTER_ENUM_NOT_SUPPORTED,
+                                        PK_FILTER_ENUM_INSTALLED,
+                                        -1);
+       results = pk_client_get_repo_list (PK_CLIENT(plugin->priv->task),
+                                          filter,
+                                          cancellable,
+                                          gs_plugin_packagekit_progress_cb, plugin,
+                                          error);
+       if (results == NULL) {
+               ret = FALSE;
+               goto out;
+       }
+       hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+       array = pk_results_get_repo_detail_array (results);
+       for (i = 0; i < array->len; i++) {
+               rd = g_ptr_array_index (array, i);
+#if PK_CHECK_VERSION(0,9,1)
+               id = pk_repo_detail_get_id (rd);
+               /* FIXME: quick hack until we have data */
+               if (g_str_has_prefix (id, "rpmfusion"))
+                       continue;
+               app = gs_app_new (id);
+               gs_app_set_management_plugin (app, "PackageKit");
+               gs_app_set_kind (app, GS_APP_KIND_SOURCE);
+               gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+               gs_app_set_name (app, GS_APP_QUALITY_LOWEST, id);
+               gs_app_set_summary (app,
+                                   GS_APP_QUALITY_LOWEST,
+                                   pk_repo_detail_get_description (rd));
+               gs_plugin_add_app (list, app);
+               g_hash_table_insert (hash,
+                                    g_strdup (id),
+                                    (gpointer) app);
+               g_object_unref (app);
+#endif
+       }
+
+       /* get every application on the system and add it as a related package
+        * if it matches */
+       ret = gs_plugin_add_sources_related (plugin, hash, cancellable, error);
+       if (!ret)
+               goto out;
+out:
+       if (hash != NULL)
+               g_hash_table_unref (hash);
+       if (array != NULL)
+               g_ptr_array_unref (array);
+       if (results != NULL)
+               g_object_unref (results);
+       return ret;
+}
+
+/**
  * gs_plugin_app_install:
  */
 gboolean
@@ -258,6 +393,79 @@ out:
 }
 
 /**
+ * gs_plugin_app_source_disable:
+ */
+static gboolean
+gs_plugin_app_source_disable (GsPlugin *plugin,
+                             GsApp *app,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       gboolean ret = TRUE;
+       PkResults *results;
+
+       /* do sync call */
+       gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING);
+       results = pk_client_repo_enable (PK_CLIENT (plugin->priv->task),
+                                        gs_app_get_id (app),
+                                        FALSE,
+                                        cancellable,
+                                        gs_plugin_packagekit_progress_cb, plugin,
+                                        error);
+       if (results == NULL) {
+               ret = FALSE;
+               goto out;
+       }
+out:
+       if (results != NULL)
+               g_object_unref (results);
+       return ret;
+}
+
+/**
+ * gs_plugin_app_source_remove:
+ */
+static gboolean
+gs_plugin_app_source_remove (GsPlugin *plugin,
+                            GsApp *app,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       gboolean ret = TRUE;
+#if PK_CHECK_VERSION(0,9,1)
+       PkResults *results;
+       GError *error_local = NULL;
+
+       /* do sync call */
+       gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING);
+       results = pk_client_repo_remove (PK_CLIENT (plugin->priv->task),
+                                        pk_bitfield_from_enums (PK_TRANSACTION_FLAG_ENUM_NONE, -1),
+                                        gs_app_get_id (app),
+                                        TRUE,
+                                        cancellable,
+                                        gs_plugin_packagekit_progress_cb, plugin,
+                                        &error_local);
+       if (results == NULL) {
+               /* fall back to disabling it */
+               g_warning ("ignoring source remove error, trying disable: %s",
+                          error_local->message);
+               g_error_free (error_local);
+               ret = gs_plugin_app_source_disable (plugin,
+                                                   app,
+                                                   cancellable,
+                                                   error);
+               goto out;
+       }
+out:
+       if (results != NULL)
+               g_object_unref (results);
+#else
+       ret = gs_plugin_app_source_disable (plugin, app, cancellable, error);
+#endif
+       return ret;
+}
+
+/**
  * gs_plugin_app_remove:
  */
 gboolean
@@ -280,6 +488,15 @@ gs_plugin_app_remove (GsPlugin *plugin,
        if (g_strcmp0 (gs_app_get_management_plugin (app), "PackageKit") != 0)
                goto out;
 
+       /* remove repo and all apps in it */
+       if (gs_app_get_kind (app) == GS_APP_KIND_SOURCE) {
+               ret = gs_plugin_app_source_remove (plugin,
+                                                  app,
+                                                  cancellable,
+                                                  error);
+               goto out;
+       }
+
        /* get the list of available package ids to install */
        source_ids = gs_app_get_source_ids (app);
        if (source_ids->len == 0) {


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