[gnome-photos/wip/rishi/collection: 12/31] Add a new mode for importing items from an attached device



commit f07dc197f75fd30fef5f15b66628d9d086c8945b
Author: Debarshi Ray <debarshir gnome org>
Date:   Tue Jan 23 13:17:16 2018 +0100

    Add a new mode for importing items from an attached device
    
    Based on code written by Petr Štětka.
    
    https://gitlab.gnome.org/GNOME/gnome-photos/issues/29

 src/Makefile.am                        |   4 +
 src/photos-application.c               |  69 ++++++
 src/photos-embed.c                     | 109 ++++++--
 src/photos-empty-results-box.c         |   3 +
 src/photos-item-manager.c              | 118 ++++++++-
 src/photos-item-manager.h              |   1 +
 src/photos-main-toolbar.c              | 101 +++++++-
 src/photos-main-window.c               |   4 +
 src/photos-offset-import-controller.c  | 139 +++++++++++
 src/photos-offset-import-controller.h  |  41 +++
 src/photos-preview-view.c              |   4 +
 src/photos-query.h                     |   5 +-
 src/photos-search-type-manager.c       |   4 +-
 src/photos-selection-toolbar.c         |  18 ++
 src/photos-selection-toolbar.ui        |  13 +
 src/photos-source-manager.c            |  28 ++-
 src/photos-source-notification.c       |  45 ++++
 src/photos-tracker-import-controller.c | 438 +++++++++++++++++++++++++++++++++
 src/photos-tracker-import-controller.h |  41 +++
 src/photos-utils.c                     |   8 +
 src/photos-view-container.c            |   4 +
 21 files changed, 1157 insertions(+), 40 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index d760de82..fbbf7f8b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -150,6 +150,8 @@ gnome_photos_SOURCES = \
        photos-offset-controller.h \
        photos-offset-favorites-controller.c \
        photos-offset-favorites-controller.h \
+       photos-offset-import-controller.c \
+       photos-offset-import-controller.h \
        photos-offset-overview-controller.c \
        photos-offset-overview-controller.h \
        photos-offset-search-controller.c \
@@ -285,6 +287,8 @@ gnome_photos_SOURCES = \
        photos-tracker-controller.h \
        photos-tracker-favorites-controller.c \
        photos-tracker-favorites-controller.h \
+       photos-tracker-import-controller.c \
+       photos-tracker-import-controller.h \
        photos-tracker-overview-controller.c \
        photos-tracker-overview-controller.h \
        photos-tracker-search-controller.c \
diff --git a/src/photos-application.c b/src/photos-application.c
index 18b5d44e..98c483e6 100644
--- a/src/photos-application.c
+++ b/src/photos-application.c
@@ -92,6 +92,8 @@ struct _PhotosApplication
   GSimpleAction *edit_revert_action;
   GSimpleAction *fs_action;
   GSimpleAction *gear_action;
+  GSimpleAction *import_action;
+  GSimpleAction *import_cancel_action;
   GSimpleAction *insta_action;
   GSimpleAction *load_next_action;
   GSimpleAction *load_previous_action;
@@ -352,9 +354,15 @@ photos_application_actions_update (PhotosApplication *self)
         case PHOTOS_WINDOW_MODE_FAVORITES:
         case PHOTOS_WINDOW_MODE_OVERVIEW:
         case PHOTOS_WINDOW_MODE_SEARCH:
+          gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.import-cancel", null_accels);
           gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.selection-mode", 
cancel_accels);
           break;
 
+        case PHOTOS_WINDOW_MODE_IMPORT:
+          gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.import-cancel", cancel_accels);
+          gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.selection-mode", null_accels);
+          break;
+
         case PHOTOS_WINDOW_MODE_NONE:
         case PHOTOS_WINDOW_MODE_EDIT:
         case PHOTOS_WINDOW_MODE_PREVIEW:
