[nautilus/wip/carlosg/favorites: 1/2] Add favorite files



commit a9c446afe262b7989bc17ef1f9ab19931db8dede
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date:   Wed Jun 28 17:47:41 2017 +0100

    Add favorite files
    
    Add option to make files Favorite, by either toggling a star in the
    list view, or from the context menu.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=786039

 data/org.gnome.nautilus.gschema.xml                |   4 +-
 eel/eel-vfs-extensions.c                           |   6 +
 eel/eel-vfs-extensions.h                           |   1 +
 src/meson.build                                    |   6 +-
 src/nautilus-application.c                         |  14 +
 src/nautilus-bookmark-list.c                       |   5 +
 src/nautilus-column-utilities.c                    |  11 +
 src/nautilus-directory.c                           |  14 +
 src/nautilus-directory.h                           |   1 +
 src/nautilus-favorite-directory.c                  | 585 ++++++++++++++
 src/nautilus-favorite-directory.h                  |  57 ++
 src/nautilus-file-undo-operations.c                | 221 ++++++
 src/nautilus-file-undo-operations.h                |  26 +
 src/nautilus-file-utilities.c                      |  18 +-
 src/nautilus-file-utilities.h                      |   1 +
 src/nautilus-file.c                                | 103 ++-
 src/nautilus-file.h                                |   3 +
 src/nautilus-files-view.c                          | 161 +++-
 src/nautilus-list-view-private.h                   |   4 +
 src/nautilus-list-view.c                           | 211 ++++-
 src/nautilus-pathbar.c                             |  12 +-
 src/nautilus-query.c                               |  19 +
 src/nautilus-query.h                               |   4 +
 src/nautilus-search-engine-model.c                 |  17 +
 src/nautilus-search-engine-simple.c                |  19 +-
 src/nautilus-search-engine-tracker.c               |   5 +
 src/nautilus-tag-manager.c                         | 884 +++++++++++++++++++++
 src/nautilus-tag-manager.h                         |  57 ++
 src/nautilus-window.c                              |  14 +
 src/resources/nautilus.gresource.xml               |   1 +
 .../ui/nautilus-files-view-context-menus.ui        |  17 +
 src/resources/ui/nautilus-starred-is-empty.ui      |  42 +
 src/resources/ui/nautilus-window.ui                |   2 +
 33 files changed, 2513 insertions(+), 32 deletions(-)
---
diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml
index d84d69409..4cbb7647b 100644
--- a/data/org.gnome.nautilus.gschema.xml
+++ b/data/org.gnome.nautilus.gschema.xml
@@ -255,11 +255,11 @@
       <summary>Default list view zoom level</summary>
     </key>
     <key type="as" name="default-visible-columns">
-      <default>[ 'name', 'size', 'date_modified' ]</default>
+      <default>[ 'name', 'size', 'date_modified', 'favorite' ]</default>
       <summary>Columns visible in list view</summary>
     </key>
     <key type="as" name="default-column-order">
-      <default>[ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 
'date_modified', 'date_modified_with_time', 'date_accessed' ]</default>
+      <default>[ 'name', 'size', 'type', 'owner', 'group', 'permissions', 'mime_type', 'where', 
'date_modified', 'date_modified_with_time', 'date_accessed', 'recency', 'favorite' ]</default>
       <summary>Column order in list view</summary>
     </key>
     <key type="b" name="use-tree-view">
diff --git a/eel/eel-vfs-extensions.c b/eel/eel-vfs-extensions.c
index 8bb33f7d2..6c626f7e3 100644
--- a/eel/eel-vfs-extensions.c
+++ b/eel/eel-vfs-extensions.c
@@ -38,6 +38,12 @@
 #include <stdlib.h>
 
 gboolean
+eel_uri_is_favorites (const gchar *uri)
+{
+    return g_str_has_prefix (uri, "favorites:");
+}
+
+gboolean
 eel_uri_is_trash (const char *uri)
 {
     return g_str_has_prefix (uri, "trash:");
diff --git a/eel/eel-vfs-extensions.h b/eel/eel-vfs-extensions.h
index 8336efe51..0ad6b2be5 100644
--- a/eel/eel-vfs-extensions.h
+++ b/eel/eel-vfs-extensions.h
@@ -35,6 +35,7 @@ G_BEGIN_DECLS
 #define EEL_DESKTOP_URI "x-nautilus-desktop:"
 #define EEL_SEARCH_URI "x-nautilus-search:"
 
+gboolean           eel_uri_is_favorites                  (const char           *uri);
 gboolean           eel_uri_is_trash                      (const char           *uri);
 gboolean           eel_uri_is_trash_folder               (const char           *uri);
 gboolean           eel_uri_is_in_trash                   (const char           *uri);
diff --git a/src/meson.build b/src/meson.build
index 9f01f5ba2..46f010a0e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -259,7 +259,11 @@ libnautilus_sources = [
     'nautilus-batch-rename-utilities.c',
     'nautilus-batch-rename-utilities.h',
     'nautilus-search-engine-tracker.c',
-    'nautilus-search-engine-tracker.h'
+    'nautilus-search-engine-tracker.h',
+    'nautilus-tag-manager.c',
+    'nautilus-tag-manager.h',
+    'nautilus-favorite-directory.c',
+    'nautilus-favorite-directory.h'
 ]
 
 nautilus_deps = [glib,
diff --git a/src/nautilus-application.c b/src/nautilus-application.c
index 98e6f35d4..adcb7d3e7 100644
--- a/src/nautilus-application.c
+++ b/src/nautilus-application.c
@@ -39,6 +39,7 @@
 #include "nautilus-window.h"
 #include "nautilus-window-slot.h"
 #include "nautilus-preferences-window.h"
+#include "nautilus-tag-manager.h"
 
 #include "nautilus-directory-private.h"
 #include "nautilus-file-utilities.h"
@@ -80,6 +81,9 @@ typedef struct
     GHashTable *notifications;
 
     NautilusFileUndoManager *undo_manager;
+
+    NautilusTagManager *tag_manager;
+    GCancellable *tag_manager_cancellable;
 } NautilusApplicationPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (NautilusApplication, nautilus_application, GTK_TYPE_APPLICATION);
@@ -612,6 +616,11 @@ nautilus_application_finalize (GObject *object)
 
     g_clear_object (&priv->undo_manager);
 
+    g_clear_object (&priv->tag_manager);
+
+    g_cancellable_cancel (priv->tag_manager_cancellable);
+    g_clear_object (&priv->tag_manager_cancellable);
+
     G_OBJECT_CLASS (nautilus_application_parent_class)->finalize (object);
 }
 
@@ -1103,6 +1112,11 @@ nautilus_application_init (NautilusApplication *self)
 
     priv->undo_manager = nautilus_file_undo_manager_new ();
 
+    priv->tag_manager_cancellable = g_cancellable_new ();
+    priv->tag_manager = nautilus_tag_manager_get ();
+    nautilus_tag_manager_set_cancellable (priv->tag_manager,
+                                          priv->tag_manager_cancellable);
+
     g_application_add_main_option_entries (G_APPLICATION (self), options);
 
     nautilus_ensure_extension_points ();
diff --git a/src/nautilus-bookmark-list.c b/src/nautilus-bookmark-list.c
index d7a0ea092..926d949fe 100644
--- a/src/nautilus-bookmark-list.c
+++ b/src/nautilus-bookmark-list.c
@@ -643,6 +643,11 @@ nautilus_bookmark_list_can_bookmark_location (NautilusBookmarkList *list,
         return FALSE;
     }
 
+    if (nautilus_is_favorite_directory (location))
+    {
+        return FALSE;
+    }
+
     bookmark = nautilus_bookmark_new (location, NULL);
     is_builtin = nautilus_bookmark_get_is_builtin (bookmark);
     g_object_unref (bookmark);
diff --git a/src/nautilus-column-utilities.c b/src/nautilus-column-utilities.c
index 1a2a0927c..22789938b 100644
--- a/src/nautilus-column-utilities.c
+++ b/src/nautilus-column-utilities.c
@@ -42,6 +42,7 @@ static const char *default_column_order[] =
     "date_modified",
     "date_accessed",
     "recency",
+    "favorite",
     NULL
 };
 
@@ -149,6 +150,16 @@ get_builtin_columns (void)
                                            "xalign", 1.0,
                                            NULL));
 
+    columns = g_list_append (columns,
+                             g_object_new (NAUTILUS_TYPE_COLUMN,
+                                           "name", "favorite",
+                                           "attribute", "favorite",
+                                           "label", _("Star"),
+                                           "description", _("Shows if file is favorite."),
+                                           "default-sort-order", GTK_SORT_DESCENDING,
+                                           "xalign", 0.5,
+                                           NULL));
+
     return columns;
 }
 
diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c
index ad3bb03db..6d529d7cf 100644
--- a/src/nautilus-directory.c
+++ b/src/nautilus-directory.c
@@ -27,6 +27,7 @@
 #include "nautilus-file-private.h"
 #include "nautilus-file-utilities.h"
 #include "nautilus-search-directory.h"
+#include "nautilus-favorite-directory.h"
 #include "nautilus-search-directory-file.h"
 #include "nautilus-vfs-file.h"
 #include "nautilus-global-preferences.h"
@@ -802,6 +803,19 @@ nautilus_directory_is_in_recent (NautilusDirectory *directory)
 }
 
 gboolean
+nautilus_directory_is_in_starred (NautilusDirectory *directory)
+{
+    g_assert (NAUTILUS_IS_DIRECTORY (directory));
+
+    if (directory->details->location == NULL)
+    {
+        return FALSE;
+    }
+
+    return g_file_has_uri_scheme (directory->details->location, "favorites");
+}
+
+gboolean
 nautilus_directory_is_in_admin (NautilusDirectory *directory)
 {
     g_assert (NAUTILUS_IS_DIRECTORY (directory));
diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h
index 9a63aa8eb..a16b9c43d 100644
--- a/src/nautilus-directory.h
+++ b/src/nautilus-directory.h
@@ -227,6 +227,7 @@ gboolean           nautilus_directory_is_local_or_fuse         (NautilusDirector
 
 gboolean           nautilus_directory_is_in_trash              (NautilusDirectory         *directory);
 gboolean           nautilus_directory_is_in_recent             (NautilusDirectory         *directory);
+gboolean           nautilus_directory_is_in_starred            (NautilusDirectory         *directory);
 gboolean           nautilus_directory_is_in_admin              (NautilusDirectory         *directory);
 
 /* Return false if directory contains anything besides a Nautilus metafile.
diff --git a/src/nautilus-favorite-directory.c b/src/nautilus-favorite-directory.c
new file mode 100644
index 000000000..7f367e45b
--- /dev/null
+++ b/src/nautilus-favorite-directory.c
@@ -0,0 +1,585 @@
+/* nautilus-favorite-directory.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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/>.
+ */
+
+#include "nautilus-favorite-directory.h"
+#include "nautilus-tag-manager.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-directory-private.h"
+#include <glib/gi18n.h>
+
+struct NautilusFavoriteDirectoryDetails
+{
+    NautilusTagManager *tag_manager;
+    GList *files;
+
+    GList *monitor_list;
+    GList *callback_list;
+    GList *pending_callback_list;
+};
+
+typedef struct
+{
+    gboolean monitor_hidden_files;
+    NautilusFileAttributes monitor_attributes;
+
+    gconstpointer client;
+} FavoriteMonitor;
+
+typedef struct
+{
+    NautilusFavoriteDirectory *favorite_directory;
+
+    NautilusDirectoryCallback callback;
+    gpointer callback_data;
+
+    NautilusFileAttributes wait_for_attributes;
+    gboolean wait_for_file_list;
+    GList *file_list;
+} FavoriteCallback;
+
+G_DEFINE_TYPE_WITH_CODE (NautilusFavoriteDirectory, nautilus_favorite_directory, NAUTILUS_TYPE_DIRECTORY,
+                         nautilus_ensure_extension_points ();
+                         g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME,
+                                                         g_define_type_id,
+                                                         NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME,
+                                                         0));
+
+static void
+file_changed (NautilusFile              *file,
+              NautilusFavoriteDirectory *favorite)
+{
+    GList list;
+
+    list.data = file;
+    list.next = NULL;
+
+    nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (favorite), &list);
+}
+
+static void
+nautilus_favorite_directory_update_files (NautilusFavoriteDirectory *self)
+{
+    GList *l;
+    GList *tmp_l;
+    GList *new_favorite_files;
+    GList *monitor_list;
+    FavoriteMonitor *monitor;
+    NautilusFile *file;
+    GHashTable *uri_table;
+    GList *files_added;
+    GList *files_removed;
+    gchar *uri;
+
+    files_added = NULL;
+    files_removed = NULL;
+
+    uri_table = g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       (GDestroyNotify) g_free,
+                                       NULL);
+
+    for (l = self->details->files; l != NULL; l = l->next)
+    {
+        g_hash_table_add (uri_table, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
+    }
+
+    new_favorite_files = nautilus_tag_manager_get_favorite_files (self->details->tag_manager);
+
+    for (l = new_favorite_files; l != NULL; l = l->next)
+    {
+        if (!g_hash_table_contains (uri_table, l->data))
+        {
+            file = nautilus_file_get_by_uri ((gchar*) l->data);
+
+            for (monitor_list = self->details->monitor_list; monitor_list; monitor_list = monitor_list->next)
+            {
+                monitor = monitor_list->data;
+
+                /* Add monitors */
+                nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+            }
+
+            g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+            files_added = g_list_prepend (files_added, file);
+        }
+    }
+
+    l = self->details->files;
+    while (l != NULL)
+    {
+        uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+        if (!nautilus_tag_manager_file_is_favorite (self->details->tag_manager, uri))
+        {
+            files_removed = g_list_prepend (files_removed,
+                                            nautilus_file_ref (NAUTILUS_FILE (l->data)));
+
+            g_signal_handlers_disconnect_by_func (NAUTILUS_FILE (l->data),
+                                                  file_changed,
+                                                  self);
+
+            /* Remove monitors */
+            for (monitor_list = self->details->monitor_list; monitor_list;
+                 monitor_list = monitor_list->next)
+            {
+                monitor = monitor_list->data;
+                nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), monitor);
+            }
+
+            if (l == self->details->files)
+            {
+                self->details->files = g_list_delete_link (self->details->files, l);
+                l = self->details->files;
+            }
+            else
+            {
+                tmp_l = l->prev;
+                self->details->files = g_list_delete_link (self->details->files, l);
+                l = tmp_l->next;
+            }
+        }
+        else
+        {
+            l = l->next;
+        }
+
+        g_free (uri);
+    }
+
+    if (files_added)
+    {
+        nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), files_added);
+
+        for (l = files_added; l != NULL; l = l->next)
+        {
+            self->details->files = g_list_prepend (self->details->files, nautilus_file_ref (NAUTILUS_FILE 
(l->data)));
+        }
+    }
+
+    if (files_removed)
+    {
+        nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), files_removed);
+    }
+
+    nautilus_file_list_free (files_added);
+    nautilus_file_list_free (files_removed);
+    g_hash_table_destroy (uri_table);
+}
+
+static void
+on_favorites_files_changed (NautilusTagManager        *tag_manager,
+                            GList                     *changed_files,
+                            gpointer                   user_data)
+{
+    NautilusFavoriteDirectory *self;
+
+    self = NAUTILUS_FAVORITE_DIRECTORY (user_data);
+
+    nautilus_favorite_directory_update_files (self);
+}
+
+static gboolean
+real_contains_file (NautilusDirectory *directory,
+                    NautilusFile      *file)
+{
+    NautilusFavoriteDirectory *self;
+    g_autofree gchar *uri = NULL;
+
+    self = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+    uri = nautilus_file_get_uri (file);
+
+    return nautilus_tag_manager_file_is_favorite (self->details->tag_manager, uri);
+}
+
+static gboolean
+real_is_editable (NautilusDirectory *directory)
+{
+    return FALSE;
+}
+
+static void
+real_force_reload (NautilusDirectory *directory)
+{
+    nautilus_favorite_directory_update_files (NAUTILUS_FAVORITE_DIRECTORY (directory));
+}
+
+static void
+real_call_when_ready (NautilusDirectory         *directory,
+                      NautilusFileAttributes     file_attributes,
+                      gboolean                   wait_for_file_list,
+                      NautilusDirectoryCallback  callback,
+                      gpointer                   callback_data)
+{
+    GList *file_list;
+    NautilusFavoriteDirectory *favorite;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+    file_list = nautilus_file_list_copy (favorite->details->files);
+
+    callback (NAUTILUS_DIRECTORY (directory),
+                                  file_list,
+                                  callback_data);
+}
+
+static gboolean
+real_are_all_files_seen (NautilusDirectory *directory)
+{
+    return TRUE;
+}
+
+static void
+real_file_monitor_add (NautilusDirectory         *directory,
+                       gconstpointer              client,
+                       gboolean                   monitor_hidden_files,
+                       NautilusFileAttributes     file_attributes,
+                       NautilusDirectoryCallback  callback,
+                       gpointer                   callback_data)
+{
+    GList *list;
+    FavoriteMonitor *monitor;
+    NautilusFavoriteDirectory *favorite;
+    NautilusFile *file;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+    monitor = g_new0 (FavoriteMonitor, 1);
+    monitor->monitor_hidden_files = monitor_hidden_files;
+    monitor->monitor_attributes = file_attributes;
+    monitor->client = client;
+
+    favorite->details->monitor_list = g_list_prepend (favorite->details->monitor_list, monitor);
+
+    if (callback != NULL)
+    {
+        (*callback) (directory, favorite->details->files, callback_data);
+    }
+
+    for (list = favorite->details->files; list != NULL; list = list->next)
+    {
+        file = list->data;
+
+        /* Add monitors */
+        nautilus_file_monitor_add (file, monitor, file_attributes);
+    }
+}
+
+static void
+favorite_monitor_destroy (FavoriteMonitor           *monitor,
+                          NautilusFavoriteDirectory *favorite)
+{
+    GList *l;
+    NautilusFile *file;
+
+    for (l = favorite->details->files; l != NULL; l = l->next)
+    {
+        file = l->data;
+
+        nautilus_file_monitor_remove (file, monitor);
+    }
+
+    g_free (monitor);
+}
+
+static void
+real_monitor_remove (NautilusDirectory *directory,
+                     gconstpointer      client)
+{
+    NautilusFavoriteDirectory *favorite;
+    FavoriteMonitor *monitor;
+    GList *list;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+    for (list = favorite->details->monitor_list; list != NULL; list = list->next)
+    {
+        monitor = list->data;
+
+        if (monitor->client != client)
+            continue;
+
+        favorite->details->monitor_list = g_list_delete_link (favorite->details->monitor_list, list);
+
+        favorite_monitor_destroy (monitor, favorite);
+
+        break;
+    }
+}
+
+static gboolean
+real_handles_location (GFile *location)
+{
+    g_autofree gchar *uri = NULL;
+
+    uri = g_file_get_uri (location);
+
+    if (eel_uri_is_favorites (uri))
+    {
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static FavoriteCallback*
+favorite_callback_find_pending (NautilusFavoriteDirectory *favorite,
+                                NautilusDirectoryCallback  callback,
+                                gpointer                   callback_data)
+{
+    FavoriteCallback *favorite_callback;
+    GList *list;
+
+    for (list = favorite->details->pending_callback_list; list != NULL; list = list->next)
+    {
+        favorite_callback = list->data;
+
+        if (favorite_callback->callback == callback &&
+            favorite_callback->callback_data == callback_data)
+        {
+            return favorite_callback;
+        }
+    }
+
+    return NULL;
+}
+
+static FavoriteCallback*
+favorite_callback_find (NautilusFavoriteDirectory *favorite,
+                        NautilusDirectoryCallback  callback,
+                        gpointer                   callback_data)
+{
+    FavoriteCallback *favorite_callback;
+    GList *list;
+
+    for (list = favorite->details->callback_list; list != NULL; list = list->next)
+    {
+        favorite_callback = list->data;
+
+        if (favorite_callback->callback == callback &&
+            favorite_callback->callback_data == callback_data)
+        {
+            return favorite_callback;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+favorite_callback_destroy (FavoriteCallback *favorite_callback)
+{
+    nautilus_file_list_free (favorite_callback->file_list);
+
+    g_free (favorite_callback);
+}
+
+static void
+real_cancel_callback (NautilusDirectory        *directory,
+                      NautilusDirectoryCallback callback,
+                      gpointer                  callback_data)
+{
+    NautilusFavoriteDirectory *favorite;
+    FavoriteCallback *favorite_callback;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+    favorite_callback = favorite_callback_find (favorite, callback, callback_data);
+
+    if (favorite_callback)
+    {
+        favorite->details->callback_list = g_list_remove (favorite->details->callback_list, 
favorite_callback);
+
+        favorite_callback_destroy (favorite_callback);
+
+        return;
+    }
+
+    /* Check for a pending callback */
+    favorite_callback = favorite_callback_find_pending (favorite, callback, callback_data);
+
+    if (favorite_callback)
+    {
+        favorite->details->pending_callback_list = g_list_remove (favorite->details->pending_callback_list, 
favorite_callback);
+
+        favorite_callback_destroy (favorite_callback);
+    }
+}
+
+static GList*
+real_get_file_list (NautilusDirectory *directory)
+{
+    NautilusFavoriteDirectory *favorite;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (directory);
+
+    return nautilus_file_list_copy (favorite->details->files);
+}
+
+static void
+nautilus_favorite_directory_set_files (NautilusFavoriteDirectory *self)
+{
+    GList *favorite_files;
+    NautilusFile *file;
+    GList *l;
+    GList *file_list;
+    FavoriteMonitor *monitor;
+    GList *monitor_list;
+
+    file_list = NULL;
+
+    favorite_files = nautilus_tag_manager_get_favorite_files (self->details->tag_manager);
+
+    for (l = favorite_files; l != NULL; l = l->next)
+    {
+        file = nautilus_file_get_by_uri ((gchar*) l->data);
+
+        g_signal_connect (file, "changed", G_CALLBACK (file_changed), self);
+
+        for (monitor_list = self->details->monitor_list; monitor_list; monitor_list = monitor_list->next)
+        {
+            monitor = monitor_list->data;
+
+            /* Add monitors */
+            nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
+        }
+
+        file_list = g_list_prepend (file_list, file);
+    }
+
+    nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list);
+
+    self->details->files = file_list;
+}
+
+static void
+nautilus_favorite_directory_finalize (GObject *object)
+{
+    NautilusFavoriteDirectory *self;
+
+    self = NAUTILUS_FAVORITE_DIRECTORY (object);
+
+    g_signal_handlers_disconnect_by_func (self->details->tag_manager,
+                                          on_favorites_files_changed,
+                                          self);
+
+    g_object_unref (self->details->tag_manager);
+    nautilus_file_list_free (self->details->files);
+
+    G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->finalize (object);
+}
+
+static void
+nautilus_favorite_directory_dispose (GObject *object)
+{
+    NautilusFavoriteDirectory *favorite;
+    GList *l;
+    GList *monitor_list;
+    FavoriteMonitor *monitor;
+    NautilusFile *file;
+
+    favorite = NAUTILUS_FAVORITE_DIRECTORY (object);
+
+    /* Remove file connections */
+    for (l = favorite->details->files; l != NULL; l = l->next)
+    {
+        file = l->data;
+
+        /* Disconnect change handler */
+        g_signal_handlers_disconnect_by_func (file, file_changed, favorite);
+
+        /* Remove monitors */
+        for (monitor_list = favorite->details->monitor_list; monitor_list;
+             monitor_list = monitor_list->next)
+        {
+            monitor = monitor_list->data;
+            nautilus_file_monitor_remove (file, monitor);
+        }
+    }
+
+    /* Remove search monitors */
+    if (favorite->details->monitor_list)
+    {
+        for (l = favorite->details->monitor_list; l != NULL; l = l->next)
+        {
+            favorite_monitor_destroy ((FavoriteMonitor*) l->data, favorite);
+        }
+
+        g_list_free (favorite->details->monitor_list);
+        favorite->details->monitor_list = NULL;
+    }
+
+    G_OBJECT_CLASS (nautilus_favorite_directory_parent_class)->dispose (object);
+}
+
+static void
+nautilus_favorite_directory_class_init (NautilusFavoriteDirectoryClass *klass)
+{
+    GObjectClass *oclass;
+    NautilusDirectoryClass *directory_class;
+
+    oclass = G_OBJECT_CLASS (klass);
+    directory_class = NAUTILUS_DIRECTORY_CLASS (klass);
+
+    oclass->finalize = nautilus_favorite_directory_finalize;
+    oclass->dispose = nautilus_favorite_directory_dispose;
+
+    directory_class->handles_location = real_handles_location;
+    directory_class->contains_file = real_contains_file;
+    directory_class->is_editable = real_is_editable;
+    directory_class->force_reload = real_force_reload;
+    directory_class->call_when_ready = real_call_when_ready;
+    directory_class->are_all_files_seen = real_are_all_files_seen;
+    directory_class->file_monitor_add = real_file_monitor_add;
+    directory_class->file_monitor_remove = real_monitor_remove;
+    directory_class->cancel_callback = real_cancel_callback;
+    directory_class->get_file_list = real_get_file_list;
+
+    g_type_class_add_private (klass, sizeof (NautilusFavoriteDirectoryDetails));
+}
+
+NautilusFavoriteDirectory*
+nautilus_favorite_directory_new ()
+{
+    NautilusFavoriteDirectory *self;
+
+    self = g_object_new (NAUTILUS_TYPE_FAVORITE_DIRECTORY, NULL);
+
+    return self;
+}
+
+static void
+nautilus_favorite_directory_init (NautilusFavoriteDirectory *self)
+{
+    NautilusTagManager *tag_manager;
+
+    self->details = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_FAVORITE_DIRECTORY,
+                                                 NautilusFavoriteDirectoryDetails);
+
+    tag_manager = nautilus_tag_manager_get ();
+
+    g_signal_connect (tag_manager,
+                      "favorites-changed",
+                      (GCallback) on_favorites_files_changed,
+                      self);
+
+    self->details->tag_manager = tag_manager;
+
+    nautilus_favorite_directory_set_files (self);
+
+}
diff --git a/src/nautilus-favorite-directory.h b/src/nautilus-favorite-directory.h
new file mode 100644
index 000000000..2acf13c42
--- /dev/null
+++ b/src/nautilus-favorite-directory.h
@@ -0,0 +1,57 @@
+/* nautilus-favorite-directory.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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/>.
+ */
+
+#ifndef NAUTILUS_FAVORITE_DIRECTORY_H
+#define NAUTILUS_FAVORITE_DIRECTORY_H
+
+#include "nautilus-directory.h"
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_FAVORITE_DIRECTORY_PROVIDER_NAME "favorite-directory-provider"
+
+#define NAUTILUS_TYPE_FAVORITE_DIRECTORY nautilus_favorite_directory_get_type()
+#define NAUTILUS_FAVORITE_DIRECTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectory))
+#define NAUTILUS_FAVORITE_DIRECTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass))
+#define NAUTILUS_IS_FAVORITE_DIRECTORY(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY))
+#define NAUTILUS_IS_FAVORITE_DIRECTORY_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_FAVORITE_DIRECTORY))
+#define NAUTILUS_FAVORITE_DIRECTORY_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), NAUTILUS_TYPE_FAVORITE_DIRECTORY, NautilusFavoriteDirectoryClass))
+
+typedef struct NautilusFavoriteDirectoryDetails NautilusFavoriteDirectoryDetails;
+
+typedef struct {
+       NautilusDirectory parent_slot;
+       NautilusFavoriteDirectoryDetails *details;
+} NautilusFavoriteDirectory;
+
+typedef struct {
+       NautilusDirectoryClass parent_slot;
+} NautilusFavoriteDirectoryClass;
+
+GType                                     nautilus_favorite_directory_get_type (void);
+
+NautilusFavoriteDirectory* nautilus_favorite_directory_new      ();
+
+G_END_DECLS
+
+#endif
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
index e833d0578..e971274ef 100644
--- a/src/nautilus-file-undo-operations.c
+++ b/src/nautilus-file-undo-operations.c
@@ -33,6 +33,7 @@
 #include "nautilus-file-undo-manager.h"
 #include "nautilus-batch-rename-dialog.h"
 #include "nautilus-batch-rename-utilities.h"
+#include "nautilus-tag-manager.h"
 
 
 /* Since we use g_get_current_time for setting "orig_trash_time" in the undo
@@ -1304,6 +1305,226 @@ nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRen
     self->priv->new_display_names = g_list_reverse (self->priv->new_display_names);
 }
 
+/* favorite files */
+G_DEFINE_TYPE (NautilusFileUndoInfoFavorites, nautilus_file_undo_info_favorites, 
NAUTILUS_TYPE_FILE_UNDO_INFO);
+
+struct _NautilusFileUndoInfoFavoritesDetails
+{
+    GList *files;
+    /* Whether the action was starring or unstarring */
+    gboolean starred;
+};
+
+enum
+{
+    PROP_FILES = 1,
+    PROP_STARRED,
+    NUM_PROPERTIES
+};
+
+static void
+favorites_strings_func (NautilusFileUndoInfo  *info,
+                        gchar                **undo_label,
+                        gchar                **undo_description,
+                        gchar                **redo_label,
+                        gchar                **redo_description)
+{
+    NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+
+    if (self->priv->starred)
+    {
+        *undo_description = g_strdup_printf (ngettext ("Unstar %d file",
+                                                       "Unstar %d files",
+                                                       g_list_length (self->priv->files)),
+                                             g_list_length (self->priv->files));
+        *redo_description = g_strdup_printf (ngettext ("Star %d file",
+                                                       "Star %d files",
+                                                       g_list_length (self->priv->files)),
+                                             g_list_length (self->priv->files));
+        *undo_label = g_strdup (_("_Undo Starring"));
+        *redo_label = g_strdup (_("_Redo Starring"));
+    }
+    else
+    {
+        *undo_description = g_strdup_printf (ngettext ("Star %d file",
+                                                       "Star %d files",
+                                                       g_list_length (self->priv->files)),
+                                             g_list_length (self->priv->files));
+        *redo_description = g_strdup_printf (ngettext ("Unstar %d file",
+                                                       "Unstar %d files",
+                                                       g_list_length (self->priv->files)),
+                                             g_list_length (self->priv->files));
+        *undo_label = g_strdup (_("_Undo Unstarring"));
+        *redo_label = g_strdup (_("_Redo Unstarring"));
+    }
+}
+
+static void
+on_undo_favorite_tags_updated (GObject      *object,
+                               GAsyncResult *res,
+                               gpointer      user_data)
+{
+    GTask *task;
+    NautilusFileUndoInfo *undo_info;
+
+    undo_info = NAUTILUS_FILE_UNDO_INFO (object);
+
+    task = user_data;
+    g_clear_object (&task);
+
+    file_undo_info_operation_callback (NULL, NULL, NULL, undo_info);
+}
+
+static void
+favorites_redo_func (NautilusFileUndoInfo *info,
+                     GtkWindow            *parent_window)
+{
+    NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+    NautilusTagManager *tag_manager;
+
+    tag_manager = nautilus_tag_manager_get ();
+
+    if (self->priv->starred)
+    {
+        nautilus_tag_manager_star_files (tag_manager,
+                                         G_OBJECT (info),
+                                         self->priv->files,
+                                         on_undo_favorite_tags_updated,
+                                         NULL);
+    }
+    else
+    {
+
+        nautilus_tag_manager_unstar_files (tag_manager,
+                                           G_OBJECT (info),
+                                           self->priv->files,
+                                           on_undo_favorite_tags_updated,
+                                           NULL);
+    }
+}
+
+static void
+favorites_undo_func (NautilusFileUndoInfo *info,
+                     GtkWindow            *parent_window)
+{
+    NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (info);
+    NautilusTagManager *tag_manager;
+
+    tag_manager = nautilus_tag_manager_get ();
+
+    if (self->priv->starred)
+    {
+        nautilus_tag_manager_unstar_files (tag_manager,
+                                           G_OBJECT (info),
+                                           self->priv->files,
+                                           on_undo_favorite_tags_updated,
+                                           NULL);
+    }
+    else
+    {
+        nautilus_tag_manager_star_files (tag_manager,
+                                         G_OBJECT (info),
+                                         self->priv->files,
+                                         on_undo_favorite_tags_updated,
+                                         NULL);
+    }
+}
+
+static void
+nautilus_file_undo_info_favorites_set_property (GObject      *object,
+                                                guint         prop_id,
+                                                const GValue *value,
+                                                GParamSpec   *pspec)
+{
+    NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (object);
+
+    switch (prop_id)
+    {
+        case PROP_FILES:
+        {
+            self->priv->files = nautilus_file_list_copy (g_value_get_pointer (value));
+        }
+        break;
+
+        case PROP_STARRED:
+        {
+            self->priv->starred = g_value_get_boolean (value);
+        }
+        break;
+
+        default:
+        {
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+    }
+}
+
+static void
+nautilus_file_undo_info_favorites_init (NautilusFileUndoInfoFavorites *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_favorites_get_type (),
+                                              NautilusFileUndoInfoFavoritesDetails);
+}
+
+static void
+nautilus_file_undo_info_favorites_finalize (GObject *obj)
+{
+    NautilusFileUndoInfoFavorites *self = NAUTILUS_FILE_UNDO_INFO_FAVORITES (obj);
+
+    nautilus_file_list_free (self->priv->files);
+
+    G_OBJECT_CLASS (nautilus_file_undo_info_favorites_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_favorites_class_init (NautilusFileUndoInfoFavoritesClass *klass)
+{
+    GObjectClass *oclass = G_OBJECT_CLASS (klass);
+    NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+    oclass->finalize = nautilus_file_undo_info_favorites_finalize;
+    oclass->set_property = nautilus_file_undo_info_favorites_set_property;
+
+    iclass->undo_func = favorites_undo_func;
+    iclass->redo_func = favorites_redo_func;
+    iclass->strings_func = favorites_strings_func;
+
+    g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoFavoritesDetails));
+
+    g_object_class_install_property (oclass,
+                                     PROP_FILES,
+                                     g_param_spec_pointer ("files",
+                                                           "files",
+                                                           "The files for which to undo star/unstar",
+                                                           G_PARAM_WRITABLE |
+                                                           G_PARAM_CONSTRUCT_ONLY));
+    g_object_class_install_property (oclass,
+                                     PROP_STARRED,
+                                     g_param_spec_boolean ("starred",
+                                                           "starred",
+                                                           "Whether the files were starred or unstarred",
+                                                           FALSE,
+                                                           G_PARAM_WRITABLE |
+                                                           G_PARAM_CONSTRUCT_ONLY));
+
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_favorites_new (GList   *files,
+                                       gboolean starred)
+{
+    NautilusFileUndoInfoFavorites *self;
+
+    self = g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES,
+                         "op-type", NAUTILUS_FILE_UNDO_OP_FAVORITES,
+                         "item-count", g_list_length (files),
+                         "files", files,
+                         "starred", starred,
+                         NULL);
+
+    return NAUTILUS_FILE_UNDO_INFO (self);
+}
+
 /* trash */
 G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO)
 
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
index 630443f10..795a01f01 100644
--- a/src/nautilus-file-undo-operations.h
+++ b/src/nautilus-file-undo-operations.h
@@ -35,6 +35,7 @@ typedef enum {
        NAUTILUS_FILE_UNDO_OP_MOVE,
        NAUTILUS_FILE_UNDO_OP_RENAME,
        NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
+       NAUTILUS_FILE_UNDO_OP_FAVORITES,
        NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE,
        NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE,
        NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER,
@@ -217,6 +218,31 @@ void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatc
 void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
                                                         GList                           *new_files);
 
+/* favorite files */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES         (nautilus_file_undo_info_favorites_get_type ())
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavorites))
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES))
+#define NAUTILUS_IS_FILE_UNDO_INFO_FAVORITES_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES))
+#define NAUTILUS_FILE_UNDO_INFO_FAVORITES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_FAVORITES, NautilusFileUndoInfoFavoritesClass))
+
+typedef struct _NautilusFileUndoInfoFavorites      NautilusFileUndoInfoFavorites;
+typedef struct _NautilusFileUndoInfoFavoritesClass NautilusFileUndoInfoFavoritesClass;
+typedef struct _NautilusFileUndoInfoFavoritesDetails NautilusFileUndoInfoFavoritesDetails;
+
+struct _NautilusFileUndoInfoFavorites {
+    NautilusFileUndoInfo parent;
+    NautilusFileUndoInfoFavoritesDetails *priv;
+};
+
+struct _NautilusFileUndoInfoFavoritesClass {
+    NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_favorites_get_type (void) G_GNUC_CONST;
+NautilusFileUndoInfo *nautilus_file_undo_info_favorites_new (GList   *files,
+                                                             gboolean starred);
+
 /* trash */
 #define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH         (nautilus_file_undo_info_trash_get_type ())
 #define NAUTILUS_FILE_UNDO_INFO_TRASH(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrash))
diff --git a/src/nautilus-file-utilities.c b/src/nautilus-file-utilities.c
index 78d693eca..65b50fa14 100644
--- a/src/nautilus-file-utilities.c
+++ b/src/nautilus-file-utilities.c
@@ -322,7 +322,10 @@ nautilus_compute_title_for_location (GFile *location)
         {
             title = g_strdup (_("Other Locations"));
         }
-        else
+        else if (nautilus_file_is_favorite_location (file))
+        {
+            title = g_strdup (_("Starred"));
+        }
         {
             title = nautilus_file_get_description (file);
 
@@ -593,6 +596,19 @@ nautilus_is_search_directory (GFile *dir)
 }
 
 gboolean
+nautilus_is_favorite_directory (GFile *dir)
+{
+    g_autofree gchar *uri = NULL;
+
+    uri = g_file_get_uri (dir);
+
+    if (eel_uri_is_favorites (uri))
+        return TRUE;
+
+    return FALSE;
+}
+
+gboolean
 nautilus_is_other_locations_directory (GFile *dir)
 {
     g_autofree gchar *uri = NULL;
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
index e9b9a2218..1aad4d487 100644
--- a/src/nautilus-file-utilities.h
+++ b/src/nautilus-file-utilities.h
@@ -45,6 +45,7 @@ gboolean nautilus_is_home_directory_file             (GFile *dir,
                                                      const char *filename);
 gboolean nautilus_is_in_system_dir                   (GFile *location);
 gboolean nautilus_is_search_directory                (GFile *dir);
+gboolean nautilus_is_favorite_directory              (GFile *dir);
 gboolean nautilus_is_other_locations_directory       (GFile *dir);
 GMount * nautilus_get_mounted_mount_for_root         (GFile *location);
 
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
index 1949a90a2..ad890a0bc 100644
--- a/src/nautilus-file.c
+++ b/src/nautilus-file.c
@@ -40,6 +40,7 @@
 #include "nautilus-vfs-file.h"
 #include "nautilus-file-undo-operations.h"
 #include "nautilus-file-undo-manager.h"
+#include "nautilus-tag-manager.h"
 #include <eel/eel-debug.h>
 #include <eel/eel-glib-extensions.h>
 #include <eel/eel-gtk-extensions.h>
@@ -159,7 +160,8 @@ static GQuark attribute_name_q,
               attribute_where_q,
               attribute_link_target_q,
               attribute_volume_q,
-              attribute_free_space_q;
+              attribute_free_space_q,
+              attribute_favorite_q;
 
 static void     nautilus_file_info_iface_init (NautilusFileInfoIface *iface);
 static char *nautilus_file_get_owner_as_string (NautilusFile *file,
@@ -3660,6 +3662,39 @@ compare_by_type (NautilusFile *file_1,
     return result;
 }
 
+static int
+compare_by_favorite (NautilusFile *file_1,
+                     NautilusFile *file_2)
+{
+    NautilusTagManager *tag_manager;
+    g_autofree gchar *uri_1 = NULL;
+    g_autofree gchar *uri_2 = NULL;
+    gboolean file_1_is_favorite;
+    gboolean file_2_is_favorite;
+
+    tag_manager = nautilus_tag_manager_get ();
+
+    uri_1 = nautilus_file_get_uri (file_1);
+    uri_2 = nautilus_file_get_uri (file_2);
+
+    file_1_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager,
+                                                                uri_1);
+    file_2_is_favorite = nautilus_tag_manager_file_is_favorite (tag_manager,
+                                                                uri_2);
+    if (!!file_1_is_favorite == !!file_2_is_favorite)
+    {
+        return 0;
+    }
+    else if (file_1_is_favorite && !file_2_is_favorite)
+    {
+        return -1;
+    }
+    else
+    {
+        return 1;
+    }
+}
+
 static Knowledge
 get_search_relevance (NautilusFile *file,
                       gdouble      *relevance_out)
@@ -3863,6 +3898,16 @@ nautilus_file_compare_for_sort (NautilusFile         *file_1,
             }
             break;
 
+            case NAUTILUS_FILE_SORT_BY_FAVORITE:
+            {
+                result = compare_by_favorite (file_1, file_2);
+                if (result == 0)
+                {
+                    result = compare_by_full_path (file_1, file_2);
+                }
+            }
+            break;
+
             case NAUTILUS_FILE_SORT_BY_MTIME:
             {
                 result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED);
@@ -3967,6 +4012,13 @@ nautilus_file_compare_for_sort_by_attribute_q   (NautilusFile *file_1,
                                                directories_first,
                                                reversed);
     }
+    else if (attribute == attribute_favorite_q)
+    {
+        return nautilus_file_compare_for_sort (file_1, file_2,
+                                               NAUTILUS_FILE_SORT_BY_FAVORITE,
+                                               directories_first,
+                                               reversed);
+    }
     else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || 
attribute == attribute_date_modified_with_time_q || attribute == attribute_date_modified_full_q)
     {
         return nautilus_file_compare_for_sort (file_1, file_2,
@@ -4576,6 +4628,11 @@ nautilus_file_peek_display_name (NautilusFile *file)
 char *
 nautilus_file_get_display_name (NautilusFile *file)
 {
+    if (nautilus_file_is_other_locations (file))
+        return g_strdup (_("Other Locations"));
+    if (nautilus_file_is_favorite_location (file))
+        return g_strdup (_("Starred"));
+
     return g_strdup (nautilus_file_peek_display_name (file));
 }
 
@@ -7608,6 +7665,11 @@ nautilus_file_get_string_attribute_with_default_q (NautilusFile *file,
         /* If n/a */
         return g_strdup ("");
     }
+    if (attribute_q == attribute_favorite_q)
+    {
+        /* If n/a */
+        return g_strdup ("");
+    }
 
     /* Fallback, use for both unknown attributes and attributes
      * for which we have no more appropriate default.
@@ -8298,6 +8360,23 @@ nautilus_file_is_in_recent (NautilusFile *file)
     return nautilus_directory_is_in_recent (file->details->directory);
 }
 
+/**
+ * nautilus_file_is_in_starred
+ *
+ * Check if this file is a file in Starred.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is in Starred.
+ *
+ **/
+gboolean
+nautilus_file_is_in_starred (NautilusFile *file)
+{
+    g_assert (NAUTILUS_IS_FILE (file));
+
+    return nautilus_directory_is_in_starred (file->details->directory);
+}
+
 static const gchar * const remote_types[] =
 {
     "afp",
@@ -8357,6 +8436,27 @@ nautilus_file_is_other_locations (NautilusFile *file)
 }
 
 /**
+ * nautilus_file_is_favorite_location
+ *
+ * Check if this file is the Favorite location.
+ * @file: NautilusFile representing the file in question.
+ *
+ * Returns: TRUE if @file is the Favorite location.
+ *
+ **/
+gboolean
+nautilus_file_is_favorite_location (NautilusFile *file)
+{
+    g_autofree gchar *uri = NULL;
+
+    g_assert (NAUTILUS_IS_FILE (file));
+
+    uri = nautilus_file_get_uri (file);
+
+    return eel_uri_is_favorites (uri);
+}
+
+/**
  * nautilus_file_is_in_admin
  *
  * Check if this file is using admin backend.
@@ -9371,6 +9471,7 @@ nautilus_file_class_init (NautilusFileClass *class)
     attribute_link_target_q = g_quark_from_static_string ("link_target");
     attribute_volume_q = g_quark_from_static_string ("volume");
     attribute_free_space_q = g_quark_from_static_string ("free_space");
+    attribute_favorite_q = g_quark_from_static_string ("favorite");
 
     G_OBJECT_CLASS (class)->finalize = finalize;
     G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor;
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
index 3a83bd4de..464c66e0e 100644
--- a/src/nautilus-file.h
+++ b/src/nautilus-file.h
@@ -55,6 +55,7 @@ typedef enum {
        NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
        NAUTILUS_FILE_SORT_BY_SIZE,
        NAUTILUS_FILE_SORT_BY_TYPE,
+       NAUTILUS_FILE_SORT_BY_FAVORITE,
        NAUTILUS_FILE_SORT_BY_MTIME,
         NAUTILUS_FILE_SORT_BY_ATIME,
        NAUTILUS_FILE_SORT_BY_TRASHED_TIME,
@@ -215,9 +216,11 @@ gboolean           nautilus_file_is_archive                        (NautilusFile         
          *file);
 gboolean                nautilus_file_is_in_search                      (NautilusFile                   
*file);
 gboolean                nautilus_file_is_in_trash                       (NautilusFile                   
*file);
 gboolean                nautilus_file_is_in_recent                      (NautilusFile                   
*file);
+gboolean                nautilus_file_is_in_starred                     (NautilusFile                   
*file);
 gboolean                nautilus_file_is_in_admin                       (NautilusFile                   
*file);
 gboolean                nautilus_file_is_remote                         (NautilusFile                   
*file);
 gboolean                nautilus_file_is_other_locations                (NautilusFile                   
*file);
+gboolean                nautilus_file_is_favorite_location              (NautilusFile                   
*file);
 gboolean               nautilus_file_is_home                           (NautilusFile                   
*file);
 gboolean                nautilus_file_is_desktop_directory              (NautilusFile                   
*file);
 gboolean                nautilus_file_is_child_of_desktop_directory     (NautilusFile                   
*file);
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index e45021e00..80db332c8 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -42,6 +42,7 @@
 #include "nautilus-window.h"
 #include "nautilus-toolbar.h"
 #include "nautilus-view.h"
+#include "nautilus-tag-manager.h"
 
 #ifdef HAVE_X11_XF86KEYSYM_H
 #include <X11/XF86keysym.h>
@@ -70,6 +71,7 @@
 #include <libnautilus-extension/nautilus-menu-provider.h>
 #include "nautilus-clipboard.h"
 #include "nautilus-search-directory.h"
+#include "nautilus-favorite-directory.h"
 #include "nautilus-directory.h"
 #include "nautilus-dnd.h"
 #include "nautilus-file-attributes.h"
@@ -254,6 +256,7 @@ typedef struct
     GtkWidget *folder_is_empty_widget;
     GtkWidget *trash_is_empty_widget;
     GtkWidget *no_search_results_widget;
+    GtkWidget *starred_is_empty_widget;
 
     /* Floating bar */
     guint floating_bar_set_status_timeout_id;
@@ -273,6 +276,9 @@ typedef struct
 
     gulong stop_signal_handler;
     gulong reload_signal_handler;
+
+    GCancellable *favorite_cancellable;
+    NautilusTagManager *tag_manager;
 } NautilusFilesViewPrivate;
 
 typedef struct
@@ -748,13 +754,27 @@ showing_recent_directory (NautilusFilesView *view)
 }
 
 static gboolean