@@ -365,6 +373,7 @@ photos_application_actions_update (PhotosApplication *self)
     }
   else
     {
+      gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.import-cancel", null_accels);
       gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.selection-mode", null_accels);
 
       switch (mode)
@@ -372,6 +381,7 @@ photos_application_actions_update (PhotosApplication *self)
         case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
         case PHOTOS_WINDOW_MODE_COLLECTIONS:
         case PHOTOS_WINDOW_MODE_FAVORITES:
+        case PHOTOS_WINDOW_MODE_IMPORT:
         case PHOTOS_WINDOW_MODE_OVERVIEW:
         case PHOTOS_WINDOW_MODE_PREVIEW:
         case PHOTOS_WINDOW_MODE_SEARCH:
@@ -392,6 +402,7 @@ photos_application_actions_update (PhotosApplication *self)
   if (mode == PHOTOS_WINDOW_MODE_COLLECTION_VIEW
       || mode == PHOTOS_WINDOW_MODE_COLLECTIONS
       || mode == PHOTOS_WINDOW_MODE_FAVORITES
+      || mode == PHOTOS_WINDOW_MODE_IMPORT
       || mode == PHOTOS_WINDOW_MODE_OVERVIEW
       || mode == PHOTOS_WINDOW_MODE_SEARCH)
     {
@@ -431,6 +442,7 @@ photos_application_actions_update (PhotosApplication *self)
   enable = ((mode == PHOTOS_WINDOW_MODE_COLLECTION_VIEW
              || mode == PHOTOS_WINDOW_MODE_COLLECTIONS
              || mode == PHOTOS_WINDOW_MODE_FAVORITES
+             || mode == PHOTOS_WINDOW_MODE_IMPORT
              || mode == PHOTOS_WINDOW_MODE_OVERVIEW
              || mode == PHOTOS_WINDOW_MODE_SEARCH)
             && n_items > 0);
@@ -438,6 +450,12 @@ photos_application_actions_update (PhotosApplication *self)
   g_simple_action_set_enabled (self->sel_none_action, enable);
   g_simple_action_set_enabled (self->selection_mode_action, enable);
 
+  enable = (mode == PHOTOS_WINDOW_MODE_IMPORT);
+  g_simple_action_set_enabled (self->import_cancel_action, enable);
+
+  enable = (mode == PHOTOS_WINDOW_MODE_IMPORT && selection != NULL);
+  g_simple_action_set_enabled (self->import_action, enable);
+
   enable = (mode == PHOTOS_WINDOW_MODE_PREVIEW);
   g_simple_action_set_enabled (self->load_next_action, enable);
   g_simple_action_set_enabled (self->load_previous_action, enable);
@@ -771,6 +789,7 @@ photos_application_activate_item (PhotosApplication *self, GObject *item)
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       can_activate = TRUE;
@@ -802,6 +821,7 @@ photos_application_activate_item (PhotosApplication *self, GObject *item)
       break;
 
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
+    case PHOTOS_WINDOW_MODE_IMPORT:
       photos_mode_controller_go_back (self->state->mode_cntrlr);
       break;
 
@@ -1026,6 +1046,35 @@ photos_application_get_state (PhotosSearchContext *context)
 }
 
 
+static void
+photos_application_import (PhotosApplication *self)
+{
+  GMount *mount;
+  PhotosSource *source;
+
+  source = PHOTOS_SOURCE (photos_base_manager_get_active_object (self->state->src_mngr));
+  g_return_if_fail (PHOTOS_IS_SOURCE (source));
+
+  mount = photos_source_get_mount (source);
+  g_return_if_fail (G_IS_MOUNT (mount));
+}
+
+
+static void
+photos_application_import_cancel (PhotosApplication *self)
+{
+  PhotosWindowMode mode;
+
+  photos_base_manager_set_active_object_by_id (self->state->src_mngr, PHOTOS_SOURCE_STOCK_ALL);
+
+  mode = photos_mode_controller_get_window_mode (self->state->mode_cntrlr);
+  g_return_if_fail (mode != PHOTOS_WINDOW_MODE_IMPORT);
+  g_return_if_fail (mode == PHOTOS_WINDOW_MODE_COLLECTIONS
+                    || mode == PHOTOS_WINDOW_MODE_FAVORITES
+                    || mode == PHOTOS_WINDOW_MODE_OVERVIEW);
+}
+
+
 static void
 photos_application_items_changed (PhotosApplication *self)
 {
@@ -1051,6 +1100,7 @@ photos_application_launch_search (PhotosApplication *self, const gchar* const *t
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       can_launch = TRUE;
@@ -1088,6 +1138,10 @@ photos_application_launch_search (PhotosApplication *self, const gchar* const *t
     case PHOTOS_WINDOW_MODE_SEARCH:
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      photos_mode_controller_go_back (self->state->mode_cntrlr);
+      break;
+
     case PHOTOS_WINDOW_MODE_EDIT:
     case PHOTOS_WINDOW_MODE_PREVIEW:
     default:
@@ -1770,6 +1824,7 @@ photos_application_window_mode_changed (PhotosApplication *self, PhotosWindowMod
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       item_mngr_chld = photos_item_manager_get_for_mode (PHOTOS_ITEM_MANAGER (self->state->item_mngr), 
old_mode);
@@ -1788,6 +1843,7 @@ photos_application_window_mode_changed (PhotosApplication *self, PhotosWindowMod
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       item_mngr_chld = photos_item_manager_get_for_mode (PHOTOS_ITEM_MANAGER (self->state->item_mngr), mode);
@@ -2157,6 +2213,17 @@ photos_application_startup (GApplication *application)
   self->gear_action = g_simple_action_new_stateful ("gear-menu", NULL, state);
   g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (self->gear_action));
 
+  self->import_action = g_simple_action_new ("import-current", NULL);
+  g_signal_connect_swapped (self->import_action, "activate", G_CALLBACK (photos_application_import), self);
+  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (self->import_action));
+
+  self->import_cancel_action = g_simple_action_new ("import-cancel", NULL);
+  g_signal_connect_swapped (self->import_cancel_action,
+                            "activate",
+                            G_CALLBACK (photos_application_import_cancel),
+                            self);
+  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (self->import_cancel_action));
+
   self->insta_action = g_simple_action_new ("insta-current", G_VARIANT_TYPE_INT16);
   g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (self->insta_action));
 
@@ -2388,6 +2455,8 @@ photos_application_dispose (GObject *object)
   g_clear_object (&self->edit_revert_action);
   g_clear_object (&self->fs_action);
   g_clear_object (&self->gear_action);
+  g_clear_object (&self->import_action);
+  g_clear_object (&self->import_cancel_action);
   g_clear_object (&self->insta_action);
   g_clear_object (&self->load_next_action);
   g_clear_object (&self->load_previous_action);
diff --git a/src/photos-embed.c b/src/photos-embed.c
index 4cc2c15b..6e65ff69 100644
--- a/src/photos-embed.c
+++ b/src/photos-embed.c
@@ -39,6 +39,7 @@
 #include "photos-selection-toolbar.h"
 #include "photos-spinner-box.h"
 #include "photos-search-context.h"
+#include "photos-search-match.h"
 #include "photos-search-type.h"
 #include "photos-search-type-manager.h"
 #include "photos-source.h"
@@ -70,6 +71,7 @@ struct _PhotosEmbed
   GtkWidget *collection_view;
   GtkWidget *collections;
   GtkWidget *favorites;
+  GtkWidget *import;
   GtkWidget *no_results;
   GtkWidget *ntfctn_mngr;
   GtkWidget *overview;
@@ -155,6 +157,10 @@ photos_embed_get_view_container_from_mode (PhotosEmbed *self, PhotosWindowMode m
       view_container = self->favorites;
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      view_container = self->import;
+      break;
+
     case PHOTOS_WINDOW_MODE_OVERVIEW:
       view_container = self->overview;
       break;
@@ -442,6 +448,7 @@ photos_embed_prepare_for_collection_view (PhotosEmbed *self, PhotosWindowMode ol
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_EDIT:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     default:
       g_assert_not_reached ();
@@ -475,6 +482,17 @@ photos_embed_prepare_for_favorites (PhotosEmbed *self, PhotosWindowMode old_mode
 }
 
 
+static void
+photos_embed_prepare_for_import (PhotosEmbed *self, PhotosWindowMode old_mode)
+{
+  if (old_mode == PHOTOS_WINDOW_MODE_PREVIEW)
+    photos_embed_tracker_controllers_set_frozen (self, FALSE);
+
+  photos_spinner_box_stop (PHOTOS_SPINNER_BOX (self->spinner_box));
+  gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "import");
+}
+
+
 static void
 photos_embed_prepare_for_overview (PhotosEmbed *self, PhotosWindowMode old_mode)
 {
@@ -519,38 +537,83 @@ photos_embed_query_status_changed (PhotosEmbed *self, gboolean querying)
 static void
 photos_embed_search_changed (PhotosEmbed *self)
 {
-  /* Whenever a search constraint is specified, we switch to the
-   * search mode. Search is always global. If we are in
-   * collection-view, we go back to the previous top-level mode and
-   * then enter the search mode.
-   *
-   * When all constraints have been lifted we want to go back to the
-   * previous top-level mode which can be either collections,
-   * favorites or overview.
-   *
-   * The constraints are saved when entering collection-view or
-   * preview from the search mode. They are restored when going back.
-   * Saving and restoring doesn't cause any further mode changes.
-   */
+  GMount *mount;
+  PhotosSource *source;
 
-  self->search_changed = TRUE;
+  source = PHOTOS_SOURCE (photos_base_manager_get_active_object (self->src_mngr));
 
-  if (photos_embed_is_search_constraint_present (self))
+  mount = photos_source_get_mount (source);
+  if (mount != NULL)
     {
+      GObject *object;
       PhotosWindowMode mode;
+      const gchar *search_match_id;
+      const gchar *search_type_id;
 
       mode = photos_mode_controller_get_window_mode (self->mode_cntrlr);
+      g_return_if_fail (mode != PHOTOS_WINDOW_MODE_NONE);
+      g_return_if_fail (mode != PHOTOS_WINDOW_MODE_EDIT);
+      g_return_if_fail (mode != PHOTOS_WINDOW_MODE_IMPORT);
+      g_return_if_fail (mode != PHOTOS_WINDOW_MODE_PREVIEW);
+
+      photos_embed_block_search_changed (self);
+
       if (mode == PHOTOS_WINDOW_MODE_COLLECTION_VIEW)
         photos_mode_controller_go_back (self->mode_cntrlr);
 
-      photos_mode_controller_set_window_mode (self->mode_cntrlr, PHOTOS_WINDOW_MODE_SEARCH);
+      mode = photos_mode_controller_get_window_mode (self->mode_cntrlr);
+      if (mode == PHOTOS_WINDOW_MODE_SEARCH)
+        photos_mode_controller_go_back (self->mode_cntrlr);
+
+      photos_base_manager_set_active_object (self->src_mngr, G_OBJECT (source));
+
+      object = photos_base_manager_get_active_object (self->srch_mtch_mngr);
+      search_match_id = photos_filterable_get_id (PHOTOS_FILTERABLE (object));
+      g_assert_cmpstr (search_match_id, ==, PHOTOS_SEARCH_MATCH_STOCK_ALL);
+
+      object = photos_base_manager_get_active_object (self->srch_typ_mngr);
+      search_type_id = photos_filterable_get_id (PHOTOS_FILTERABLE (object));
+      g_assert_cmpstr (search_type_id, ==, PHOTOS_SEARCH_TYPE_STOCK_ALL);
+
+      photos_mode_controller_set_window_mode (self->mode_cntrlr, PHOTOS_WINDOW_MODE_IMPORT);
+
+      photos_embed_unblock_search_changed (self);
     }
   else
     {
-      photos_mode_controller_go_back (self->mode_cntrlr);
-    }
+      /* Whenever a search constraint is specified, we switch to the
+       * search mode. Search is always global. If we are in
+       * collection-view, we go back to the previous top-level mode and
+       * then enter the search mode.
+       *
+       * When all constraints have been lifted we want to go back to the
+       * previous top-level mode which can be either collections,
+       * favorites or overview.
+       *
+       * The constraints are saved when entering collection-view or
+       * preview from the search mode. They are restored when going back.
+       * Saving and restoring doesn't cause any further mode changes.
+       */
+
+      self->search_changed = TRUE;
+
+      if (photos_embed_is_search_constraint_present (self))
+        {
+          PhotosWindowMode mode;
 
-  self->search_changed = FALSE;
+          mode = photos_mode_controller_get_window_mode (self->mode_cntrlr);
+          if (mode == PHOTOS_WINDOW_MODE_COLLECTION_VIEW)
+            photos_mode_controller_go_back (self->mode_cntrlr);
+
+          photos_mode_controller_set_window_mode (self->mode_cntrlr, PHOTOS_WINDOW_MODE_SEARCH);
+        }
+      else
+        {
+          photos_mode_controller_go_back (self->mode_cntrlr);
+        }
+
+      self->search_changed = FALSE;
+    }
 }
 
 
@@ -645,6 +708,7 @@ photos_embed_window_mode_changed (PhotosModeController *mode_cntrlr,
 
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_EDIT:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_PREVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       break;
@@ -674,6 +738,10 @@ photos_embed_window_mode_changed (PhotosModeController *mode_cntrlr,
       photos_embed_prepare_for_favorites (self, old_mode);
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      photos_embed_prepare_for_import (self, old_mode);
+      break;
+
     case PHOTOS_WINDOW_MODE_OVERVIEW:
       photos_embed_prepare_for_overview (self, old_mode);
       break;
@@ -765,6 +833,9 @@ photos_embed_init (PhotosEmbed *self)
   name = photos_view_container_get_name (PHOTOS_VIEW_CONTAINER (self->favorites));
   gtk_stack_add_titled (GTK_STACK (self->stack), self->favorites, "favorites", name);
 
+  self->import = photos_view_container_new (PHOTOS_WINDOW_MODE_IMPORT, _("Import"));
+  gtk_stack_add_named (GTK_STACK (self->stack), self->import, "import");
+
   self->search = photos_view_container_new (PHOTOS_WINDOW_MODE_SEARCH, _("Search"));
   gtk_stack_add_named (GTK_STACK (self->stack), self->search, "search");
 
diff --git a/src/photos-empty-results-box.c b/src/photos-empty-results-box.c
index 9db63a1c..93123a14 100644
--- a/src/photos-empty-results-box.c
+++ b/src/photos-empty-results-box.c
@@ -111,6 +111,7 @@ photos_empty_results_box_add_image (PhotosEmptyResultsBox *self)
      * the relevant locations.
      */
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
       icon_name = "camera-photo-symbolic";
       break;
@@ -155,6 +156,7 @@ photos_empty_results_box_add_primary_label (PhotosEmptyResultsBox *self)
      * the relevant locations.
      */
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       text = _("No photos found");
@@ -194,6 +196,7 @@ photos_empty_results_box_add_secondary_label (PhotosEmptyResultsBox *self)
      */
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
       break;
 
     case PHOTOS_WINDOW_MODE_OVERVIEW:
diff --git a/src/photos-item-manager.c b/src/photos-item-manager.c
index b0c3f7bd..18136838 100644
--- a/src/photos-item-manager.c
+++ b/src/photos-item-manager.c
@@ -31,6 +31,7 @@
 
 #include "egg-counter.h"
 #include "photos-debug.h"
+#include "photos-device-item.h"
 #include "photos-enums.h"
 #include "photos-filterable.h"
 #include "photos-item-manager.h"
@@ -241,6 +242,7 @@ static void
 photos_item_manager_info_updated (PhotosBaseItem *item, gpointer user_data)
 {
   PhotosItemManager *self = PHOTOS_ITEM_MANAGER (user_data);
+  GType base_item_type;
   PhotosBaseItem *updated_item;
   gboolean is_collection;
   gboolean is_favorite;
@@ -252,6 +254,10 @@ photos_item_manager_info_updated (PhotosBaseItem *item, gpointer user_data)
   updated_item = PHOTOS_BASE_ITEM (photos_base_manager_get_object_by_id (PHOTOS_BASE_MANAGER (self), id));
   g_return_if_fail (updated_item == item);
 
+  base_item_type = G_OBJECT_TYPE (item);
+  if (base_item_type == PHOTOS_TYPE_DEVICE_ITEM)
+    goto out;
+
   is_collection = photos_base_item_is_collection (item);
   is_favorite = photos_base_item_is_favorite (item);
 
@@ -281,6 +287,9 @@ photos_item_manager_info_updated (PhotosBaseItem *item, gpointer user_data)
       if (!is_favorite)
         photos_base_manager_remove_object (self->item_mngr_chldrn[PHOTOS_WINDOW_MODE_FAVORITES], G_OBJECT 
(item));
     }
+
+ out:
+  return;
 }
 
 
@@ -374,6 +383,34 @@ photos_item_manager_check_wait_for_changes (PhotosItemManager *self, const gchar
 }
 
 
+static void
+photos_item_manager_item_created_executed_import (GObject *source_object, GAsyncResult *res, gpointer 
user_data)
+{
+  g_autoptr (PhotosItemManager) self = PHOTOS_ITEM_MANAGER (user_data);
+  PhotosSingleItemJob *job_import = PHOTOS_SINGLE_ITEM_JOB (source_object);
+  TrackerSparqlCursor *cursor = NULL; /* TODO: use g_autoptr */
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    cursor = photos_single_item_job_finish (job_import, res, &error);
+    if (error != NULL)
+      {
+        g_warning ("Unable to query single item: %s", error->message);
+        goto out;
+      }
+  }
+
+  if (cursor == NULL)
+    goto out;
+
+  photos_item_manager_add_item_for_mode (self, PHOTOS_TYPE_DEVICE_ITEM, PHOTOS_WINDOW_MODE_IMPORT, cursor);
+
+ out:
+  g_clear_object (&cursor);
+}
+
+
 static void
 photos_item_manager_item_created_executed_overview (GObject *source_object, GAsyncResult *res, gpointer 
user_data)
 {
@@ -447,6 +484,7 @@ photos_item_manager_item_created (PhotosItemManager *self, const gchar *urn)
   GApplication *app;
   PhotosItemManagerHiddenItem *old_hidden_item;
   PhotosSearchContextState *state;
+  g_autoptr (PhotosSingleItemJob) job_import = NULL;
   g_autoptr (PhotosSingleItemJob) job_overview = NULL;
   guint wait_for_changes_size;
 
@@ -456,6 +494,14 @@ photos_item_manager_item_created (PhotosItemManager *self, const gchar *urn)
   app = g_application_get_default ();
   state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
 
+  job_import = photos_single_item_job_new (urn);
+  photos_single_item_job_run (job_import,
+                              state,
+                              PHOTOS_QUERY_FLAGS_IMPORT,
+                              NULL,
+                              photos_item_manager_item_created_executed_import,
+                              g_object_ref (self));
+
   job_overview = photos_single_item_job_new (urn);
   photos_single_item_job_run (job_overview,
                               state,
@@ -875,6 +921,7 @@ photos_item_manager_set_active_object (PhotosBaseManager *manager, GObject *obje
   g_return_val_if_fail (object != NULL, FALSE);
   g_return_val_if_fail (PHOTOS_IS_BASE_ITEM (object), FALSE);
   g_return_val_if_fail (self->mode != PHOTOS_WINDOW_MODE_EDIT, FALSE);
+  g_return_val_if_fail (self->mode != PHOTOS_WINDOW_MODE_IMPORT, FALSE);
 
   is_collection = photos_base_item_is_collection (PHOTOS_BASE_ITEM (object));
   if (is_collection)
@@ -1630,14 +1677,20 @@ photos_mode_controller_get_window_mode (PhotosModeController *self)
 void
 photos_mode_controller_go_back (PhotosModeController *self)
 {
+  GAction *selection_mode_action;
+  GApplication *app;
   PhotosWindowMode old_mode;
   PhotosWindowMode tmp;
   gboolean active_changed = FALSE;
   gboolean active_collection_changed = FALSE;
+  gboolean unset_selection_mode = FALSE;
 
   g_return_if_fail (PHOTOS_IS_MODE_CONTROLLER (self));
   g_return_if_fail (!g_queue_is_empty (self->history));
 
+  app = g_application_get_default ();
+  selection_mode_action = g_action_map_lookup_action (G_ACTION_MAP (app), "selection-mode");
+
   old_mode = (PhotosWindowMode) GPOINTER_TO_INT (g_queue_peek_head (self->history));
   g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_NONE);
 
@@ -1662,6 +1715,20 @@ photos_mode_controller_go_back (PhotosModeController *self)
       g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_PREVIEW);
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      {
+        g_autoptr (GVariant) state = NULL;
+
+        g_return_if_fail (old_mode == PHOTOS_WINDOW_MODE_COLLECTIONS
+                          || old_mode == PHOTOS_WINDOW_MODE_FAVORITES
+                          || old_mode == PHOTOS_WINDOW_MODE_OVERVIEW);
+
+        state = g_action_get_state (selection_mode_action);
+        g_return_if_fail (state != NULL);
+        g_return_if_fail (g_variant_get_boolean (state));
+        break;
+      }
+
     case PHOTOS_WINDOW_MODE_PREVIEW:
       g_return_if_fail (PHOTOS_IS_BASE_ITEM (self->active_object));
       g_return_if_fail (self->active_object != (GObject *) self->active_collection);
@@ -1698,6 +1765,10 @@ photos_mode_controller_go_back (PhotosModeController *self)
     case PHOTOS_WINDOW_MODE_EDIT:
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      unset_selection_mode = TRUE;
+      break;
+
     case PHOTOS_WINDOW_MODE_PREVIEW:
       self->load_state = PHOTOS_LOAD_STATE_NONE;
       g_set_object (&self->active_object, G_OBJECT (self->active_collection));
@@ -1726,6 +1797,14 @@ photos_mode_controller_go_back (PhotosModeController *self)
       break;
     }
 
+  if (unset_selection_mode)
+    {
+      GVariant *state;
+
+      state = g_variant_new_boolean (FALSE);
+      g_action_change_state (selection_mode_action, state);
+    }
+
   if (active_changed)
     g_signal_emit_by_name (self, "active-changed", self->active_object);
 
@@ -1760,19 +1839,38 @@ photos_mode_controller_set_fullscreen (PhotosModeController *self, gboolean full
 void
 photos_mode_controller_set_window_mode (PhotosModeController *self, PhotosWindowMode mode)
 {
+  GAction *selection_mode_action;
+  GApplication *app;
   PhotosWindowMode old_mode;
+  gboolean active_changed = FALSE;
   gboolean active_collection_changed = FALSE;
+  gboolean set_selection_mode = FALSE;
 
   g_return_if_fail (PHOTOS_IS_MODE_CONTROLLER (self));
   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_NONE);
   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_COLLECTION_VIEW);
   g_return_if_fail (mode != PHOTOS_WINDOW_MODE_PREVIEW);
 
+  app = g_application_get_default ();
+  selection_mode_action = g_action_map_lookup_action (G_ACTION_MAP (app), "selection-mode");
+
   if (mode == PHOTOS_WINDOW_MODE_EDIT)
     {
       g_return_if_fail (self->load_state == PHOTOS_LOAD_STATE_FINISHED);
       g_return_if_fail (self->mode == PHOTOS_WINDOW_MODE_PREVIEW);
     }
+  else if (mode == PHOTOS_WINDOW_MODE_IMPORT)
+    {
+      g_autoptr (GVariant) state = NULL;
+
+      g_return_if_fail (self->mode == PHOTOS_WINDOW_MODE_COLLECTIONS
+                        || self->mode == PHOTOS_WINDOW_MODE_FAVORITES
+                        || self->mode == PHOTOS_WINDOW_MODE_OVERVIEW);
+
+      state = g_action_get_state (selection_mode_action);
+      g_return_if_fail (state != NULL);
+      g_return_if_fail (!g_variant_get_boolean (state));
+    }
   else
     {
       g_return_if_fail (self->mode != PHOTOS_WINDOW_MODE_EDIT);
@@ -1796,11 +1894,25 @@ photos_mode_controller_set_window_mode (PhotosModeController *self, PhotosWindow
         }
 
       g_clear_object (&self->active_object);
-      g_signal_emit_by_name (self, "active-changed", self->active_object);
+      active_changed = TRUE;
+    }
+
+  if (mode == PHOTOS_WINDOW_MODE_IMPORT)
+    set_selection_mode = TRUE;
 
-      if (active_collection_changed)
-        g_signal_emit (self, signals[ACTIVE_COLLECTION_CHANGED], 0, self->active_collection);
+  if (set_selection_mode)
+    {
+      GVariant *state;
+
+      state = g_variant_new_boolean (TRUE);
+      g_action_change_state (selection_mode_action, state);
     }
 
+  if (active_changed)
+    g_signal_emit_by_name (self, "active-changed", self->active_object);
+
+  if (active_collection_changed)
+    g_signal_emit (self, signals[ACTIVE_COLLECTION_CHANGED], 0, self->active_collection);
+
   g_signal_emit (self, signals[WINDOW_MODE_CHANGED], 0, mode, old_mode);
 }
diff --git a/src/photos-item-manager.h b/src/photos-item-manager.h
index eb8d4106..df2e5c1f 100644
--- a/src/photos-item-manager.h
+++ b/src/photos-item-manager.h
@@ -60,6 +60,7 @@ typedef enum
   PHOTOS_WINDOW_MODE_COLLECTIONS,
   PHOTOS_WINDOW_MODE_EDIT,
   PHOTOS_WINDOW_MODE_FAVORITES,
+  PHOTOS_WINDOW_MODE_IMPORT,
   PHOTOS_WINDOW_MODE_OVERVIEW,
   PHOTOS_WINDOW_MODE_PREVIEW,
   PHOTOS_WINDOW_MODE_SEARCH
diff --git a/src/photos-main-toolbar.c b/src/photos-main-toolbar.c
index c4fd549c..a541be02 100644
--- a/src/photos-main-toolbar.c
+++ b/src/photos-main-toolbar.c
@@ -28,6 +28,7 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
+#include "photos-base-manager.h"
 #include "photos-dlna-renderers-manager.h"
 #include "photos-dropdown.h"
 #include "photos-item-manager.h"
@@ -52,6 +53,7 @@ struct _PhotosMainToolbar
   GtkWidget *selection_menu;
   GtkWidget *stack_switcher;
   PhotosBaseManager *item_mngr;
+  PhotosBaseManager *src_mngr;
   PhotosModeController *mode_cntrlr;
   PhotosRemoteDisplayManager *remote_mngr;
   PhotosSelectionController *sel_cntrlr;
@@ -65,7 +67,9 @@ static void photos_main_toolbar_favorite_button_update (PhotosMainToolbar *self,
 
 
 static gchar *
-photos_main_toolbar_create_selection_mode_label (PhotosMainToolbar *self, PhotosBaseItem *active_collection)
+photos_main_toolbar_create_selection_mode_label (PhotosMainToolbar *self,
+                                                 PhotosBaseItem *active_collection,
+                                                 PhotosSource *source)
 {
   GList *selection;
   g_autofree gchar *label = NULL;
@@ -80,9 +84,20 @@ photos_main_toolbar_create_selection_mode_label (PhotosMainToolbar *self, Photos
     label = g_strdup_printf (ngettext ("%d selected", "%d selected", length), length);
 
   if (active_collection != NULL)
-    ret_val = g_markup_printf_escaped ("<b>%s</b> (%s)", photos_base_item_get_name (active_collection), 
label);
+    {
+      ret_val = g_markup_printf_escaped ("<b>%s</b> (%s)", photos_base_item_get_name (active_collection), 
label);
+    }
+  else if (source != NULL)
+    {
+      const gchar *name;
+
+      name = photos_source_get_name (source);
+      ret_val = g_markup_printf_escaped ("<b>%s</b> (%s)", name, label);
+    }
   else
-    ret_val = g_steal_pointer (&label);
+    {
+      ret_val = g_steal_pointer (&label);
+    }
 
   return ret_val;
 }
@@ -92,6 +107,7 @@ static void
 photos_main_toolbar_set_toolbar_title (PhotosMainToolbar *self)
 {
   PhotosBaseItem *active_collection;
+  PhotosSource *source;
   PhotosWindowMode window_mode;
   gboolean selection_mode;
   g_autofree gchar *primary = NULL;
@@ -104,6 +120,7 @@ photos_main_toolbar_set_toolbar_title (PhotosMainToolbar *self)
       g_return_if_fail (window_mode == PHOTOS_WINDOW_MODE_COLLECTION_VIEW
                         || window_mode == PHOTOS_WINDOW_MODE_COLLECTIONS
                         || window_mode == PHOTOS_WINDOW_MODE_FAVORITES
+                        || window_mode == PHOTOS_WINDOW_MODE_IMPORT
                         || window_mode == PHOTOS_WINDOW_MODE_OVERVIEW
                         || window_mode == PHOTOS_WINDOW_MODE_SEARCH);
     }
@@ -112,12 +129,24 @@ photos_main_toolbar_set_toolbar_title (PhotosMainToolbar *self)
     g_return_if_fail (!selection_mode);
 
   active_collection = photos_item_manager_get_active_collection (PHOTOS_ITEM_MANAGER (self->item_mngr));
+  source = PHOTOS_SOURCE (photos_base_manager_get_active_object (self->src_mngr));
+
+  if (window_mode == PHOTOS_WINDOW_MODE_IMPORT)
+    {
+      GMount *mount;
+
+      g_return_if_fail (active_collection == NULL);
+      g_return_if_fail (PHOTOS_IS_SOURCE (source));
+
+      mount = photos_source_get_mount (source);
+      g_return_if_fail (G_IS_MOUNT (mount));
+    }
 
   switch (window_mode)
     {
     case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
       if (selection_mode)
-        primary = photos_main_toolbar_create_selection_mode_label (self, active_collection);
+        primary = photos_main_toolbar_create_selection_mode_label (self, active_collection, NULL);
       else
         primary = g_strdup (photos_base_item_get_name (active_collection));
       break;
@@ -127,7 +156,7 @@ photos_main_toolbar_set_toolbar_title (PhotosMainToolbar *self)
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       if (selection_mode)
-        primary = photos_main_toolbar_create_selection_mode_label (self, NULL);
+        primary = photos_main_toolbar_create_selection_mode_label (self, NULL, NULL);
       break;
 
     case PHOTOS_WINDOW_MODE_EDIT:
@@ -142,6 +171,10 @@ photos_main_toolbar_set_toolbar_title (PhotosMainToolbar *self)
         break;
       }
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      primary = photos_main_toolbar_create_selection_mode_label (self, NULL, source);
+      break;
+
     case PHOTOS_WINDOW_MODE_NONE:
     default:
       break;
@@ -450,6 +483,28 @@ photos_main_toolbar_populate_for_favorites (PhotosMainToolbar *self)
 }
 
 
+static void
+photos_main_toolbar_populate_for_import (PhotosMainToolbar *self)
+{
+  GtkStyleContext *context;
+  GtkWidget *cancel_button;
+
+  gtk_header_bar_set_custom_title (GTK_HEADER_BAR (self->header_bar), self->selection_menu);
+  context = gtk_widget_get_style_context (self->header_bar);
+  gtk_style_context_add_class (context, "selection-mode");
+
+  cancel_button = gtk_button_new_with_label (_("Cancel"));
+  gtk_actionable_set_action_name (GTK_ACTIONABLE (cancel_button), "app.import-cancel");
+  gtk_header_bar_pack_end (GTK_HEADER_BAR (self->header_bar), cancel_button);
+
+  g_signal_connect_object (self->sel_cntrlr,
+                           "selection-changed",
+                           G_CALLBACK (photos_main_toolbar_set_toolbar_title),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+
 static void
 photos_main_toolbar_populate_for_overview (PhotosMainToolbar *self)
 {
@@ -582,6 +637,7 @@ photos_main_toolbar_dispose (GObject *object)
   g_clear_object (&self->remote_mngr);
   g_clear_object (&self->sel_cntrlr);
   g_clear_object (&self->selection_menu);
+  g_clear_object (&self->src_mngr);
   g_clear_object (&self->stack_switcher);
 
   G_OBJECT_CLASS (photos_main_toolbar_parent_class)->dispose (object);
@@ -652,6 +708,7 @@ photos_main_toolbar_init (PhotosMainToolbar *self)
   self->item_mngr = g_object_ref (state->item_mngr);
   self->mode_cntrlr = g_object_ref (state->mode_cntrlr);
   self->sel_cntrlr = photos_selection_controller_dup_singleton ();
+  self->src_mngr = g_object_ref (state->src_mngr);
 
   self->remote_mngr = photos_remote_display_manager_dup_singleton ();
   g_signal_connect_object (self->remote_mngr,
@@ -726,21 +783,44 @@ photos_main_toolbar_is_focus (PhotosMainToolbar *self)
 void
 photos_main_toolbar_reset_toolbar_mode (PhotosMainToolbar *self)
 {
+  PhotosWindowMode window_mode;
   gboolean selection_mode;
 
   photos_main_toolbar_clear_toolbar (self);
+  window_mode = photos_mode_controller_get_window_mode (self->mode_cntrlr);
   selection_mode = photos_utils_get_selection_mode ();
 
   if (selection_mode)
-    photos_main_toolbar_populate_for_selection_mode (self);
-  else
     {
-      PhotosWindowMode window_mode;
+      switch (window_mode)
+        {
+        case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
+        case PHOTOS_WINDOW_MODE_COLLECTIONS:
+        case PHOTOS_WINDOW_MODE_FAVORITES:
+        case PHOTOS_WINDOW_MODE_OVERVIEW:
+        case PHOTOS_WINDOW_MODE_SEARCH:
+          photos_main_toolbar_populate_for_selection_mode (self);
+          break;
 
-      window_mode = photos_mode_controller_get_window_mode (self->mode_cntrlr);
+        case PHOTOS_WINDOW_MODE_IMPORT:
+          photos_main_toolbar_populate_for_import (self);
+          break;
 
+        case PHOTOS_WINDOW_MODE_NONE:
+        case PHOTOS_WINDOW_MODE_EDIT:
+        case PHOTOS_WINDOW_MODE_PREVIEW:
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  else
+    {
       switch (window_mode)
         {
+        case PHOTOS_WINDOW_MODE_NONE:
+          break;
+
         case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
           photos_main_toolbar_populate_for_collection_view (self);
           break;
@@ -769,8 +849,9 @@ photos_main_toolbar_reset_toolbar_mode (PhotosMainToolbar *self)
           photos_main_toolbar_populate_for_search (self);
           break;
 
-        case PHOTOS_WINDOW_MODE_NONE:
+        case PHOTOS_WINDOW_MODE_IMPORT:
         default:
+          g_assert_not_reached ();
           break;
         }
     }
diff --git a/src/photos-main-window.c b/src/photos-main-window.c
index 2a884a8f..94b06650 100644
--- a/src/photos-main-window.c
+++ b/src/photos-main-window.c
@@ -41,6 +41,7 @@ struct _PhotosMainWindow
 {
   GtkApplicationWindow parent_instance;
   GAction *edit_cancel;
+  GAction *import_cancel;
   GAction *load_next;
   GAction *load_previous;
   GtkWidget *embed;
@@ -166,6 +167,7 @@ photos_main_window_go_back (PhotosMainWindow *self)
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_EDIT:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
     default:
@@ -262,6 +264,7 @@ photos_main_window_key_press_event (GtkWidget *widget, GdkEventKey *event)
     case PHOTOS_WINDOW_MODE_COLLECTIONS:
     case PHOTOS_WINDOW_MODE_EDIT:
     case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_IMPORT:
     case PHOTOS_WINDOW_MODE_OVERVIEW:
     case PHOTOS_WINDOW_MODE_SEARCH:
       handled = GDK_EVENT_PROPAGATE;
@@ -332,6 +335,7 @@ photos_main_window_constructed (GObject *object)
   gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (self));
 
   self->edit_cancel = g_action_map_lookup_action (G_ACTION_MAP (app), "edit-cancel");
+  self->import_cancel = g_action_map_lookup_action (G_ACTION_MAP (app), "import-cancel");
   self->load_next = g_action_map_lookup_action (G_ACTION_MAP (app), "load-next");
   self->load_previous = g_action_map_lookup_action (G_ACTION_MAP (app), "load-previous");
 
diff --git a/src/photos-offset-import-controller.c b/src/photos-offset-import-controller.c
new file mode 100644
index 00000000..37d58c1c
--- /dev/null
+++ b/src/photos-offset-import-controller.c
@@ -0,0 +1,139 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2017 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+/* Based on code from:
+ *   + Documents
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "photos-base-manager.h"
+#include "photos-filterable.h"
+#include "photos-offset-import-controller.h"
+#include "photos-query.h"
+#include "photos-query-builder.h"
+#include "photos-search-context.h"
+#include "photos-source.h"
+
+
+struct _PhotosOffsetImportController
+{
+  PhotosOffsetController parent_instance;
+  PhotosBaseManager *src_mngr;
+};
+
+
+G_DEFINE_TYPE (PhotosOffsetImportController, photos_offset_import_controller, PHOTOS_TYPE_OFFSET_CONTROLLER);
+
+
+static PhotosQuery *
+photos_offset_import_controller_get_query (PhotosOffsetController *offset_cntrlr)
+{
+  PhotosOffsetImportController *self = PHOTOS_OFFSET_IMPORT_CONTROLLER (offset_cntrlr);
+  GApplication *app;
+  GMount *mount;
+  g_autoptr (PhotosQuery) query = NULL;
+  PhotosQuery *ret_val = NULL;
+  PhotosSearchContextState *state;
+  PhotosSource *source;
+  const gchar *id;
+
+  source = PHOTOS_SOURCE (photos_base_manager_get_active_object (self->src_mngr));
+  g_return_val_if_fail (PHOTOS_IS_SOURCE (source), NULL);
+
+  id = photos_filterable_get_id (PHOTOS_FILTERABLE (source));
+  mount = photos_source_get_mount (source);
+  g_return_val_if_fail (g_strcmp0 (id, PHOTOS_SOURCE_STOCK_ALL) == 0 || G_IS_MOUNT (mount), NULL);
+
+  app = g_application_get_default ();
+  state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
+
+  if (mount != NULL)
+    query = photos_query_builder_count_query (state, PHOTOS_QUERY_FLAGS_IMPORT);
+  else
+    query = photos_query_new (state, "SELECT DISTINCT COUNT (?urn) WHERE { ?urn a rdfs:Resource FILTER 
(false) }");
+
+  ret_val = g_steal_pointer (&query);
+  return ret_val;
+}
+
+
+static GObject *
+photos_offset_import_controller_constructor (GType type,
+                                             guint n_construct_params,
+                                             GObjectConstructParam *construct_params)
+{
+  static GObject *self = NULL;
+
+  if (self == NULL)
+    {
+      self = G_OBJECT_CLASS (photos_offset_import_controller_parent_class)->constructor (type,
+                                                                                         n_construct_params,
+                                                                                         construct_params);
+      g_object_add_weak_pointer (self, (gpointer) &self);
+      return self;
+    }
+
+  return g_object_ref (self);
+}
+
+
+static void
+photos_offset_import_controller_dispose (GObject *object)
+{
+  PhotosOffsetImportController *self = PHOTOS_OFFSET_IMPORT_CONTROLLER (object);
+
+  g_clear_object (&self->src_mngr);
+
+  G_OBJECT_CLASS (photos_offset_import_controller_parent_class)->dispose (object);
+}
+
+
+static void
+photos_offset_import_controller_init (PhotosOffsetImportController *self)
+{
+  GApplication *app;
+  PhotosSearchContextState *state;
+
+  app = g_application_get_default ();
+  state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
+
+  self->src_mngr = g_object_ref (state->src_mngr);
+}
+
+
+static void
+photos_offset_import_controller_class_init (PhotosOffsetImportControllerClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  PhotosOffsetControllerClass *offset_controller_class = PHOTOS_OFFSET_CONTROLLER_CLASS (class);
+
+  object_class->constructor = photos_offset_import_controller_constructor;
+  object_class->dispose = photos_offset_import_controller_dispose;
+  offset_controller_class->get_query = photos_offset_import_controller_get_query;
+}
+
+
+PhotosOffsetController *
+photos_offset_import_controller_dup_singleton (void)
+{
+  return g_object_new (PHOTOS_TYPE_OFFSET_IMPORT_CONTROLLER, NULL);
+}
diff --git a/src/photos-offset-import-controller.h b/src/photos-offset-import-controller.h
new file mode 100644
index 00000000..c4623434
--- /dev/null
+++ b/src/photos-offset-import-controller.h
@@ -0,0 +1,41 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2017 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+/* Based on code from:
+ *   + Documents
+ */
+
+#ifndef PHOTOS_OFFSET_IMPORT_CONTROLLER_H
+#define PHOTOS_OFFSET_IMPORT_CONTROLLER_H
+
+#include "photos-offset-controller.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_OFFSET_IMPORT_CONTROLLER (photos_offset_import_controller_get_type ())
+G_DECLARE_FINAL_TYPE (PhotosOffsetImportController,
+                      photos_offset_import_controller,
+                      PHOTOS,
+                      OFFSET_IMPORT_CONTROLLER,
+                      PhotosOffsetController);
+
+PhotosOffsetController  *photos_offset_import_controller_dup_singleton     (void);
+
+G_END_DECLS
+
+#endif /* PHOTOS_OFFSET_IMPORT_CONTROLLER_H */
diff --git a/src/photos-preview-view.c b/src/photos-preview-view.c
index 5dd4b971..82f4bb10 100644
--- a/src/photos-preview-view.c
+++ b/src/photos-preview-view.c
@@ -908,6 +908,9 @@ photos_preview_view_window_mode_changed (PhotosPreviewView *self, PhotosWindowMo
       photos_preview_nav_buttons_hide (self->nav_buttons);
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      break;
+
     case PHOTOS_WINDOW_MODE_PREVIEW:
       gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), FALSE);
       photos_edit_palette_hide_details (PHOTOS_EDIT_PALETTE (self->palette));
@@ -1427,6 +1430,7 @@ photos_preview_view_set_mode (PhotosPreviewView *self, PhotosWindowMode old_mode
   g_return_if_fail (PHOTOS_IS_PREVIEW_VIEW (self));
   g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_NONE);
   g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_EDIT);
+  g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_IMPORT);
   g_return_if_fail (old_mode != PHOTOS_WINDOW_MODE_PREVIEW);
 
   photos_preview_nav_buttons_set_auto_hide (self->nav_buttons, TRUE);
diff --git a/src/photos-query.h b/src/photos-query.h
index 94707161..e59c03df 100644
--- a/src/photos-query.h
+++ b/src/photos-query.h
@@ -66,8 +66,9 @@ typedef enum
   PHOTOS_QUERY_FLAGS_UNFILTERED     = 1 << 0,
   PHOTOS_QUERY_FLAGS_COLLECTIONS    = 1 << 1,
   PHOTOS_QUERY_FLAGS_FAVORITES      = 1 << 2,
-  PHOTOS_QUERY_FLAGS_OVERVIEW       = 1 << 3,
-  PHOTOS_QUERY_FLAGS_SEARCH         = 1 << 4
+  PHOTOS_QUERY_FLAGS_IMPORT         = 1 << 3,
+  PHOTOS_QUERY_FLAGS_OVERVIEW       = 1 << 4,
+  PHOTOS_QUERY_FLAGS_SEARCH         = 1 << 5
 } PhotosQueryFlags;
 
 extern const gchar *PHOTOS_QUERY_COLLECTIONS_IDENTIFIER;
diff --git a/src/photos-search-type-manager.c b/src/photos-search-type-manager.c
index 25f856e9..4afd1b4a 100644
--- a/src/photos-search-type-manager.c
+++ b/src/photos-search-type-manager.c
@@ -58,7 +58,7 @@ photos_search_type_manager_get_filter (PhotosBaseManager *mngr, gint flags)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_COLLECTIONS);
   else if (flags & PHOTOS_QUERY_FLAGS_FAVORITES)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_FAVORITES);
-  else if (flags & PHOTOS_QUERY_FLAGS_OVERVIEW)
+  else if (flags & PHOTOS_QUERY_FLAGS_IMPORT || flags & PHOTOS_QUERY_FLAGS_OVERVIEW)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_PHOTOS);
   else if (flags & PHOTOS_QUERY_FLAGS_SEARCH)
     search_type = photos_base_manager_get_active_object (mngr);
@@ -79,7 +79,7 @@ photos_search_type_manager_get_where (PhotosBaseManager *mngr, gint flags)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_COLLECTIONS);
   else if (flags & PHOTOS_QUERY_FLAGS_FAVORITES)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_FAVORITES);
-  else if (flags & PHOTOS_QUERY_FLAGS_OVERVIEW)
+  else if (flags & PHOTOS_QUERY_FLAGS_IMPORT || flags & PHOTOS_QUERY_FLAGS_OVERVIEW)
     search_type = photos_base_manager_get_object_by_id (mngr, PHOTOS_SEARCH_TYPE_STOCK_PHOTOS);
   else if (flags & PHOTOS_QUERY_FLAGS_SEARCH)
     search_type = photos_base_manager_get_active_object (mngr);
diff --git a/src/photos-selection-toolbar.c b/src/photos-selection-toolbar.c
index ff0e6fb7..24931e89 100644
--- a/src/photos-selection-toolbar.c
+++ b/src/photos-selection-toolbar.c
@@ -46,6 +46,7 @@ struct _PhotosSelectionToolbar
   GtkWidget *toolbar_collection;
   GtkWidget *toolbar_export;
   GtkWidget *toolbar_favorite;
+  GtkWidget *toolbar_import;
   GtkWidget *toolbar_open;
   GtkWidget *toolbar_print;
   GtkWidget *toolbar_properties;
@@ -205,6 +206,7 @@ photos_selection_toolbar_set_item_visibility (PhotosSelectionToolbar *self)
   gboolean show_collection;
   gboolean show_export;
   gboolean show_favorite;
+  gboolean show_import;
   gboolean show_open;
   gboolean show_print;
   gboolean show_properties;
@@ -229,6 +231,7 @@ photos_selection_toolbar_set_item_visibility (PhotosSelectionToolbar *self)
       show_collection = TRUE;
       show_export = TRUE;
       show_favorite = TRUE;
+      show_import = FALSE;
       show_open = TRUE;
       show_print = TRUE;
       show_properties = TRUE;
@@ -237,6 +240,19 @@ photos_selection_toolbar_set_item_visibility (PhotosSelectionToolbar *self)
       show_trash = TRUE;
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      show_collection = FALSE;
+      show_export = FALSE;
+      show_favorite = FALSE;
+      show_import = TRUE;
+      show_open = FALSE;
+      show_print = FALSE;
+      show_properties = FALSE;
+      show_separator = FALSE;
+      show_share = FALSE;
+      show_trash = FALSE;
+      break;
+
     case PHOTOS_WINDOW_MODE_NONE:
     case PHOTOS_WINDOW_MODE_EDIT:
     case PHOTOS_WINDOW_MODE_PREVIEW:
@@ -307,6 +323,7 @@ photos_selection_toolbar_set_item_visibility (PhotosSelectionToolbar *self)
   gtk_widget_set_visible (self->toolbar_collection, show_collection);
   gtk_widget_set_visible (self->toolbar_export, show_export);
   gtk_widget_set_visible (self->toolbar_favorite, show_favorite);
+  gtk_widget_set_visible (self->toolbar_import, show_import);
   gtk_widget_set_visible (self->toolbar_open, show_open);
   gtk_widget_set_visible (self->toolbar_print, show_print);
   gtk_widget_set_visible (self->toolbar_properties, show_properties);
@@ -433,6 +450,7 @@ photos_selection_toolbar_class_init (PhotosSelectionToolbarClass *class)
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Photos/selection-toolbar.ui");
   gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_export);
   gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_favorite);