+showing_starred_directory (NautilusFilesView *view)
+{
+    NautilusFile *file;
+
+    file = nautilus_files_view_get_directory_as_file (view);
+    if (file != NULL)
+    {
+        return nautilus_file_is_in_starred (file);
+    }
+    return FALSE;
+}
+
+static gboolean
 nautilus_files_view_supports_creating_files (NautilusFilesView *view)
 {
     g_return_val_if_fail (NAUTILUS_IS_FILES_VIEW (view), FALSE);
 
     return !nautilus_files_view_is_read_only (view)
            && !showing_trash_directory (view)
-           && !showing_recent_directory (view);
+           && !showing_recent_directory (view)
+           && !showing_starred_directory (view);
 }
 
 static gboolean
@@ -1562,6 +1582,46 @@ action_delete (GSimpleAction *action,
 }
 
 static void
+action_star (GSimpleAction *action,
+             GVariant      *state,
+             gpointer       user_data)
+{
+    NautilusFilesView *view;
+    GList *selection;
+    NautilusFilesViewPrivate *priv;
+
+    view = NAUTILUS_FILES_VIEW (user_data);
+    priv = nautilus_files_view_get_instance_private (view);
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+    nautilus_tag_manager_star_files (priv->tag_manager,
+                                     G_OBJECT (view),
+                                     selection,
+                                     NULL,
+                                     priv->favorite_cancellable);
+}
+
+static void
+action_unstar (GSimpleAction *action,
+               GVariant      *state,
+               gpointer       user_data)
+{
+    NautilusFilesView *view;
+    GList *selection;
+    NautilusFilesViewPrivate *priv;
+
+    view = NAUTILUS_FILES_VIEW (user_data);
+    priv = nautilus_files_view_get_instance_private (view);
+    selection = nautilus_view_get_selection (NAUTILUS_VIEW (view));
+
+    nautilus_tag_manager_unstar_files (priv->tag_manager,
+                                       G_OBJECT (view),
+                                       selection,
+                                       NULL,
+                                       priv->favorite_cancellable);
+}
+
+static void
 action_restore_from_trash (GSimpleAction *action,
                            GVariant      *state,
                            gpointer       user_data)
@@ -3226,6 +3286,9 @@ nautilus_files_view_finalize (GObject *object)
     g_hash_table_destroy (priv->non_ready_files);
     g_hash_table_destroy (priv->pending_reveal);
 
+    g_cancellable_cancel (priv->favorite_cancellable);
+    g_clear_object (&priv->favorite_cancellable);
+
     G_OBJECT_CLASS (nautilus_files_view_parent_class)->finalize (object);
 }
 
@@ -3474,6 +3537,10 @@ nautilus_files_view_set_location (NautilusView *view,
         set_search_query_internal (files_view, previous_query, base_model);
         g_object_unref (previous_query);
     }
+    else if (NAUTILUS_IS_FAVORITE_DIRECTORY (directory))
+    {
+        load_directory (NAUTILUS_FILES_VIEW (view), directory);
+    }
     else
     {
         load_directory (NAUTILUS_FILES_VIEW (view), directory);
@@ -3514,6 +3581,7 @@ real_check_empty_states (NautilusFilesView *view)
     gtk_widget_hide (priv->no_search_results_widget);
     gtk_widget_hide (priv->folder_is_empty_widget);
     gtk_widget_hide (priv->trash_is_empty_widget);
+    gtk_widget_hide (priv->starred_is_empty_widget);
 
     if (!priv->loading &&
         nautilus_files_view_is_empty (view))
@@ -3528,6 +3596,10 @@ real_check_empty_states (NautilusFilesView *view)
         {
             gtk_widget_show (priv->trash_is_empty_widget);
         }
+        else if (eel_uri_is_favorites (uri))
+        {
+            gtk_widget_show (priv->starred_is_empty_widget);
+        }
         else
         {
             gtk_widget_show (priv->folder_is_empty_widget);
@@ -7014,6 +7086,8 @@ const GActionEntry view_entries[] =
     { "copy-to", action_copy_to},
     { "move-to-trash", action_move_to_trash},
     { "delete-from-trash", action_delete },
+    { "star", action_star},
+    { "unstar", action_unstar},
     /* We separate the shortcut and the menu item since we want the shortcut
      * to always be available, but we don't want the menu item shown if not
      * completely necesary. Since the visibility of the menu item is based on
@@ -7093,6 +7167,7 @@ on_clipboard_contents_received (GtkClipboard     *clipboard,
     gboolean settings_show_create_link;
     gboolean is_read_only;
     gboolean selection_contains_recent;
+    gboolean selection_contains_starred;
     GAction *action;
 
     view = NAUTILUS_FILES_VIEW (user_data);
@@ -7110,9 +7185,10 @@ on_clipboard_contents_received (GtkClipboard     *clipboard,
                                                         NAUTILUS_PREFERENCES_SHOW_CREATE_LINK);
     is_read_only = nautilus_files_view_is_read_only (view);
     selection_contains_recent = showing_recent_directory (view);
+    selection_contains_starred = showing_starred_directory (view);
     can_link_from_copied_files = !nautilus_clipboard_is_cut_from_selection_data (selection_data) &&
-                                 !selection_contains_recent && !is_read_only &&
-                                 gtk_selection_data_get_length (selection_data) > 0;
+                                 !selection_contains_recent && !selection_contains_starred &&
+                                 !is_read_only && gtk_selection_data_get_length (selection_data) > 0;
 
     action = g_action_map_lookup_action (G_ACTION_MAP (priv->view_action_group),
                                          "create-link");
@@ -7399,6 +7475,7 @@ real_update_actions_state (NautilusFilesView *view)
     gboolean selection_contains_desktop_or_home_dir;
     gboolean selection_contains_recent;
     gboolean selection_contains_search;
+    gboolean selection_contains_starred;
     gboolean selection_all_in_trash;
     gboolean selection_is_read_only;
     gboolean can_create_files;
@@ -7423,6 +7500,9 @@ real_update_actions_state (NautilusFilesView *view)
     gboolean settings_show_delete_permanently;
     gboolean settings_show_create_link;
     GDriveStartStopType start_stop_type;
+    gboolean show_star;
+    gboolean show_unstar;
+    gchar *uri;
 
     priv = nautilus_files_view_get_instance_private (view);
 
@@ -7433,6 +7513,7 @@ real_update_actions_state (NautilusFilesView *view)
     selection_contains_special_link = nautilus_files_view_special_link_in_selection (view, selection);
     selection_contains_desktop_or_home_dir = desktop_or_home_dir_in_selection (selection);
     selection_contains_recent = showing_recent_directory (view);
+    selection_contains_starred = showing_starred_directory (view);
     selection_contains_search = nautilus_view_is_searching (NAUTILUS_VIEW (view));
     selection_is_read_only = selection_count == 1 &&
                              (!nautilus_file_can_write (NAUTILUS_FILE (selection->data)) &&
@@ -7454,8 +7535,10 @@ real_update_actions_state (NautilusFilesView *view)
         !selection_contains_desktop_or_home_dir;
     can_copy_files = selection_count != 0
                      && !selection_contains_special_link;
-    can_move_files = can_delete_files && !selection_contains_recent;
+    can_move_files = can_delete_files && !selection_contains_recent &&
+                     !selection_contains_starred;
     can_paste_files_into = (!selection_contains_recent &&
+                            !selection_contains_starred &&
                             selection_count == 1 &&
                             can_paste_into_file (NAUTILUS_FILE (selection->data)));
     can_extract_files = selection_count != 0 &&
@@ -7471,7 +7554,8 @@ real_update_actions_state (NautilusFilesView *view)
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "new-folder-with-selection");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
-                                 can_create_files && can_delete_files && (selection_count > 1) && 
!selection_contains_recent);
+                                 can_create_files && can_delete_files && (selection_count > 1) && 
!selection_contains_recent
+                                 && !selection_contains_starred);
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "rename");
@@ -7519,7 +7603,8 @@ real_update_actions_state (NautilusFilesView *view)
 
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  selection_count == 1 &&
-                                 (selection_contains_recent || selection_contains_search));
+                                 (selection_contains_recent || selection_contains_search ||
+                                  selection_contains_starred));
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "new-folder");
@@ -7585,14 +7670,16 @@ real_update_actions_state (NautilusFilesView *view)
                                          "delete-permanently-menu-item");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  can_delete_files && !can_trash_files &&
-                                 !selection_all_in_trash && !selection_contains_recent);
+                                 !selection_all_in_trash && !selection_contains_recent &&
+                                 !selection_contains_starred);
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "permanent-delete-permanently-menu-item");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  can_delete_files && can_trash_files &&
                                  settings_show_delete_permanently &&
-                                 !selection_all_in_trash && !selection_contains_recent);
+                                 !selection_all_in_trash && !selection_contains_recent &&
+                                 !selection_contains_starred);
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "remove-from-recent");
@@ -7602,7 +7689,8 @@ real_update_actions_state (NautilusFilesView *view)
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "cut");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
-                                 can_move_files && !selection_contains_recent);
+                                 can_move_files && !selection_contains_recent &&
+                                 !selection_contains_starred);
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "copy");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
@@ -7620,7 +7708,8 @@ real_update_actions_state (NautilusFilesView *view)
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "move-to");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
-                                 can_move_files && !selection_contains_recent);
+                                 can_move_files && !selection_contains_recent &&
+                                 !selection_contains_starred);
 
     /* Drive menu */
     show_mount = (selection != NULL);
@@ -7697,13 +7786,14 @@ real_update_actions_state (NautilusFilesView *view)
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "paste");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
-                                 !is_read_only && !selection_contains_recent);
+                                 !is_read_only && !selection_contains_recent &&
+                                 !selection_contains_starred);
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "paste-into");
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  !selection_is_read_only && !selection_contains_recent &&
-                                 can_paste_files_into);
+                                 can_paste_files_into && !selection_contains_starred);
 
     action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
                                          "properties");