+  gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_import);
   gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_open);
   gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_print);
   gtk_widget_class_bind_template_child (widget_class, PhotosSelectionToolbar, toolbar_properties);
diff --git a/src/photos-selection-toolbar.ui b/src/photos-selection-toolbar.ui
index bb7522a5..7e7aa466 100644
--- a/src/photos-selection-toolbar.ui
+++ b/src/photos-selection-toolbar.ui
@@ -106,6 +106,19 @@
           <property name="pack_type">end</property>
         </packing>
       </child>
+      <child>
+        <object class="GtkButton" id="toolbar_import">
+          <property name="label" translatable="yes">Add to Photos</property>
+          <property name="no-show-all">1</property>
+          <property name="action_name">app.import-current</property>
+          <style>
+            <class name="suggested-action"/>
+          </style>
+        </object>
+        <packing>
+          <property name="pack_type">end</property>
+        </packing>
+      </child>
       <child>
         <object class="GtkButton" id="toolbar_collection">
           <property name="label" translatable="yes">Add to Album</property>
diff --git a/src/photos-source-manager.c b/src/photos-source-manager.c
index 5b0b287f..92871f02 100644
--- a/src/photos-source-manager.c
+++ b/src/photos-source-manager.c
@@ -69,20 +69,40 @@ photos_source_manager_get_filter (PhotosBaseManager *mngr, gint flags)
 {
   GApplication *app;
   GObject *source;
+  const gchar *empty_filter = "(false)";
   const gchar *id;
   gchar *filter;
 
   app = g_application_get_default ();
   if (photos_application_get_empty_results (PHOTOS_APPLICATION (app)))
     {
-      filter = g_strdup ("(false)");
+      filter = g_strdup (empty_filter);
       goto out;
     }
 
-  if (flags & PHOTOS_QUERY_FLAGS_SEARCH)
-    source = photos_base_manager_get_active_object (mngr);
+  if (flags & PHOTOS_QUERY_FLAGS_IMPORT)
+    {
+      GMount *mount;
+
+      source = photos_base_manager_get_active_object (mngr);
+      mount = photos_source_get_mount (PHOTOS_SOURCE (source));
+      if (mount == NULL)
+        source = NULL;
+    }
+  else if (flags & PHOTOS_QUERY_FLAGS_SEARCH)
+    {
+      source = photos_base_manager_get_active_object (mngr);
+    }
   else
-    source = photos_base_manager_get_object_by_id (mngr, PHOTOS_SOURCE_STOCK_ALL);
+    {
+      source = photos_base_manager_get_object_by_id (mngr, PHOTOS_SOURCE_STOCK_ALL);
+    }
+
+  if (source == NULL)
+    {
+      filter = g_strdup (empty_filter);
+      goto out;
+    }
 
   id = photos_filterable_get_id (PHOTOS_FILTERABLE (source));
   if (g_strcmp0 (id, PHOTOS_SOURCE_STOCK_ALL) == 0)
diff --git a/src/photos-source-notification.c b/src/photos-source-notification.c
index 26b87c56..02858cb8 100644
--- a/src/photos-source-notification.c
+++ b/src/photos-source-notification.c
@@ -25,6 +25,7 @@
 
 #include "photos-base-manager.h"
 #include "photos-filterable.h"
+#include "photos-item-manager.h"
 #include "photos-search-context.h"
 #include "photos-source-notification.h"
 #include "photos-utils.h"
@@ -34,6 +35,7 @@ struct _PhotosSourceNotification
 {
   GtkGrid parent_instance;
   PhotosBaseManager *src_mngr;
+  PhotosModeController *mode_cntrlr;
   PhotosSource *source;
 };
 
@@ -126,6 +128,41 @@ photos_source_notification_settings_clicked (PhotosSourceNotification *self)
 }
 
 