@@ -7714,6 +7804,7 @@ real_update_actions_state (NautilusFilesView *view)
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  can_create_files &&
                                  !selection_contains_recent &&
+                                 !selection_contains_starred &&
                                  priv->templates_present);
 
     /* Actions that are related to the clipboard need request, request the data
@@ -7757,6 +7848,38 @@ real_update_actions_state (NautilusFilesView *view)
     g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
                                  !nautilus_files_view_is_empty (view));
 
+    show_star = (selection != NULL);
+    show_unstar = (selection != NULL);
+    for (l = selection; l != NULL; l = l->next)
+    {
+        file = NAUTILUS_FILE (l->data);
+        uri = nautilus_file_get_uri (file);
+
+        if (!show_star && !show_unstar)
+        {
+            break;
+        }
+
+        if (nautilus_tag_manager_file_is_favorite (priv->tag_manager, uri))
+        {
+            show_star = FALSE;
+        }
+        else
+        {
+            show_unstar = FALSE;
+        }
+
+        g_free (uri);
+    }
+
+    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+                                         "star");
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_star);
+
+    action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
+                                         "unstar");
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), show_unstar);
+
     nautilus_file_list_free (selection);
 }
 
@@ -8031,7 +8154,8 @@ static void
 update_background_menu (NautilusFilesView *view)
 {
     if (nautilus_files_view_supports_creating_files (view) &&
-        !showing_recent_directory (view))
+        !showing_recent_directory (view) &&
+        !showing_starred_directory (view))
     {
         update_templates_menu (view);
     }
@@ -9601,6 +9725,14 @@ nautilus_files_view_init (NautilusFilesView *view)
                                           TRUE);
     g_object_unref (builder);
 
+    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-starred-is-empty.ui");
+    priv->starred_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "starred_is_empty"));
+    gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->starred_is_empty_widget);
+    gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (priv->overlay),
+                                          priv->starred_is_empty_widget,
+                                          TRUE);
+    g_object_unref (builder);
+
     builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-trash-is-empty.ui");
     priv->trash_is_empty_widget = GTK_WIDGET (gtk_builder_get_object (builder, "trash_is_empty"));
     gtk_overlay_add_overlay (GTK_OVERLAY (priv->overlay), priv->trash_is_empty_widget);
@@ -9742,6 +9874,9 @@ nautilus_files_view_init (NautilusFilesView *view)
      * changed */
     nautilus_application_set_accelerator (app, "view.show-move-to-trash-shortcut-changed-dialog", 
"<control>Delete");
 
+    priv->favorite_cancellable = g_cancellable_new ();
+    priv->tag_manager = nautilus_tag_manager_get ();
+
     nautilus_profile_end (NULL);
 }
 
diff --git a/src/nautilus-list-view-private.h b/src/nautilus-list-view-private.h
index 2638c36ec..ac0919f62 100644
--- a/src/nautilus-list-view-private.h
+++ b/src/nautilus-list-view-private.h
@@ -21,6 +21,7 @@
 #include "nautilus-list-model.h"
 #include "nautilus-tree-view-drag-dest.h"
 #include "nautilus-dnd.h"
+#include "nautilus-tag-manager.h"
 
 struct NautilusListViewDetails {
   GtkTreeView *tree_view;
@@ -66,5 +67,8 @@ struct NautilusListViewDetails {
   GQuark last_sort_attr;
 
   GRegex *regex;
+
+  NautilusTagManager *tag_manager;
+  GCancellable *favorite_cancellable;
 };
 
diff --git a/src/nautilus-list-view.c b/src/nautilus-list-view.c
index 86f52c093..1de1b4b6a 100644
--- a/src/nautilus-list-view.c
+++ b/src/nautilus-list-view.c
@@ -32,6 +32,7 @@
 #include "nautilus-toolbar.h"
 #include "nautilus-list-view-dnd.h"
 #include "nautilus-view.h"
+#include "nautilus-tag-manager.h"
 
 #include <string.h>
 #include <eel/eel-vfs-extensions.h>
@@ -457,6 +458,90 @@ row_activated_callback (GtkTreeView       *treeview,
     activate_selected_items (view);
 }
 
+gboolean
+check_starred_status (GtkTreeModel *model,
+                      GtkTreePath  *path,
+                      GtkTreeIter  *iter,
+                      gpointer      data)
+{
+    NautilusFile *file;
+    GList *l;
+    GList *changed_files;
+
+    changed_files = data;
+
+    gtk_tree_model_get (GTK_TREE_MODEL (model),
+                        iter,
+                        NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+                        -1);
+
+    if (!file)
+    {
+        return FALSE;
+    }
+
+    for (l = changed_files; l != NULL; l = l->next)
+    {
+        if (nautilus_file_compare_location (NAUTILUS_FILE (l->data), file) == 0)
+        {
+            gtk_tree_model_row_changed (model, path, iter);
+        }
+    }
+
+    nautilus_file_unref (file);
+
+    return FALSE;
+}
+
+static void
+on_favorites_files_changed (NautilusTagManager *tag_manager,
+                            GList              *changed_files,
+                            gpointer            user_data)
+{
+    NautilusListView *list_view;
+
+    list_view = NAUTILUS_LIST_VIEW (user_data);
+
+    gtk_tree_model_foreach (GTK_TREE_MODEL (list_view->details->model),
+                            check_starred_status,
+                            changed_files);
+}
+
+static void
+on_star_cell_renderer_clicked (GtkTreePath      *path,
+                               NautilusListView *list_view)
+{
+    NautilusListModel *list_model;
+    NautilusFile *file;
+    g_autofree gchar *uri = NULL;
+    GList *selection;
+
+    list_model = list_view->details->model;
+
+    file = nautilus_list_model_file_for_path (list_model, path);
+    uri = nautilus_file_get_uri (file);
+    selection = g_list_prepend (NULL, file);
+
+    if (nautilus_tag_manager_file_is_favorite (list_view->details->tag_manager, uri))
+    {
+        nautilus_tag_manager_unstar_files (list_view->details->tag_manager,
+                                           G_OBJECT (list_view),
+                                           selection,
+                                           NULL,
+                                           list_view->details->favorite_cancellable);
+    }
+    else
+    {
+        nautilus_tag_manager_star_files (list_view->details->tag_manager,
+                                         G_OBJECT (list_view),
+                                         selection,
+                                         NULL,
+                                         list_view->details->favorite_cancellable);
+    }
+
+    nautilus_file_list_free (selection);
+}
+
 static gboolean
 button_press_callback (GtkWidget      *widget,
                        GdkEventButton *event,
@@ -703,6 +788,32 @@ button_press_callback (GtkWidget      *widget,
         }
     }
 
+    if (is_simple_click)
+    {
+        GtkTreeViewColumn *column = NULL;
+        gdouble cell_middle_x;
+
+        gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->details->tree_view),
+                                       event->x,
+                                       event->y,
+                                       NULL,
+                                       &column,
+                                       NULL,
+                                       NULL);
+
+        if (g_strcmp0 (gtk_tree_view_column_get_title (column), "Star") == 0)
+        {
+            cell_middle_x = gtk_tree_view_column_get_width (column) / 2 +
+                            gtk_tree_view_column_get_x_offset (column);
+
+            if (event->x > cell_middle_x - 10 &&
+                event->x < cell_middle_x + 10)
+            {
+                on_star_cell_renderer_clicked (path, view);
+            }
+        }
+    }
+
     gtk_tree_path_free (path);
 
     /* We chained to the default handler in this method, so never
@@ -1547,6 +1658,45 @@ apply_columns_settings (NautilusListView  *list_view,
 }
 
 static void
+favorite_cell_data_func (GtkTreeViewColumn *column,
+                         GtkCellRenderer   *renderer,
+                         GtkTreeModel      *model,
+                         GtkTreeIter       *iter,
+                         NautilusListView  *view)
+{
+    g_autofree gchar *text = NULL;
+    g_autofree gchar *uri = NULL;
+    NautilusFile *file;
+
+    gtk_tree_model_get (model, iter,
+                        view->details->file_name_column_num, &text,
+                        -1);
+
+    gtk_tree_model_get (GTK_TREE_MODEL (model),
+                        iter,
+                        NAUTILUS_LIST_MODEL_FILE_COLUMN, &file,
+                        -1);
+
+    uri = nautilus_file_get_uri (file);
+
+    if (nautilus_tag_manager_file_is_favorite (view->details->tag_manager, uri))
+    {
+        g_object_set (renderer,
+                      "icon-name", "starred-symbolic",
+                      NULL);
+    }
+    else
+    {
+        g_object_set (renderer,
+                      "icon-name", "non-starred-symbolic",
+                      NULL);
+
+    }
+
+    nautilus_file_unref (file);
+}
+
+static void
 filename_cell_data_func (GtkTreeViewColumn *column,
                          GtkCellRenderer   *renderer,
                          GtkTreeModel      *model,
@@ -2040,13 +2190,34 @@ create_and_set_up_tree_view (NautilusListView *view)
         }
         else
         {
-            /* We need to use libgd */
-            cell = gd_styled_text_renderer_new ();
-            /* FIXME: should be just dim-label.
-             * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
-             */
-            gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
-                                               "nautilus-list-dim-label");
+            if (g_strcmp0 (name, "favorite") == 0)
+            {
+                cell = gtk_cell_renderer_pixbuf_new ();
+                g_object_set (cell,
+                              "icon-name", "non-starred-symbolic",
+                              "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+                              NULL);
+
+                column = gtk_tree_view_column_new_with_attributes (label,
+                                                                   cell,
+                                                                   NULL);
+            }
+            else
+            {
+                /* We need to use libgd */
+                cell = gd_styled_text_renderer_new ();
+                /* FIXME: should be just dim-label.
+                 * See https://bugzilla.gnome.org/show_bug.cgi?id=744397
+                 */
+                gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (cell),
+                                                   "nautilus-list-dim-label");
+
+                column = gtk_tree_view_column_new_with_attributes (label,
+                                                                   cell,
+                                                                   "text", column_num,
+                                                                   NULL);
+            }
+
 
             g_object_set (cell,
                           "xalign", xalign,
@@ -2060,10 +2231,7 @@ create_and_set_up_tree_view (NautilusListView *view)
             }
             view->details->cells = g_list_append (view->details->cells,
                                                   cell);
-            column = gtk_tree_view_column_new_with_attributes (label,
-                                                               cell,
-                                                               "text", column_num,
-                                                               NULL);
+
             gtk_tree_view_append_column (view->details->tree_view, column);
             gtk_tree_view_column_set_sort_column_id (column, column_num);
             g_hash_table_insert (view->details->columns,
@@ -2090,6 +2258,12 @@ create_and_set_up_tree_view (NautilusListView *view)
                                                          (GtkTreeCellDataFunc) 
trash_orig_path_cell_data_func,
                                                          view, NULL);
             }
+            else if (!strcmp (name, "favorite"))
+            {
+                gtk_tree_view_column_set_cell_data_func (column, cell,
+                                                         (GtkTreeCellDataFunc) favorite_cell_data_func,
+                                                         view, NULL);
+            }
         }
         g_free (name);
         g_free (label);
@@ -3378,6 +3552,13 @@ nautilus_list_view_finalize (GObject *object)
 
     g_regex_unref (list_view->details->regex);
 
+    g_cancellable_cancel (list_view->details->favorite_cancellable);
+    g_clear_object (&list_view->details->favorite_cancellable);
+
+    g_signal_handlers_disconnect_by_func (list_view->details->tag_manager,
+                                          on_favorites_files_changed,
+                                          list_view);
+
     g_free (list_view->details);
 
     G_OBJECT_CLASS (nautilus_list_view_parent_class)->finalize (object);
@@ -3664,6 +3845,14 @@ nautilus_list_view_init (NautilusListView *list_view)
                                         "zoom-to-level", g_variant_new_int32 (get_default_zoom_level ()));
 
     list_view->details->regex = g_regex_new ("\\R+", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+
+    list_view->details->tag_manager = nautilus_tag_manager_get ();
+    list_view->details->favorite_cancellable = g_cancellable_new ();
+
+    g_signal_connect (list_view->details->tag_manager,
+                      "favorites-changed",
+                      (GCallback) on_favorites_files_changed,
+                      list_view);
 }
 
 NautilusFilesView *
diff --git a/src/nautilus-pathbar.c b/src/nautilus-pathbar.c
index 1c54cc9f5..dc8ee6dbe 100644
--- a/src/nautilus-pathbar.c
+++ b/src/nautilus-pathbar.c
@@ -47,7 +47,8 @@ typedef enum
     OTHER_LOCATIONS_BUTTON,
     ROOT_BUTTON,
     HOME_BUTTON,
-    MOUNT_BUTTON
+    MOUNT_BUTTON,
+    FAVORITE_LOCATION_BUTTON
 } ButtonType;
 
 #define BUTTON_DATA(x) ((ButtonData *) (x))
@@ -454,6 +455,11 @@ get_dir_name (ButtonData *button_data)
             return _("Other Locations");
         }
 
+        case FAVORITE_LOCATION_BUTTON:
+        {
+            return _("Starred");
+        }
+
         default:
             return button_data->dir_name;
     }
@@ -1924,6 +1930,10 @@ setup_button_type (ButtonData      *button_data,
 
         g_object_unref (mount);
     }
+    else if (nautilus_is_favorite_directory (location))
+    {
+        button_data->type = FAVORITE_LOCATION_BUTTON;
+    }
     else
     {
         button_data->type = NORMAL_BUTTON;
diff --git a/src/nautilus-query.c b/src/nautilus-query.c
index 59600d195..62f7badbf 100644
--- a/src/nautilus-query.c
+++ b/src/nautilus-query.c
@@ -46,6 +46,7 @@ struct _NautilusQuery
     GPtrArray *date_range;
     NautilusQuerySearchType search_type;
     NautilusQuerySearchContent search_content;
+    gboolean search_favorite;
 
     gboolean searching;
     gboolean recursive;
@@ -343,6 +344,7 @@ nautilus_query_init (NautilusQuery *query)
     query->location = g_file_new_for_path (g_get_home_dir ());
     query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
     query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE;
+    query->search_favorite = FALSE;
     g_mutex_init (&query->prepared_words_mutex);
 }
 
@@ -550,6 +552,23 @@ nautilus_query_set_search_content (NautilusQuery              *query,
     }
 }
 