+static void
+photos_source_notification_window_mode_changed (PhotosSourceNotification *self,
+                                                PhotosWindowMode mode,
+                                                PhotosWindowMode old_mode)
+{
+  GMount *mount;
+
+  g_return_if_fail (PHOTOS_IS_SOURCE_NOTIFICATION (self));
+
+  mount = photos_source_get_mount (self->source);
+  g_return_if_fail (G_IS_MOUNT (mount));
+
+  switch (mode)
+    {
+    case PHOTOS_WINDOW_MODE_COLLECTION_VIEW:
+    case PHOTOS_WINDOW_MODE_COLLECTIONS:
+    case PHOTOS_WINDOW_MODE_EDIT:
+    case PHOTOS_WINDOW_MODE_FAVORITES:
+    case PHOTOS_WINDOW_MODE_OVERVIEW:
+    case PHOTOS_WINDOW_MODE_PREVIEW:
+    case PHOTOS_WINDOW_MODE_SEARCH:
+      break;
+
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      photos_source_notification_close (self);
+      break;
+
+    case PHOTOS_WINDOW_MODE_NONE:
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+
 static void
 photos_source_notification_constructed (GObject *object)
 {
@@ -186,6 +223,12 @@ photos_source_notification_constructed (GObject *object)
                         "notify::sensitive",
                         G_CALLBACK (photos_source_notification_import_notify_sensitive),
                         NULL);
+
+      g_signal_connect_object (self->mode_cntrlr,
+                               "window-mode-changed",
+                               G_CALLBACK (photos_source_notification_window_mode_changed),
+                               self,
+                               G_CONNECT_SWAPPED);
     }
   else if (goa_object != NULL)
     {
@@ -232,6 +275,7 @@ photos_source_notification_dispose (GObject *object)
   PhotosSourceNotification *self = PHOTOS_SOURCE_NOTIFICATION (object);
 
   g_clear_object (&self->src_mngr);
+  g_clear_object (&self->mode_cntrlr);
   g_clear_object (&self->source);
 
   G_OBJECT_CLASS (photos_source_notification_parent_class)->dispose (object);
@@ -284,6 +328,7 @@ photos_source_notification_init (PhotosSourceNotification *self)
   state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
 
   self->src_mngr = g_object_ref (state->src_mngr);
+  self->mode_cntrlr = g_object_ref (state->mode_cntrlr);
 }
 
 
diff --git a/src/photos-tracker-import-controller.c b/src/photos-tracker-import-controller.c
new file mode 100644
index 00000000..ee668d39
--- /dev/null
+++ b/src/photos-tracker-import-controller.c
@@ -0,0 +1,438 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+/* Based on code from:
+ *   + Documents
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <libtracker-control/tracker-control.h>
+
+#include "photos-base-manager.h"
+#include "photos-device-item.h"
+#include "photos-item-manager.h"
+#include "photos-offset-import-controller.h"
+#include "photos-query-builder.h"
+#include "photos-search-context.h"
+#include "photos-tracker-import-controller.h"
+#include "photos-utils.h"
+
+
+struct _PhotosTrackerImportController
+{
+  PhotosTrackerController parent_instance;
+  GCancellable *cancellable;
+  GQueue *pending_directories;
+  PhotosBaseManager *item_mngr;
+  PhotosBaseManager *src_mngr;
+  PhotosOffsetController *offset_cntrlr;
+  TrackerMinerManager *manager;
+};
+
+
+G_DEFINE_TYPE_WITH_CODE (PhotosTrackerImportController,
+                         photos_tracker_import_controller,
+                         PHOTOS_TYPE_TRACKER_CONTROLLER,
+                         photos_utils_ensure_extension_points ();
+                         g_io_extension_point_implement (PHOTOS_TRACKER_CONTROLLER_EXTENSION_POINT_NAME,
+                                                         g_define_type_id,
+                                                         "import",
+                                                         0));
+
+
+static const gchar *IMPORTABLE_MIME_TYPES[] =
+{
+  "image/jpeg",
+  "image/png",
+  "image/x-dcraw"
+};
+
+
+static void photos_tracker_import_controller_enumerate_children (GObject *source_object,
+                                                                 GAsyncResult *res,
+                                                                 gpointer user_data);
+
+
+static void
+photos_tracker_import_controller_index_file (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  g_autoptr (GFile) file = G_FILE (user_data);
+  TrackerMinerManager *manager = TRACKER_MINER_MANAGER (source_object);
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    if (!tracker_miner_manager_index_file_for_process_finish (manager, res, &error))
+      {
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+          {
+            g_autofree gchar *uri = NULL;
+
+            uri = g_file_get_uri (file);
+            g_warning ("Unable to index %s: %s", uri, error->message);
+          }
+      }
+  }
+}
+
+
+static void
+photos_tracker_import_controller_next_files (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  PhotosTrackerImportController *self;
+  GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object);
+  GList *infos = NULL;
+  GList *l;
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    infos = g_file_enumerator_next_files_finish (enumerator, res, &error);
+    if (error != NULL)
+      {
+        GFile *directory;
+        g_autofree gchar *uri = NULL;
+
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+          goto out;
+
+        directory = g_file_enumerator_get_container (enumerator);
+        uri = g_file_get_uri (directory);
+        g_warning ("Unable to read files from %s: %s", uri, error->message);
+      }
+  }
+
+  self = PHOTOS_TRACKER_IMPORT_CONTROLLER (user_data);
+
+  if (infos == NULL)
+    {
+      GFile *directory;
+      GFile *queue_head;
+
+      directory = g_file_enumerator_get_container (enumerator);
+      queue_head = G_FILE (g_queue_pop_head (self->pending_directories));
+      g_assert_true (g_file_equal (directory, queue_head));
+
+      if (!g_queue_is_empty (self->pending_directories))
+        {
+          directory = G_FILE (g_queue_peek_head (self->pending_directories));
+          g_file_enumerate_children_async (directory,
+                                           G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","
+                                           G_FILE_ATTRIBUTE_STANDARD_NAME","
+                                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                           G_PRIORITY_DEFAULT,
+                                           self->cancellable,
+                                           photos_tracker_import_controller_enumerate_children,
+                                           self);
+        }
+    }
+  else
+    {
+      for (l = infos; l != NULL; l = l->next)
+        {
+          g_autoptr (GFile) file = NULL;
+          GFileInfo *info = G_FILE_INFO (l->data);
+          GFileType file_type;
+
+          file = g_file_enumerator_get_child (enumerator, info);
+          file_type = g_file_info_get_file_type (info);
+
+          switch (file_type)
+            {
+            case G_FILE_TYPE_DIRECTORY:
+              g_queue_push_tail (self->pending_directories, g_object_ref (file));
+              break;
+
+            case G_FILE_TYPE_REGULAR:
+              {
+                const gchar *mime_type;
+                guint i;
+                guint n_elements;
+
+                mime_type = g_file_info_get_content_type (info);
+                n_elements = G_N_ELEMENTS (IMPORTABLE_MIME_TYPES);
+                for (i = 0; i < n_elements; i++)
+                  {
+                    if (g_content_type_equals (mime_type, IMPORTABLE_MIME_TYPES[i])
+                        || g_content_type_is_a (mime_type, IMPORTABLE_MIME_TYPES[i]))
+                      {
+                        tracker_miner_manager_index_file_for_process_async (self->manager,
+                                                                            file,
+                                                                            self->cancellable,
+                                                                            
photos_tracker_import_controller_index_file,
+                                                                            g_object_ref (file));
+                      }
+                  }
+
+                break;
+              }
+
+            case G_FILE_TYPE_UNKNOWN:
+            case G_FILE_TYPE_MOUNTABLE:
+            case G_FILE_TYPE_SHORTCUT:
+            case G_FILE_TYPE_SPECIAL:
+            case G_FILE_TYPE_SYMBOLIC_LINK:
+            default:
+              break;
+            }
+        }
+
+      g_file_enumerator_next_files_async (enumerator,
+                                          5,
+                                          G_PRIORITY_DEFAULT,
+                                          self->cancellable,
+                                          photos_tracker_import_controller_next_files,
+                                          self);
+    }
+
+ out:
+  g_list_free_full (infos, g_object_unref);
+}
+
+
+static void
+photos_tracker_import_controller_enumerate_children (GObject *source_object, GAsyncResult *res, gpointer 
user_data)
+{
+  PhotosTrackerImportController *self;
+  GFile *directory = G_FILE (source_object);
+  g_autoptr (GFileEnumerator) enumerator = NULL;
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    enumerator = g_file_enumerate_children_finish (directory, res, &error);
+    if (error != NULL)
+      {
+        if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+          {
+            g_autofree gchar *uri = NULL;
+
+            uri = g_file_get_uri (directory);
+            g_warning ("Unable to get an enumerator for %s: %s", uri, error->message);
+          }
+
+        goto out;
+      }
+  }
+
+  self = PHOTOS_TRACKER_IMPORT_CONTROLLER (user_data);
+
+  g_file_enumerator_next_files_async (enumerator,
+                                      5,
+                                      G_PRIORITY_DEFAULT,
+                                      self->cancellable,
+                                      photos_tracker_import_controller_next_files,
+                                      self);
+
+ out:
+  return;
+}
+
+
+static void
+photos_tracker_import_controller_source_active_changed (PhotosTrackerImportController *self, GObject *source)
+{
+  GMount *mount;
+  gboolean frozen;
+
+  g_return_if_fail (PHOTOS_IS_TRACKER_IMPORT_CONTROLLER (self));
+  g_return_if_fail (PHOTOS_IS_SOURCE (source));
+  g_return_if_fail (PHOTOS_IS_ITEM_MANAGER (self->item_mngr));
+
+  mount = photos_source_get_mount (PHOTOS_SOURCE (source));
+  frozen = mount == NULL;
+  photos_tracker_controller_set_frozen (PHOTOS_TRACKER_CONTROLLER (self), frozen);
+
+  if (mount == NULL)
+    {
+      g_cancellable_cancel (self->cancellable);
+      g_clear_object (&self->cancellable);
+      self->cancellable = g_cancellable_new ();
+
+      g_queue_free_full (self->pending_directories, g_object_unref);
+      self->pending_directories = g_queue_new ();
+
+      photos_item_manager_clear (PHOTOS_ITEM_MANAGER (self->item_mngr), PHOTOS_WINDOW_MODE_IMPORT);
+    }
+  else
+    {
+      g_return_if_fail (g_queue_is_empty (self->pending_directories));
+
+      if (G_LIKELY (self->manager != NULL))
+        {
+          g_autoptr (GFile) root = NULL;
+
+          root = g_mount_get_root (mount);
+          g_queue_push_tail (self->pending_directories, g_object_ref (root));
+          g_file_enumerate_children_async (root,
+                                           G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","
+                                           G_FILE_ATTRIBUTE_STANDARD_NAME","
+                                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                           G_PRIORITY_DEFAULT,
+                                           self->cancellable,
+                                           photos_tracker_import_controller_enumerate_children,
+                                           self);
+        }
+
+      photos_tracker_controller_refresh_for_object (PHOTOS_TRACKER_CONTROLLER (self));
+    }
+}
+
+
+static PhotosOffsetController *
+photos_tracker_import_controller_get_offset_controller (PhotosTrackerController *trk_cntrlr)
+{
+  PhotosTrackerImportController *self = PHOTOS_TRACKER_IMPORT_CONTROLLER (trk_cntrlr);
+  return g_object_ref (self->offset_cntrlr);
+}
+
+
+static PhotosQuery *
+photos_tracker_import_controller_get_query (PhotosTrackerController *trk_cntrlr)
+{
+  PhotosTrackerImportController *self = PHOTOS_TRACKER_IMPORT_CONTROLLER (trk_cntrlr);
+  GApplication *app;
+  PhotosSearchContextState *state;
+
+  app = g_application_get_default ();
+  state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
+
+  return photos_query_builder_global_query (state, PHOTOS_QUERY_FLAGS_IMPORT, self->offset_cntrlr);
+}
+
+
+static GObject *
+photos_tracker_import_controller_constructor (GType type,
+                                              guint n_construct_params,
+                                              GObjectConstructParam *construct_params)
+{
+  static GObject *self = NULL;
+
+  if (self == NULL)
+    {
+      self = G_OBJECT_CLASS (photos_tracker_import_controller_parent_class)->constructor (type,
+                                                                                          n_construct_params,
+                                                                                          construct_params);
+      g_object_add_weak_pointer (self, (gpointer) &self);
+      return self;
+    }
+
+  return g_object_ref (self);
+}
+
+
+static void
+photos_tracker_import_controller_dispose (GObject *object)
+{
+  PhotosTrackerImportController *self = PHOTOS_TRACKER_IMPORT_CONTROLLER (object);
+
+  if (self->cancellable != NULL)
+    {
+      g_cancellable_cancel (self->cancellable);
+      g_clear_object (&self->cancellable);
+    }
+
+  if (self->pending_directories != NULL)
+    {
+      g_queue_free_full (self->pending_directories, g_object_unref);
+      self->pending_directories = NULL;
+    }
+
+  g_clear_object (&self->src_mngr);
+  g_clear_object (&self->offset_cntrlr);
+  g_clear_object (&self->manager);
+
+  G_OBJECT_CLASS (photos_tracker_import_controller_parent_class)->dispose (object);
+}
+
+
+static void
+photos_tracker_import_controller_finalize (GObject *object)
+{
+  PhotosTrackerImportController *self = PHOTOS_TRACKER_IMPORT_CONTROLLER (object);
+
+  if (self->item_mngr != NULL)
+    g_object_remove_weak_pointer (G_OBJECT (self->item_mngr), (gpointer *) &self->item_mngr);
+
+  G_OBJECT_CLASS (photos_tracker_import_controller_parent_class)->finalize (object);
+}
+
+
+static void
+photos_tracker_import_controller_init (PhotosTrackerImportController *self)
+{
+  GApplication *app;
+  PhotosSearchContextState *state;
+
+  app = g_application_get_default ();
+  state = photos_search_context_get_state (PHOTOS_SEARCH_CONTEXT (app));
+
+  self->cancellable = g_cancellable_new ();
+  self->pending_directories = g_queue_new ();
+
+  self->item_mngr = state->item_mngr;
+  g_object_add_weak_pointer (G_OBJECT (self->item_mngr), (gpointer *) &self->item_mngr);
+
+  self->src_mngr = g_object_ref (state->src_mngr);
+  g_signal_connect_object (self->src_mngr,
+                           "active-changed",
+                           G_CALLBACK (photos_tracker_import_controller_source_active_changed),
+                           self,
+                           G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+
+  self->offset_cntrlr = photos_offset_import_controller_dup_singleton ();
+
+  {
+    g_autoptr (GError) error = NULL;
+
+    self->manager = tracker_miner_manager_new_full (FALSE, &error);
+    if (error != NULL)
+      g_warning ("Unable to create a TrackerMinerManager, indexing attached devices won't work: %s", 
error->message);
+  }
+}
+
+
+static void
+photos_tracker_import_controller_class_init (PhotosTrackerImportControllerClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  PhotosTrackerControllerClass *tracker_controller_class = PHOTOS_TRACKER_CONTROLLER_CLASS (class);
+
+  tracker_controller_class->base_item_type = PHOTOS_TYPE_DEVICE_ITEM;
+
+  object_class->constructor = photos_tracker_import_controller_constructor;
+  object_class->dispose = photos_tracker_import_controller_dispose;
+  object_class->finalize = photos_tracker_import_controller_finalize;
+  tracker_controller_class->get_offset_controller = photos_tracker_import_controller_get_offset_controller;
+  tracker_controller_class->get_query = photos_tracker_import_controller_get_query;
+}
+
+
+PhotosTrackerController *
+photos_tracker_import_controller_dup_singleton (void)
+{
+  return g_object_new (PHOTOS_TYPE_TRACKER_IMPORT_CONTROLLER,
+                       "delay-start", TRUE,
+                       "mode", PHOTOS_WINDOW_MODE_IMPORT,
+                       NULL);
+}
diff --git a/src/photos-tracker-import-controller.h b/src/photos-tracker-import-controller.h
new file mode 100644
index 00000000..3c9ada8a
--- /dev/null
+++ b/src/photos-tracker-import-controller.h
@@ -0,0 +1,41 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2018 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+/* Based on code from:
+ *   + Documents
+ */
+
+#ifndef PHOTOS_TRACKER_IMPORT_CONTROLLER_H
+#define PHOTOS_TRACKER_IMPORT_CONTROLLER_H
+
+#include "photos-tracker-controller.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_TRACKER_IMPORT_CONTROLLER (photos_tracker_import_controller_get_type ())
+G_DECLARE_FINAL_TYPE (PhotosTrackerImportController,
+                      photos_tracker_import_controller,
+                      PHOTOS,
+                      TRACKER_IMPORT_CONTROLLER,
+                      PhotosTrackerController);
+
+PhotosTrackerController  *photos_tracker_import_controller_dup_singleton     (void);
+
+G_END_DECLS
+
+#endif /* PHOTOS_TRACKER_IMPORT_CONTROLLER_H */
diff --git a/src/photos-utils.c b/src/photos-utils.c
index e31db31e..1eb4ade6 100644
--- a/src/photos-utils.c
+++ b/src/photos-utils.c
@@ -46,6 +46,7 @@
 #include "photos-offset-collection-view-controller.h"
 #include "photos-offset-collections-controller.h"
 #include "photos-offset-favorites-controller.h"
+#include "photos-offset-import-controller.h"
 #include "photos-offset-overview-controller.h"
 #include "photos-offset-search-controller.h"
 #include "photos-query.h"
@@ -63,6 +64,7 @@
 #include "photos-tracker-collection-view-controller.h"
 #include "photos-tracker-collections-controller.h"
 #include "photos-tracker-favorites-controller.h"
+#include "photos-tracker-import-controller.h"
 #include "photos-tracker-overview-controller.h"
 #include "photos-tracker-queue.h"
 #include "photos-tracker-search-controller.h"
@@ -723,6 +725,7 @@ photos_utils_ensure_builtins (void)
       g_type_ensure (PHOTOS_TYPE_TRACKER_COLLECTION_VIEW_CONTROLLER);
       g_type_ensure (PHOTOS_TYPE_TRACKER_COLLECTIONS_CONTROLLER);
       g_type_ensure (PHOTOS_TYPE_TRACKER_FAVORITES_CONTROLLER);
+      g_type_ensure (PHOTOS_TYPE_TRACKER_IMPORT_CONTROLLER);
       g_type_ensure (PHOTOS_TYPE_TRACKER_OVERVIEW_CONTROLLER);
       g_type_ensure (PHOTOS_TYPE_TRACKER_SEARCH_CONTROLLER);
 
@@ -1019,6 +1022,11 @@ photos_utils_get_controller (PhotosWindowMode mode,
       trk_cntrlr = photos_tracker_favorites_controller_dup_singleton ();
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      offset_cntrlr = photos_offset_import_controller_dup_singleton ();
+      trk_cntrlr = photos_tracker_import_controller_dup_singleton ();
+      break;
+
     case PHOTOS_WINDOW_MODE_OVERVIEW:
       offset_cntrlr = photos_offset_overview_controller_dup_singleton ();
       trk_cntrlr = photos_tracker_overview_controller_dup_singleton ();
diff --git a/src/photos-view-container.c b/src/photos-view-container.c
index 1679bf1e..da5bd854 100644
--- a/src/photos-view-container.c
+++ b/src/photos-view-container.c
@@ -120,6 +120,10 @@ photos_view_container_get_show_primary_text (PhotosViewContainer *self)
       ret_val = FALSE;
       break;
 
+    case PHOTOS_WINDOW_MODE_IMPORT:
+      ret_val = FALSE;
+      break;
+
     case PHOTOS_WINDOW_MODE_OVERVIEW:
       ret_val = FALSE;
       break;


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