+gboolean
+nautilus_query_get_search_favorite (NautilusQuery *query)
+{
+    g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
+
+    return query->search_favorite;
+}
+
+void
+nautilus_query_set_search_favorite (NautilusQuery *query,
+                                    gboolean       search_favorite)
+{
+    g_return_if_fail (NAUTILUS_IS_QUERY (query));
+
+    query->search_favorite = search_favorite;
+}
+
 NautilusQuerySearchType
 nautilus_query_get_search_type (NautilusQuery *query)
 {
diff --git a/src/nautilus-query.h b/src/nautilus-query.h
index 2264f505f..5367844b2 100644
--- a/src/nautilus-query.h
+++ b/src/nautilus-query.h
@@ -59,6 +59,10 @@ NautilusQuerySearchContent nautilus_query_get_search_content (NautilusQuery *que
 void                       nautilus_query_set_search_content (NautilusQuery              *query,
                                                               NautilusQuerySearchContent  content);
 
+gboolean nautilus_query_get_search_favorite (NautilusQuery *query);
+void     nautilus_query_set_search_favorite (NautilusQuery *query,
+                                             gboolean       search_favorite);
+
 NautilusQuerySearchType nautilus_query_get_search_type (NautilusQuery *query);
 void                    nautilus_query_set_search_type (NautilusQuery           *query,
                                                         NautilusQuerySearchType  type);
diff --git a/src/nautilus-search-engine-model.c b/src/nautilus-search-engine-model.c
index b00f3f7c5..f6330daaf 100644
--- a/src/nautilus-search-engine-model.c
+++ b/src/nautilus-search-engine-model.c
@@ -27,6 +27,7 @@
 #include "nautilus-directory-private.h"
 #include "nautilus-file.h"
 #include "nautilus-ui-utilities.h"
+#include "nautilus-tag-manager.h"
 #define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
 #include "nautilus-debug.h"
 
@@ -139,6 +140,7 @@ model_directory_ready_cb (NautilusDirectory *directory,
     GDateTime *initial_date;
     GDateTime *end_date;
     GPtrArray *date_range;
+    NautilusTagManager *tag_manager;
 
     files = nautilus_directory_get_file_list (directory);
     mime_types = nautilus_query_get_mime_types (model->query);
@@ -191,12 +193,27 @@ model_directory_ready_cb (NautilusDirectory *directory,
             g_ptr_array_unref (date_range);
         }
 
+        if (nautilus_query_get_search_favorite (model->query))
+        {
+            tag_manager = nautilus_tag_manager_get ();
+
+            uri = nautilus_file_get_uri (file);
+
+            if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri))
+            {
+                found = FALSE;
+            }
+
+            g_free (uri);
+        }
+
         if (found)
         {
             uri = nautilus_file_get_uri (file);
             hit = nautilus_search_hit_new (uri);
             nautilus_search_hit_set_fts_rank (hit, match);
             hits = g_list_prepend (hits, hit);
+
             g_free (uri);
         }
 
diff --git a/src/nautilus-search-engine-simple.c b/src/nautilus-search-engine-simple.c
index ea2dbe364..fee7d2633 100644
--- a/src/nautilus-search-engine-simple.c
+++ b/src/nautilus-search-engine-simple.c
@@ -24,6 +24,7 @@
 #include "nautilus-search-provider.h"
 #include "nautilus-search-engine-simple.h"
 #include "nautilus-ui-utilities.h"
+#include "nautilus-tag-manager.h"
 #define DEBUG_FLAG NAUTILUS_DEBUG_SEARCH
 #include "nautilus-debug.h"
 
@@ -222,7 +223,8 @@ visit_directory (GFile            *dir,
     GPtrArray *date_range;
     GDateTime *initial_date;
     GDateTime *end_date;
-
+    NautilusTagManager *tag_manager;
+    gchar *uri;
 
     enumerator = g_file_enumerate_children (dir,
                                             data->mime_types != NULL ?
@@ -299,11 +301,24 @@ visit_directory (GFile            *dir,
             g_ptr_array_unref (date_range);
         }
 
+        if (nautilus_query_get_search_favorite (data->query))
+        {
+            tag_manager = nautilus_tag_manager_get ();
+
+            uri = g_file_get_uri (child);
+
+            if (!nautilus_tag_manager_file_is_favorite (tag_manager, uri))
+            {
+                found = FALSE;
+            }
+
+            g_free (uri);
+        }
+
         if (found)
         {
             NautilusSearchHit *hit;
             GDateTime *date;
-            char *uri;
 
             uri = g_file_get_uri (child);
             hit = nautilus_search_hit_new (uri);
diff --git a/src/nautilus-search-engine-tracker.c b/src/nautilus-search-engine-tracker.c
index 4c917ddf5..5954c1fa6 100644
--- a/src/nautilus-search-engine-tracker.c
+++ b/src/nautilus-search-engine-tracker.c
@@ -353,6 +353,11 @@ nautilus_search_engine_tracker_start (NautilusSearchProvider *provider)
         g_string_append_printf (sparql, "; fts:match '\"%s\"*'", search_text);
     }
 
+    if (nautilus_query_get_search_favorite (tracker->query))
+    {
+        g_string_append_printf (sparql, "; nao:hasTag nao:predefined-tag-favorite");
+    }
+
     if (mime_count > 0)
     {
         g_string_append (sparql, "; nie:mimeType ?mime");
diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c
new file mode 100644
index 000000000..be8797e79
--- /dev/null
+++ b/src/nautilus-tag-manager.c
@@ -0,0 +1,884 @@
+/* nautilus-tag-manager.c
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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/>.
+ */
+
+#include "nautilus-tag-manager.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
+
+#include <tracker-sparql.h>
+
+struct _NautilusTagManager
+{
+    GObject object;
+
+    TrackerNotifier *notifier;
+    GError *notifier_error;
+
+    GHashTable *favorite_files;
+    GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (NautilusTagManager, nautilus_tag_manager, G_TYPE_OBJECT);
+
+static NautilusTagManager *tag_manager = NULL;
+
+typedef enum
+{
+    GET_FAVORITE_FILES,
+    GET_IDS_FOR_URLS
+} OperationType;
+
+typedef struct
+{
+    GTask *task;
+    GList *selection;
+    GHashTable *ids;
+    GObject *object;
+    GAsyncReadyCallback callback;
+    GCancellable *cancellable;
+} InsertTaskData;
+
+typedef struct
+{
+    NautilusTagManager *tag_manager;
+    GTask *task;
+    GList *selection;
+    gboolean star;
+    GHashTable *ids;
+} UpdateData;
+
+enum
+{
+    FAVORITES_CHANGED,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static const gchar*
+nautilus_tag_manager_file_with_id_changed_url (GHashTable  *hash_table,
+                                               gint64       id,
+                                               const gchar *url)
+{
+    GHashTableIter iter;
+    gpointer key, value;
+
+    g_hash_table_iter_init (&iter, hash_table);
+    while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+        if ((gint64) value == id && g_strcmp0 (url, key) != 0)
+        {
+            return key;
+        }
+    }
+
+    return NULL;
+}
+
+void
+destroy_insert_task_data (gpointer data)
+{
+    InsertTaskData *task_data;
+
+    task_data = data;
+
+    nautilus_file_list_free (task_data->selection);
+    g_free (data);
+}
+
+static GString*
+add_selection_filter (GList   *selection,
+                      GString *query)
+{
+    NautilusFile *file;
+    GList *l;
+    gchar *uri;
+
+    g_string_append (query, " FILTER(?url IN (");
+
+    for (l = selection; l != NULL; l = l->next)
+    {
+        file = l->data;
+
+        uri = nautilus_file_get_uri (file);
+
+        g_string_append_printf (query, "'%s'", uri);
+
+        if (l->next != NULL)
+        {
+            g_string_append (query, ", ");
+        }
+
+        g_free (uri);
+    }
+
+    g_string_append (query, "))");
+
+    return query;
+}
+
+static void
+start_query_or_update (GString             *query,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data,
+                       gboolean             is_query,
+                       GCancellable        *cancellable)
+{
+    g_autoptr (GError) error = NULL;
+    TrackerSparqlConnection *connection;
+
+    connection = tracker_sparql_connection_get (cancellable, &error);
+    if (!connection)
+    {
+        if (error)
+        {
+            g_warning ("Error on getting connection: %s", error->message);
+        }
+
+        return;
+    }
+
+    if (is_query)
+    {
+        tracker_sparql_connection_query_async (connection,
+                                               query->str,
+                                               cancellable,
+                                               callback,
+                                               user_data);
+    }
+    else
+    {
+        tracker_sparql_connection_update_async (connection,
+                                                query->str,
+                                                G_PRIORITY_DEFAULT,
+                                                cancellable,
+                                                callback,
+                                                user_data);
+    }
+
+    g_object_unref (connection);
+}
+
+static void
+on_query_callback (GObject              *object,
+                   GAsyncResult         *result,
+                   gpointer              user_data,
+                   GAsyncReadyCallback   callback,
+                   OperationType         op_type,
+                   GCancellable         *cancellable)
+{
+    TrackerSparqlCursor *cursor;
+    g_autoptr (GError) error = NULL;
+    TrackerSparqlConnection *connection;
+    GTask *task;
+
+    task = user_data;
+
+    connection = TRACKER_SPARQL_CONNECTION (object);
+
+    cursor = tracker_sparql_connection_query_finish (connection,
+                                                     result,
+                                                     &error);
+
+    if (error != NULL)
+    {
+        if (error->code != G_IO_ERROR_CANCELLED)
+        {
+            if (op_type == GET_FAVORITE_FILES)
+            {
+                g_warning ("Error on getting favorite files: %s", error->message);
+            }
+            else if (op_type == GET_IDS_FOR_URLS)
+            {
+                g_warning ("Error on getting id for url: %s", error->message);
+                g_task_return_pointer (task, g_task_get_task_data (task), NULL);
+                g_object_unref (task);
+            }
+            else
+            {
+                g_warning ("Error on getting query callback: %s", error->message);
+            }
+        }
+    }
+    else
+    {
+        tracker_sparql_cursor_next_async (cursor,
+                                          cancellable,
+                                          callback,
+                                          user_data);
+    }
+}
+
+static void
+on_update_callback (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+    TrackerSparqlConnection *connection;
+    GError *error;
+    UpdateData *data;
+    gint64 *id;
+    GList *l;
+    gchar *uri;
+
+    data = user_data;
+
+    error = NULL;
+
+    connection = TRACKER_SPARQL_CONNECTION (object);
+
+    tracker_sparql_connection_update_finish (connection, result, &error);
+
+    if (error == NULL ||
+        (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+    {
+        for (l = data->selection; l != NULL; l = l->next)
+        {
+            uri = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+            if (data->star)
+            {
+                if (g_hash_table_contains (data->ids, uri))
+                {
+                    id = g_new0 (gint64, 1);
+
+                    *id = (gint64) g_hash_table_lookup (data->ids, uri);
+                    g_hash_table_insert (data->tag_manager->favorite_files,
+                                         nautilus_file_get_uri (NAUTILUS_FILE (l->data)),
+                                         id);
+                }
+            }
+            else
+            {
+                g_hash_table_remove (data->tag_manager->favorite_files, uri);
+            }
+
+            g_free (uri);
+        }
+
+        if (!nautilus_file_undo_manager_is_operating ())
+        {
+            NautilusFileUndoInfo *undo_info;
+
+            undo_info = nautilus_file_undo_info_favorites_new (data->selection, data->star);
+            nautilus_file_undo_manager_set_action (undo_info);
+
+            g_object_unref (undo_info);
+        }
+
+        g_signal_emit_by_name (data->tag_manager, "favorites-changed", nautilus_file_list_copy 
(data->selection));
+
+        g_task_return_boolean (data->task, TRUE);
+        g_object_unref (data->task);
+    }
+    else if (error && error->code == G_IO_ERROR_CANCELLED)
+    {
+       g_error_free (error);
+    }
+    else
+    {
+        g_task_return_error (data->task, error);
+        g_object_unref (data->task);
+        g_warning ("error updating tags: %s", error->message);
+    }
+
+    if (data->ids)
+    {
+        g_hash_table_destroy (data->ids);
+    }
+    nautilus_file_list_free (data->selection);
+    g_free (data);
+}
+
+static gboolean
+get_query_status (TrackerSparqlCursor *cursor,
+                  GAsyncResult        *result,
+                  OperationType        op_type,
+                  gpointer             user_data)
+{
+    gboolean success;
+    GTask *task;
+    g_autoptr (GError) error = NULL;
+
+    task = user_data;
+
+    success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+
+    if (!success)
+    {
+        if (error)
+        {
+            g_warning ("Error on getting all tags cursor callback: %s", error->message);
+        }
+
+        g_clear_object (&cursor);
+
+        if (error == NULL ||
+            (error != NULL && error->code != G_IO_ERROR_CANCELLED))
+        {
+            if (op_type == GET_IDS_FOR_URLS)
+            {
+                g_task_return_pointer (task, g_task_get_task_data (task), NULL);
+                g_object_unref (task);
+            }
+        }
+    }
+
+    return success;
+}
+
+GList*
+nautilus_tag_manager_get_favorite_files (NautilusTagManager *self)
+{
+    return g_hash_table_get_keys (self->favorite_files);
+}
+
+static void
+on_get_favorite_files_cursor_callback (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+    TrackerSparqlCursor *cursor;
+    const gchar *url;
+    gint64 *id;
+    gboolean success;
+    NautilusTagManager *self;
+    GList *changed_files;
+    NautilusFile *file;
+
+    cursor = TRACKER_SPARQL_CURSOR (object);
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    success = get_query_status (cursor, result, GET_FAVORITE_FILES, NULL);
+    if (!success)
+    {
+        return;
+    }
+
+    id = g_new0 (gint64, 1);
+
+    url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+    *id = tracker_sparql_cursor_get_integer (cursor, 1);
+
+    g_hash_table_insert (self->favorite_files,
+                         g_strdup (url),
+                         id);
+
+    file = nautilus_file_get_by_uri (url);
+    changed_files = g_list_prepend (NULL, file);
+
+    g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+    nautilus_file_list_free (changed_files);
+
+    tracker_sparql_cursor_next_async (cursor,
+                                      self->cancellable,
+                                      on_get_favorite_files_cursor_callback,
+                                      self);
+}
+
+static void
+on_get_favorite_files_query_callback (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    on_query_callback (object,
+                       result,
+                       user_data,
+                       on_get_favorite_files_cursor_callback,
+                       GET_FAVORITE_FILES,
+                       self->cancellable);
+}
+
+static void
+nautilus_tag_manager_query_favorite_files (NautilusTagManager *self,
+                                           GCancellable       *cancellable)
+{
+    GString *query;
+
+    self->cancellable = cancellable;
+
+    query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url ; nao:hasTag 
nao:predefined-tag-favorite}");
+
+    start_query_or_update (query,
+                           on_get_favorite_files_query_callback,
+                           self,
+                           TRUE,
+                           cancellable);
+
+    g_string_free (query, TRUE);
+}
+
+static gpointer
+nautilus_tag_manager_gpointer_task_finish (GObject      *source_object,
+                                           GAsyncResult *res,
+                                           GError      **error)
+{
+    g_return_val_if_fail (g_task_is_valid (res, source_object), FALSE);
+
+    return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static GString*
+nautilus_tag_manager_delete_tag (NautilusTagManager  *self,
+                                 GList               *selection,
+                                 GString             *query)
+{
+
+    g_string_append (query,
+                     "DELETE { ?urn nao:hasTag nao:predefined-tag-favorite }"
+                     "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+
+    query = add_selection_filter (selection, query);
+
+    g_string_append (query, "}\n");
+
+    return query;
+}
+
+static GString*
+nautilus_tag_manager_insert_tag (NautilusTagManager  *self,
+                                 GList               *selection,
+                                 GString             *query)
+{
+    g_string_append (query,
+                     "INSERT { ?urn nao:hasTag nao:predefined-tag-favorite }"
+                     "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url .");
+
+    query = add_selection_filter (selection, query);
+
+    g_string_append (query, "}\n");
+
+    return query;
+}
+
+gboolean
+nautilus_tag_manager_file_is_favorite (NautilusTagManager *self,
+                                       const gchar        *file_name)
+{
+    return g_hash_table_contains (self->favorite_files, file_name);
+}
+
+static void
+on_get_file_ids_for_urls_cursor_callback (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+    TrackerSparqlCursor *cursor;
+    GTask *task;
+    gint64 *id;
+    const gchar *url;
+    gboolean success;
+    GList *l;
+    gchar *file_url;
+    InsertTaskData *data;
+
+    task = user_data;
+    data = g_task_get_task_data (task);
+
+    cursor = TRACKER_SPARQL_CURSOR (object);
+
+    success = get_query_status (cursor, result, GET_IDS_FOR_URLS, task);
+    if (!success)
+    {
+        return;
+    }
+
+    id = g_new0 (gint64, 1);
+
+    url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+    *id = tracker_sparql_cursor_get_integer (cursor, 1);
+
+    for (l = data->selection; l != NULL; l = l->next)
+    {
+        file_url = nautilus_file_get_uri (NAUTILUS_FILE (l->data));
+
+        if (g_strcmp0 (file_url, url) == 0)
+        {
+            g_hash_table_insert (data->ids,
+                                 g_strdup (url),
+                                 id);
+
+            g_free (file_url);
+
+            break;
+        }
+
+        g_free (file_url);
+    }
+
+    tracker_sparql_cursor_next_async (cursor,
+                                      g_task_get_cancellable (task),
+                                      on_get_file_ids_for_urls_cursor_callback,
+                                      task);
+
+}
+
+
+static void
+on_get_file_ids_for_urls_query_callback (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+    GTask *task;
+
+    task = user_data;
+
+    on_query_callback (object,
+                       result,
+                       user_data,
+                       on_get_file_ids_for_urls_cursor_callback,
+                       GET_IDS_FOR_URLS,
+                       g_task_get_cancellable (task));
+}
+
+static void
+nautilus_tag_manager_get_file_ids_for_urls (GObject *object,
+                                            GList   *selection,
+                                            GTask   *task)
+{
+    GString *query;
+
+    query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url; .");
+
+    query = add_selection_filter (selection, query);
+
+    g_string_append (query, "}\n");
+
+    start_query_or_update (query,
+                           on_get_file_ids_for_urls_query_callback,
+                           task,
+                           TRUE,
+                           g_task_get_cancellable (task));
+
+    g_string_free (query, TRUE);
+}
+
+static void
+on_star_files_callback (GObject      *object,
+                        GAsyncResult *res,
+                        gpointer      user_data)
+{
+    NautilusTagManager *self;
+    GString *query;
+    InsertTaskData *data;
+    g_autoptr (GError) error = NULL;
+    GTask *task;
+    UpdateData *update_data;
+
+    self = NAUTILUS_TAG_MANAGER (object);
+
+    data = nautilus_tag_manager_gpointer_task_finish (object, res, &error);
+
+    task = g_task_new (data->object, data->cancellable, data->callback, NULL);
+
+    query = g_string_new ("");
+
+    query = nautilus_tag_manager_insert_tag (self,
+                                             data->selection,
+                                             query);
+
+    update_data = g_new0 (UpdateData, 1);
+    update_data->task = task;
+    update_data->tag_manager = self;
+    update_data->selection = nautilus_file_list_copy (data->selection);
+    update_data->star = TRUE;
+    update_data->ids = data->ids;
+
+    /* the ids hash table is now owned by the update_data,
+     * so it will be freed by it.
+     */
+    destroy_insert_task_data (data);
+
+    start_query_or_update (query,
+                           on_update_callback,
+                           update_data,
+                           FALSE,
+                           g_task_get_cancellable (task));
+
+    g_string_free (query, TRUE);
+}
+
+void
+nautilus_tag_manager_star_files (NautilusTagManager  *self,
+                                 GObject             *object,
+                                 GList               *selection,
+                                 GAsyncReadyCallback  callback,
+                                 GCancellable        *cancellable)
+{
+    GTask *task;
+    InsertTaskData *data;
+
+    data = g_new0 (InsertTaskData, 1);
+    data->selection = nautilus_file_list_copy (selection);
+    data->ids = g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       (GDestroyNotify) g_free,
+                                       (GDestroyNotify) g_free);
+    data->callback = callback;
+    data->object = object;
+    data->cancellable = cancellable;
+
+    task = g_task_new (self, cancellable, on_star_files_callback, NULL);
+    g_task_set_task_data (task,
+                          data,
+                          NULL);
+
+    nautilus_tag_manager_get_file_ids_for_urls (G_OBJECT (self), selection, task);
+}
+
+void
+nautilus_tag_manager_unstar_files (NautilusTagManager  *self,
+                                   GObject             *object,
+                                   GList               *selection,
+                                   GAsyncReadyCallback  callback,
+                                   GCancellable        *cancellable)
+{
+    GString *query;
+    GTask *task;
+    UpdateData *update_data;
+
+    task = g_task_new (object, cancellable, callback, NULL);
+
+    query = g_string_new ("");
+
+    query = nautilus_tag_manager_delete_tag (self,
+                                             selection,
+                                             query);
+
+    update_data = g_new0 (UpdateData, 1);
+    update_data->task = task;
+    update_data->tag_manager = self;
+    update_data->selection = nautilus_file_list_copy (selection);
+    update_data->star = FALSE;
+
+    start_query_or_update (query,
+                           on_update_callback,
+                           update_data,
+                           FALSE,
+                           cancellable);
+
+    g_string_free (query, TRUE);
+}
+
+void
+on_tracker_notifier_events(TrackerNotifier *notifier,
+                           GPtrArray       *events,
+                           gpointer         user_data)
+{
+    TrackerNotifierEvent *event;
+    NautilusTagManager *self;
+    int i;
+    const gchar *location_uri;
+    const gchar *new_location_uri;
+    GError *error = NULL;
+    TrackerSparqlConnection *connection;
+    TrackerSparqlCursor *cursor;
+    GString *query;
+    gboolean query_has_results;
+    gint64 *id;
+    GList *changed_files;
+    NautilusFile *file;
+
+    self = NAUTILUS_TAG_MANAGER (user_data);
+
+    for (i = 0; i < events->len; i++)
+    {
+        event = g_ptr_array_index (events, i);
+
+        location_uri = tracker_notifier_event_get_location (event);
+
+        query = g_string_new ("");
+        g_string_append_printf (query,
+                                "SELECT ?url WHERE { ?urn nie:url ?url; nao:hasTag 
nao:predefined-tag-favorite . FILTER (tracker:id(?urn) = %ld)}",
+                                tracker_notifier_event_get_id (event));
+
+        /* check if the file changed it's location and update hash table if so */
+        new_location_uri = nautilus_tag_manager_file_with_id_changed_url (self->favorite_files,
+                                                                          tracker_notifier_event_get_id 
(event),
+                                                                          location_uri);
+        if (new_location_uri)
+        {
+            id = g_new0 (gint64, 1);
+            *id = tracker_notifier_event_get_id (event);
+
+            g_hash_table_remove (self->favorite_files, new_location_uri);
+            g_hash_table_insert (self->favorite_files,
+                                 g_strdup (location_uri),
+                                 id);
+
+            file = nautilus_file_get_by_uri (location_uri);
+            changed_files = g_list_prepend (NULL, file);
+
+            g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+            nautilus_file_list_free (changed_files);
+        }
+
+        connection = tracker_sparql_connection_get (NULL, &error);
+
+        if (!connection)
+        {
+            g_printerr ("Couldn't obtain a direct connection to the Tracker store: %s",
+                        error ? error->message : "unknown error");
+            g_clear_error (&error);
+
+            return;
+        }
+
+        cursor = tracker_sparql_connection_query (connection,
+                                                  query->str,
+                                                  NULL,
+                                                  &error);
+
+        if (error)
+        {
+            g_printerr ("Couldn't query the Tracker Store: '%s'", error->message);
+
+            g_clear_error (&error);
+
+            return;
+        }
+
+        if (cursor)
+        {
+            query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error);
+
+            /* if no results are found, then the file isn't marked as favorite.
+             * If needed, update the hashtable.
+             */
+            if (!query_has_results && location_uri && g_hash_table_contains (self->favorite_files, 
location_uri))
+            {
+                g_hash_table_remove (self->favorite_files, location_uri);
+
+                file = nautilus_file_get_by_uri (location_uri);
+                changed_files = g_list_prepend (NULL, file);
+
+                g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+                nautilus_file_list_free (changed_files);
+            }
+            else if (query_has_results && location_uri && !g_hash_table_contains (self->favorite_files, 
location_uri))
+            {
+                id = g_new0 (gint64, 1);
+                *id = tracker_notifier_event_get_id (event);
+
+                g_hash_table_insert (self->favorite_files,
+                                     g_strdup (location_uri),
+                                     id);
+
+                file = nautilus_file_get_by_uri (location_uri);
+                changed_files = g_list_prepend (NULL, file);
+
+                g_signal_emit_by_name (self, "favorites-changed", changed_files);
+
+                nautilus_file_list_free (changed_files);
+            }
+
+            g_object_unref (cursor);
+        }
+
+        g_object_unref (connection);
+
+        g_string_free (query, TRUE);
+    }
+
+}
+
+static void
+nautilus_tag_manager_finalize (GObject *object)
+{
+    NautilusTagManager *self;
+
+    self = NAUTILUS_TAG_MANAGER (object);
+
+    g_signal_handlers_disconnect_by_func (self->notifier,
+                                          G_CALLBACK (on_tracker_notifier_events),
+                                          self);
+    g_clear_object (&self->notifier);
+
+    g_hash_table_destroy (self->favorite_files);
+
+    G_OBJECT_CLASS (nautilus_tag_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_tag_manager_class_init (NautilusTagManagerClass *klass)
+{
+    GObjectClass *oclass;
+
+    oclass = G_OBJECT_CLASS (klass);
+
+    oclass->finalize = nautilus_tag_manager_finalize;
+
+    signals[FAVORITES_CHANGED] = g_signal_new ("favorites-changed",
+                                               NAUTILUS_TYPE_TAG_MANAGER,
+                                               G_SIGNAL_RUN_LAST,
+                                               0,
+                                               NULL,
+                                               NULL,
+                                               g_cclosure_marshal_VOID__POINTER,
+                                               G_TYPE_NONE,
+                                               1,
+                                               G_TYPE_POINTER);
+}
+
+NautilusTagManager* nautilus_tag_manager_get ()
+{
+    if (tag_manager != NULL)
+    {
+        return g_object_ref (tag_manager);
+    }
+
+    tag_manager = g_object_new (NAUTILUS_TYPE_TAG_MANAGER, NULL);
+    g_object_add_weak_pointer (G_OBJECT (tag_manager), (gpointer)&tag_manager);
+
+    return tag_manager;
+}
+
+void nautilus_tag_manager_set_cancellable (NautilusTagManager *tag_manager,
+                                           GCancellable *cancellable)
+{
+    nautilus_tag_manager_query_favorite_files (tag_manager, cancellable);
+
+    tag_manager->notifier = tracker_notifier_new (NULL,
+                                                  TRACKER_NOTIFIER_FLAG_QUERY_LOCATION,
+                                                  cancellable,
+                                                  &tag_manager->notifier_error);
+
+    g_signal_connect (tag_manager->notifier,
+                      "events",
+                      G_CALLBACK (on_tracker_notifier_events),
+                      tag_manager);
+
+}
+
+static void
+nautilus_tag_manager_init (NautilusTagManager *self)
+{
+    self->favorite_files = g_hash_table_new_full (g_str_hash,
+                                                  g_str_equal,
+                                                  (GDestroyNotify) g_free,
+                                                  (GDestroyNotify) g_free);
+}
diff --git a/src/nautilus-tag-manager.h b/src/nautilus-tag-manager.h
new file mode 100644
index 000000000..4072dd083
--- /dev/null
+++ b/src/nautilus-tag-manager.h
@@ -0,0 +1,57 @@
+/* nautilus-tag-manager.h
+ *
+ * Copyright (C) 2017 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 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/>.
+ */
+
+#ifndef NAUTILUS_TAG_MANAGER_H
+#define NAUTILUS_TAG_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define NAUTILUS_TYPE_TAG_MANAGER (nautilus_tag_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (NautilusTagManager, nautilus_tag_manager, NAUTILUS, TAG_MANAGER, GObject);
+
+NautilusTagManager* nautilus_tag_manager_get                ();
+
+void                nautilus_tag_manager_set_cancellable    (NautilusTagManager *tag_manager,
+                                                             GCancellable *cancellable);
+
+GList*              nautilus_tag_manager_get_favorite_files (NautilusTagManager *self);
+
+void                nautilus_tag_manager_star_files         (NautilusTagManager  *self,
+                                                             GObject             *object,
+                                                             GList               *selection,
+                                                             GAsyncReadyCallback  callback,
+                                                             GCancellable        *cancellable);
+
+void                nautilus_tag_manager_unstar_files       (NautilusTagManager  *self,
+                                                             GObject             *object,
+                                                             GList               *selection,
+                                                             GAsyncReadyCallback  callback,
+                                                             GCancellable        *cancellable);
+
+
+gboolean            nautilus_tag_manager_file_is_favorite   (NautilusTagManager *self,
+                                                             const gchar        *file_name);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index 653b9cfc1..df669f81a 100644
--- a/src/nautilus-window.c
+++ b/src/nautilus-window.c
@@ -1140,6 +1140,19 @@ places_sidebar_show_other_locations_with_flags (NautilusWindow     *window,
     g_object_unref (location);
 }
 
+static void
+places_sidebar_show_starred_location (NautilusWindow    *window,
+                                      GtkPlacesOpenFlags open_flags)
+{
+    GFile *location;
+
+    location = g_file_new_for_uri ("favorites:///");
+
+    open_location_cb (window, location, open_flags);
+
+    g_object_unref (location);
+}
+
 static GList *
 build_selection_list_from_gfile_list (GList *gfile_list)
 {
@@ -2947,6 +2960,7 @@ nautilus_window_class_init (NautilusWindowClass *class)
     gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_close);
 
     gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_other_locations_with_flags);
+    gtk_widget_class_bind_template_callback (wclass, places_sidebar_show_starred_location);
 
     properties[PROP_DISABLE_CHROME] =
         g_param_spec_boolean ("disable-chrome",
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index 3dd60ba6d..e114d34aa 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -18,6 +18,7 @@
     <file>ui/nautilus-no-search-results.ui</file>
     <file>ui/nautilus-folder-is-empty.ui</file>
     <file>ui/nautilus-trash-is-empty.ui</file>
+    <file>ui/nautilus-starred-is-empty.ui</file>
     <file>gtk/help-overlay.ui</file>
     <file>ui/nautilus-batch-rename-dialog.ui</file>
     <file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui 
b/src/resources/ui/nautilus-files-view-context-menus.ui
index 4a53de439..377b71f57 100644
--- a/src/resources/ui/nautilus-files-view-context-menus.ui
+++ b/src/resources/ui/nautilus-files-view-context-menus.ui
@@ -253,6 +253,23 @@
     </section>
     <section>
       <item>
+        <attribute name="label" translatable="yes">Tags</attribute>
+        <attribute name="action">view.edit-tags</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Star</attribute>
+        <attribute name="action">view.star</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Unstar</attribute>
+        <attribute name="action">view.unstar</attribute>
+        <attribute name="hidden-when">action-disabled</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
         <attribute name="label" translatable="yes">P_roperties</attribute>
         <attribute name="action">view.properties</attribute>
       </item>
diff --git a/src/resources/ui/nautilus-starred-is-empty.ui b/src/resources/ui/nautilus-starred-is-empty.ui
new file mode 100644
index 000000000..a0482a05a
--- /dev/null
+++ b/src/resources/ui/nautilus-starred-is-empty.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <object class="GtkGrid" id="starred_is_empty">
+    <property name="visible">False</property>
+    <property name="row_spacing">12</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="halign">center</property>
+    <property name="valign">center</property>
+    <style>
+      <class name="dim-label"/>
+    </style>
+    <child>
+      <object class="GtkImage">
+        <property name="visible">True</property>
+        <property name="icon-name">starred-symbolic</property>
+        <property name="pixel-size">72</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Starred files will appear here</property>
+        <attributes>
+          <attribute name="weight" value="bold"/>
+          <attribute name="scale" value="1.44"/>
+        </attributes>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/src/resources/ui/nautilus-window.ui b/src/resources/ui/nautilus-window.ui
index 2fc5ffcc1..74e8c6293 100644
--- a/src/resources/ui/nautilus-window.ui
+++ b/src/resources/ui/nautilus-window.ui
@@ -34,7 +34,9 @@
                     <property name="visible">True</property>
                     <property name="populate-all">True</property>
                     <property name="show-other-locations">True</property>
+                    <property name="show-starred-location">True</property>
                     <signal name="show-other-locations-with-flags" 
handler="places_sidebar_show_other_locations_with_flags" object="NautilusWindow" swapped="yes" />
+                    <signal name="show-starred-location" handler="places_sidebar_show_starred_location" 
object="NautilusWindow" swapped="yes" />
                   </object>
                   <packing>
                     <property name="pack_type">start</property>


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