Andreas Wacknitz
2024-02-26 e3f6f2c3f9a81740833756aeb6ba285e1c6e014e
caja: update to 1.28.0

42 files added
3 files modified
2 files renamed
11738 ■■■■■ changed files
components/desktop/mate/caja/Makefile 7 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/caja.p5m 3 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/manifests/sample-manifest.p5m 3 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch 54 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch 913 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch 26 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch 183 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch 20 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch 2835 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch 10 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch 20 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch 10 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch 184 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch 58 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/12-timeslider-caja-window.patch 49 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/13-timeslider-file-manager.patch 313 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch 22 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/15-timeslider-timescale.patch 1350 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/01-time-slider.patch.org patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1 59 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2 854 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/02-time-slider-part2.patch.org patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1 10 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2 16 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1 10 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2 31 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3 142 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1 17 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10 12 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11 1242 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12 78 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13 23 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14 14 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2 57 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3 21 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4 282 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5 44 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6 10 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7 30 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8 967 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9 38 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1 58 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1 30 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2 12 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3 271 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/15-timescale.patch1 1289 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/patches/Archiv/15-timescale.patch2 61 ●●●●● patch | view | raw | blame | history
components/desktop/mate/caja/Makefile
@@ -23,11 +23,10 @@
include $(WS_MAKE_RULES)/mate.mk
COMPONENT_NAME=        caja
COMPONENT_MJR_VERSION=    1.26
COMPONENT_MNR_VERSION=    3
COMPONENT_REVISION=    1
COMPONENT_MJR_VERSION=    1.28
COMPONENT_MNR_VERSION=    0
COMPONENT_SUMMARY=    File manager for the MATE desktop
COMPONENT_ARCHIVE_HASH= sha256:813edf08a36f995ec3c1504131ff8afbbd021f6e1586643fe5dced5e73e5790d
COMPONENT_ARCHIVE_HASH= sha256:1e3014ce1455817ec2ef74d09efdfb6835d8a372ed9a16efb5919ef7b821957a
COMPONENT_CLASSIFICATION= System/Libraries
COMPONENT_LICENSE=    GPLv2, LGPLv2, FDLv1.1
components/desktop/mate/caja/caja.p5m
@@ -104,10 +104,8 @@
file path=usr/share/caja/patterns/stucco.jpg
file path=usr/share/caja/patterns/terracotta.png
file path=usr/share/caja/patterns/wavy_white.png
file path=usr/share/caja/ui/caja-bookmarks-window.ui
file path=usr/share/caja/ui/caja-desktop-icon-view-ui.xml
file path=usr/share/caja/ui/caja-directory-view-ui.xml
file path=usr/share/caja/ui/caja-file-management-properties.ui
file path=usr/share/caja/ui/caja-icon-view-ui.xml
file path=usr/share/caja/ui/caja-list-view-ui.xml
file path=usr/share/caja/ui/caja-navigation-window-ui.xml
@@ -201,7 +199,6 @@
file path=usr/share/locale/mn/LC_MESSAGES/caja.mo
file path=usr/share/locale/mr/LC_MESSAGES/caja.mo
file path=usr/share/locale/ms/LC_MESSAGES/caja.mo
file path=usr/share/locale/nan/LC_MESSAGES/caja.mo
file path=usr/share/locale/nb/LC_MESSAGES/caja.mo
file path=usr/share/locale/nds/LC_MESSAGES/caja.mo
file path=usr/share/locale/ne/LC_MESSAGES/caja.mo
components/desktop/mate/caja/manifests/sample-manifest.p5m
@@ -97,10 +97,8 @@
file path=usr/share/caja/patterns/stucco.jpg
file path=usr/share/caja/patterns/terracotta.png
file path=usr/share/caja/patterns/wavy_white.png
file path=usr/share/caja/ui/caja-bookmarks-window.ui
file path=usr/share/caja/ui/caja-desktop-icon-view-ui.xml
file path=usr/share/caja/ui/caja-directory-view-ui.xml
file path=usr/share/caja/ui/caja-file-management-properties.ui
file path=usr/share/caja/ui/caja-icon-view-ui.xml
file path=usr/share/caja/ui/caja-list-view-ui.xml
file path=usr/share/caja/ui/caja-navigation-window-ui.xml
@@ -194,7 +192,6 @@
file path=usr/share/locale/mn/LC_MESSAGES/caja.mo
file path=usr/share/locale/mr/LC_MESSAGES/caja.mo
file path=usr/share/locale/ms/LC_MESSAGES/caja.mo
file path=usr/share/locale/nan/LC_MESSAGES/caja.mo
file path=usr/share/locale/nb/LC_MESSAGES/caja.mo
file path=usr/share/locale/nds/LC_MESSAGES/caja.mo
file path=usr/share/locale/ne/LC_MESSAGES/caja.mo
components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch
New file
@@ -0,0 +1,54 @@
--- caja-1.28.0/configure.ac.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/configure.ac    2024-02-26 08:42:41.073474185 +0100
@@ -159,6 +159,51 @@
 dnl ==========================================================================
+dnl ********************
+dnl * Check for libzfs *
+dnl ********************
+ZFS_LIBS=
+msg_zfs=no
+AC_CHECK_LIB(zfs, zfs_iter_root,
+   [AC_CHECK_HEADER(libzfs.h,
+       [AC_DEFINE(HAVE_ZFS, 1, [Define to 1 if ZFS is available])
+        ZFS_LIBS="-lzfs"
+        msg_zfs=yes])
+     ])
+AC_SUBST(ZFS_LIBS)
+
+dnl ==========================================================================
+
+dnl ********************
+dnl * Check for libscf*
+dnl ********************
+SCF_LIBS=
+msg_scf=no
+AC_CHECK_LIB(scf, scf_handle_bind,
+   [AC_CHECK_HEADER(libscf.h,
+       [AC_DEFINE(HAVE_SCF, 1, [Define to 1 if SCF is available])
+        SCF_LIBS="-lscf"
+        msg_scf=yes])
+     ])
+AC_SUBST(SCF_LIBS)
+
+dnl ==========================================================================
+
+dnl ********************
+dnl * Check for libscf*
+dnl ********************
+NVPAIR_LIBS=
+msg_nvpair=no
+AC_CHECK_LIB(nvpair, nvpair_value_match,
+   [AC_CHECK_HEADER(libscf.h,
+       [AC_DEFINE(NVPAIR_LIBS, 1, [Define to 1 if nvpair is available])
+        NVPAIR_LIBS="-lnvpair"
+        msg_nvpair=yes])
+     ])
+AC_SUBST(NVPAIR_LIBS)
+
+dnl ==========================================================================
+
 dnl exempi checking
 AM_CONDITIONAL(HAVE_EXEMPI, false)
components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch
New file
@@ -0,0 +1,913 @@
--- caja-1.28.0/src/caja-zfs-bar.h.orig    2024-02-26 08:42:41.083373824 +0100
+++ caja-1.28.0/src/caja-zfs-bar.h    2024-02-26 08:42:41.083327328 +0100
@@ -0,0 +1,56 @@
+#ifndef __CAJA_ZFS_BAR_H
+#define __CAJA_ZFS_BAR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-directory.h>
+#include "caja-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define CAJA_TYPE_ZFS_BAR         (caja_zfs_bar_get_type ())
+#define CAJA_ZFS_BAR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBar))
+#define CAJA_ZFS_BAR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_ZFS_BAR, CajaZfsBarClass))
+#define CAJA_IS_ZFS_BAR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_ZFS_BAR))
+#define CAJA_IS_ZFS_BAR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_ZFS_BAR))
+#define CAJA_ZFS_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBarClass))
+
+typedef struct CajaZfsBarPrivate CajaZfsBarPrivate;
+
+typedef struct
+{
+  GtkEventBox eventbox;
+  CajaZfsBarPrivate *priv;
+} CajaZfsBar;
+
+typedef struct
+{
+  GtkEventBoxClass parent_class;
+} CajaZfsBarClass;
+
+GType                        caja_zfs_bar_get_type (void) G_GNUC_CONST;
+
+GtkWidget *            caja_zfs_bar_new ();
+
+void                caja_zfs_bar_setup (CajaZfsBar* bar,
+                                                    CajaDirectory *dir,
+                                                             CajaWindowSlot *active_slot,
+                                                     GtkToggleAction* action);
+
+void                caja_zfs_bar_display (CajaZfsBar *bar,
+                                                    CajaWindow *window,
+                                                      CajaDirectory *new_dir,
+                                                       GCancellable* cancellable);
+
+void                caja_zfs_set_snap (CajaZfsBar *bar,
+                                               CajaDirectory *dir);
+void                   caja_zfs_bar_remove_and_skip_snap
+                                      (CajaZfsBar *bar, char *path);
+
+void                caja_zfs_bar_hide (CajaZfsBar *bar);
+
+void                caja_zfs_bar_cancel_tasks (CajaWindow *window);
+CajaDirectory * caja_zfs_bar_get_dir (CajaZfsBar* bar);
+
+G_END_DECLS
+
+#endif /* __CAJA_ZFS_BAR_H */
--- caja-1.28.0/src/caja-zfs-bar.c.orig    2024-02-26 08:42:41.083214681 +0100
+++ caja-1.28.0/src/caja-zfs-bar.c    2024-02-26 08:42:41.083165019 +0100
@@ -0,0 +1,851 @@
+/*
+ * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+#include "config.h"
+#include <strings.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include "caja-zfs-bar.h"
+#include "caja-window.h"
+#include "caja-window-private.h"
+#include "timescale.h"
+#include <libcaja-private/caja-zfs.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include "caja-window-slot.h"
+#include "caja-navigation-window.h"
+
+#define CAJA_ZFS_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBarPrivate))
+
+struct CajaZfsBarPrivate
+{
+    CajaDirectory     *dir;
+    GtkWidget         *scale;
+    GtkWidget         *delete_button;
+    GdkColor           in_snapshot;
+    GtkToggleAction   *action;
+    CajaWindowSlot    *slot;
+    int                num_range_items;
+    gboolean           is_setup;
+    gboolean           set_only;
+    char              *current_path;
+    ZfsSnapDirMonitor *zfs_dir_monitor_data;
+    gboolean           in_snapshot_dir;
+    GtkWidget         *camera_image;
+    GtkWidget         *delete_image;
+    gboolean           explicit_user_hide;
+};
+
+G_DEFINE_TYPE (CajaZfsBar, caja_zfs_bar, GTK_TYPE_EVENT_BOX)
+
+static void
+close_clicked_callback (GtkWidget *widget,
+                        CajaZfsBar *bar);
+static void
+slider_moved_callback (TimeScale *ts,
+        CajaZfsBar *bar);
+static void
+update_delete_or_snap_button (CajaZfsBar *bar,
+        gboolean in_snap);
+
+static void
+caja_zfs_bar_class_init (CajaZfsBarClass *klass)
+{
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (klass, sizeof (CajaZfsBarPrivate));
+}
+
+static void
+set_scale_range (CajaZfsBar *bar, gboolean set_initial_position)
+{
+    int i;
+    GList *tmp;
+
+    if (bar->priv->dir)
+    {
+        i = caja_directory_get_num_snapshots (bar->priv->dir);
+
+        if (i==0)
+            return;
+
+        bar->priv->num_range_items = i;
+
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+
+        if (set_initial_position)
+            timescale_set_snapshots (TIMESCALE (bar->priv->scale), tmp, -1);
+        else
+            timescale_set_snapshots (TIMESCALE (bar->priv->scale), tmp, bar->priv->num_range_items);
+    }
+}
+
+static void
+update_range (CajaZfsBar *bar)
+{
+    if (caja_directory_get_snapshots (bar->priv->dir))
+    {
+        set_scale_range (bar, FALSE);
+        slider_moved_callback (TIMESCALE (bar->priv->scale), bar);
+    }
+    else
+        close_clicked_callback (NULL, bar);
+}
+
+static int
+match_func (ZfsDataSet *set, char *dir)
+{
+    /* remove trailing slash from dir */
+    char mountp[PATH_MAX+1];
+    int length = strlen (set->mountpoint);
+
+    if (set->mountpoint[length-1] == '/')
+    {
+        memcpy (mountp, set->mountpoint, length - 1);
+        mountp[length-1] = NULL;
+        return strcmp (mountp, dir);
+    }
+    else
+        return strcmp (set->mountpoint, dir);
+}
+
+void
+caja_zfs_bar_remove_and_skip_snap (CajaZfsBar *bar, char *path)
+{
+    GList* match = NULL;
+    GList* snap_list = NULL;
+    ZfsDataSet *snap;
+
+    snap_list = caja_directory_get_snapshots (bar->priv->dir);
+    match = g_list_find_custom (snap_list, path, (GCompareFunc)match_func);
+
+    if (match)
+    {
+        snap = (ZfsDataSet*) match->data;
+        caja_directory_remove_snapshot (bar->priv->dir, snap);
+        update_range (bar);
+    }
+}
+
+static void
+update_delete_or_snap_button (CajaZfsBar *bar, gboolean in_snap)
+{
+    if (in_snap)
+    {
+        if (!bar->priv->in_snapshot_dir)
+        { /*in main dir to snap */
+            bar->priv->in_snapshot_dir = TRUE;
+            g_object_ref (bar->priv->camera_image);
+            gtk_button_set_image (GTK_BUTTON (bar->priv->delete_button),
+                    bar->priv->delete_image);
+            gtk_widget_set_tooltip_text (bar->priv->delete_button,
+                    /* SUN_BRANDING */
+                    _("Delete this snapshot"));
+        }
+    }
+    else
+    {
+        if (bar->priv->in_snapshot_dir)
+        { /* in snap to main dir */
+            bar->priv->in_snapshot_dir = FALSE;
+            g_object_ref (bar->priv->delete_image);
+            gtk_button_set_image (GTK_BUTTON (bar->priv->delete_button),
+                    bar->priv->camera_image);
+            gtk_widget_set_tooltip_text (bar->priv->delete_button,
+                    /* SUN_BRANDING */
+                    _("Take a zfs snapshot of this directory now"));
+        }
+    }
+}
+
+static void
+slider_moved_callback (TimeScale *ts,
+                         CajaZfsBar *bar)
+{
+    GList *tmp;
+    ZfsDataSet *snap;
+    GFile *snap_file;
+    int pos = timescale_get_position (ts);
+
+    if (pos < bar->priv->num_range_items)
+    {
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+
+        snap = g_list_nth_data (tmp, pos);
+
+        snap_file = g_file_new_for_path (snap->mountpoint);
+    }
+    else
+    {
+        snap_file = caja_directory_get_location (bar->priv->dir);
+        pos = bar->priv->num_range_items;
+    }
+
+    if (!bar->priv->set_only)
+    {
+        gboolean in_snap = FALSE;
+        char *path = g_file_get_path (snap_file);
+
+        if (g_file_test (path, G_FILE_TEST_IS_DIR))
+        {
+            caja_window_slot_go_to (bar->priv->slot,
+                    snap_file,
+                    FALSE);
+            if (bar->priv->current_path)
+                g_free (bar->priv->current_path);
+            bar->priv->current_path = path;
+        }
+        else
+        { /* the snapshot diappeared try the next one */
+            g_free (path);
+            g_object_unref (snap_file);
+            /* remove snap from list */
+            caja_directory_remove_snapshot (bar->priv->dir, snap);
+            update_range (bar);
+            return;
+        }
+
+        in_snap = ts_is_in_snapshot (path);
+        update_delete_or_snap_button (bar, ts_is_in_snapshot (path));
+
+    }
+
+    g_object_unref (snap_file);
+
+}
+
+static void
+delete_clicked_callback (GtkWidget *widget,
+        CajaZfsBar *bar)
+{
+    GList *tmp;
+    ZfsDataSet *snap;
+    GFile *snap_file;
+    char *path;
+    char *full_command;
+    int pos = timescale_get_position (TIMESCALE (bar->priv->scale));
+
+    if (bar->priv->in_snapshot_dir)
+    {
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+        snap = g_list_nth_data (tmp, pos);
+        snap_file = g_file_new_for_path (snap->mountpoint);
+        path = g_file_get_path (snap_file);
+
+        g_object_unref (snap_file);
+        if (snap->type)
+        {
+            /*printf ("path %s snapshot to delete %s\n", path, snap->name);*/
+            full_command = g_strdup_printf ("/usr/lib/time-slider-delete '%s'", snap->name);
+        }
+        else
+        {
+            /*printf ("path %s backup to delete %s\n", path, snap->name);*/
+            full_command = g_strdup_printf ("/usr/lib/time-slider-delete '%s'", path);
+        }
+
+        mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (widget),
+                full_command, NULL);
+    }
+    else
+    {
+        path = g_file_get_path (caja_directory_get_location (bar->priv->dir));
+        char *fs = ts_get_zfs_filesystem (path);
+        /* printf ("take a snapshot of zfs fs %s for dir %s\n", fs, path); */
+        full_command = g_strdup_printf ("/usr/lib/time-slider-snapshot '%s' '%s'", path, fs);
+        mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (widget),
+                full_command, NULL);
+        g_free (fs);
+    }
+
+    g_free (full_command);
+    g_free (path);
+}
+
+static void
+close_clicked_callback (GtkWidget *widget,
+        CajaZfsBar *bar)
+{
+    GFile *snap_file = caja_directory_get_location (bar->priv->dir);
+
+    caja_window_slot_go_to (bar->priv->slot,
+            snap_file,
+            FALSE);
+    g_object_unref (snap_file);
+
+    bar->priv->is_setup = FALSE;
+
+    gtk_widget_hide (GTK_WIDGET (bar));
+    gtk_toggle_action_set_active (bar->priv->action, FALSE);
+
+}
+
+static void
+caja_zfs_bar_init (CajaZfsBar *bar)
+{
+    GtkWidget *hbox, *toolbar, *close, *delete, *image, *button_vbox;
+    GtkToolItem *item;
+    char *path;
+
+    bar->priv = CAJA_ZFS_BAR_GET_PRIVATE (bar);
+
+    bar->priv->dir = NULL;
+    bar->priv->num_range_items = 0;
+    bar->priv->action = NULL;
+    bar->priv->slot = NULL;
+    bar->priv->is_setup = FALSE;
+    bar->priv->set_only = FALSE;
+
+    /* GUI init */
+
+    toolbar = gtk_toolbar_new ();
+    gtk_widget_show (toolbar);
+
+    item = gtk_tool_item_new ();
+    gtk_widget_show (GTK_WIDGET (item));
+    gtk_tool_item_set_expand (item, TRUE);
+
+    hbox = gtk_hbox_new (FALSE, 2);
+    gtk_widget_show (GTK_WIDGET (hbox));
+
+    gtk_container_add (GTK_CONTAINER (item),hbox);
+
+    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
+    gtk_container_add (GTK_CONTAINER (bar),toolbar);
+    gtk_container_set_border_width (GTK_CONTAINER (bar), 0);
+
+    /* buttons */
+
+    button_vbox = gtk_vbox_new (FALSE, 0);
+    gtk_widget_show (button_vbox);
+
+    close = gtk_button_new ();
+    gtk_button_set_relief (GTK_BUTTON (close), GTK_RELIEF_NONE);
+    gtk_widget_set_tooltip_text (close,
+            /* SUN_BRANDING */
+            _("Close Time Slider and return to original directory"));
+    gtk_widget_show (close);
+
+    g_signal_connect (close,
+            "clicked",
+            G_CALLBACK (close_clicked_callback),
+            bar);
+
+    image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+            GTK_ICON_SIZE_MENU);
+    gtk_widget_show (image);
+
+    gtk_container_add (GTK_CONTAINER (close), image);
+
+    gtk_box_pack_start (GTK_BOX (button_vbox), close, FALSE, FALSE, 0);
+
+    delete = gtk_button_new ();
+    gtk_button_set_relief (GTK_BUTTON (delete), GTK_RELIEF_NONE);
+    gtk_widget_set_tooltip_text (delete,
+            /* SUN_BRANDING */
+            _("Take a zfs snapshot of this directory now"));
+    gtk_widget_show (delete);
+
+    g_signal_connect (delete,
+            "clicked",
+            G_CALLBACK (delete_clicked_callback),
+            bar);
+
+    {
+        GtkIconTheme *it =  gtk_icon_theme_get_default ();
+        GdkPixbuf * pb = gtk_icon_theme_load_icon (it,
+                "user-trash-full.png",
+                16,
+                GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+                NULL);
+        bar->priv->delete_image = gtk_image_new_from_pixbuf (pb);
+        g_object_unref (pb);
+    }
+
+    path = caja_pixmap_file ("camera.png");
+    bar->priv->camera_image = gtk_image_new_from_file (path);
+    g_free (path);
+
+    gtk_widget_show (bar->priv->delete_image);
+    gtk_widget_show (bar->priv->camera_image);
+
+    gtk_container_add (GTK_CONTAINER (delete), bar->priv->camera_image);
+
+    gtk_box_pack_end (GTK_BOX (button_vbox), delete, FALSE, FALSE, 0);
+    gtk_box_pack_end (GTK_BOX (hbox), button_vbox, FALSE, FALSE, 0);
+
+    bar->priv->delete_button = delete;
+
+    bar->priv->scale = timescale_new();
+
+    g_signal_connect (bar->priv->scale,
+            "value-changed",
+            G_CALLBACK (slider_moved_callback),
+            bar);
+
+    gtk_widget_show (bar->priv->scale);
+
+    gtk_box_pack_start (GTK_BOX (hbox), bar->priv->scale, TRUE, TRUE, 0);
+
+}
+
+    CajaDirectory *
+caja_zfs_bar_get_dir (CajaZfsBar* bar)
+{
+    g_return_val_if_fail (CAJA_IS_ZFS_BAR (bar), NULL);
+    return bar->priv->dir;
+}
+
+static void
+snapshot_data_ready (CajaDirectory *dir,
+                     GCancellable  *cancellable,
+                     gpointer callback_data)
+{
+    CajaWindowSlot *slot;
+    GFile *location;
+    GFile *dir_location;
+    CajaWindow *window = (CajaWindow*)callback_data;
+
+    g_return_if_fail (CAJA_IS_WINDOW (window));
+
+    slot = caja_window_get_active_slot (window);
+    location = caja_window_slot_get_location (slot);
+    dir_location = caja_directory_get_location (dir);
+
+    if (g_cancellable_is_cancelled (cancellable) && g_file_equal (location, dir_location))
+    {
+        caja_navigation_window_set_spinner_active (CAJA_NAVIGATION_WINDOW (window), FALSE);
+        caja_navigation_window_set_restore_icon ( CAJA_NAVIGATION_WINDOW (window), RESTORE_NO);
+    }
+    else if (g_file_equal (location, dir_location))
+    {
+        char *path = g_file_get_path (dir_location);
+        g_cancellable_cancel (cancellable);
+        caja_window_slot_set_allow_stop (slot, FALSE);
+        caja_navigation_window_set_restore_icon ( CAJA_NAVIGATION_WINDOW (window),
+                caja_directory_has_snapshots (dir) ? RESTORE_NORMAL : RESTORE_NO);
+        if (caja_directory_has_snapshots (dir))
+            caja_window_allow_restore (window, TRUE);
+        else
+            caja_window_allow_restore (window, FALSE);
+        g_free (path);
+    }
+    else
+    {
+        caja_window_slot_set_allow_stop (slot, FALSE);
+    }
+    g_object_unref (location);
+    g_object_unref (dir_location);
+}
+
+
+void
+caja_zfs_bar_cancel_tasks (CajaWindow *window)
+{
+    if (CAJA_IS_WINDOW (window))
+    {
+        if (CAJA_IS_WINDOW_SLOT (window->details->active_pane->active_slot))
+        {
+            CajaDirectory *directory = NULL;
+            g_cancellable_cancel (window->details->active_pane->active_slot->find_zfs_snapshots_cancellable);
+            g_object_unref (window->details->active_pane->active_slot->find_zfs_snapshots_cancellable);
+            window->details->active_pane->active_slot->find_zfs_snapshots_cancellable = NULL;
+            directory = caja_directory_get (window->details->active_pane->active_slot->location);
+            if (directory)
+            {
+                caja_directory_cancel_restore_info (directory);
+                caja_directory_unref (directory);
+            }
+        }
+        if (CAJA_IS_NAVIGATION_WINDOW (window))
+        {
+            CajaZfsBar *bar = CAJA_ZFS_BAR (CAJA_NAVIGATION_WINDOW (window)->zfs_bar);
+            monitor_zfs_snap_directory_cancel (bar->priv->zfs_dir_monitor_data);
+            bar->priv->zfs_dir_monitor_data = NULL;
+        }
+    }
+}
+
+void
+caja_zfs_bar_hide (CajaZfsBar *bar)
+{
+    bar->priv->explicit_user_hide = TRUE;
+    close_clicked_callback (NULL, bar);
+}
+
+
+/* Display AND Scan */
+
+void
+caja_zfs_bar_display (CajaZfsBar *bar,
+                      CajaWindow *window,
+                      CajaDirectory *new_dir,
+                      GCancellable *cancellable)
+{
+    gboolean show = FALSE;
+    gboolean time_slider_enabled = g_settings_get_boolean (caja_preferences,
+                                                        CAJA_PREFERENCES_ENABLE_TIME_SLIDER);
+    gboolean visible = gtk_widget_get_visible (GTK_WIDGET (bar));
+    gboolean enable_button = FALSE;
+    gboolean do_scan = FALSE;
+    gboolean do_cancel = FALSE;
+
+
+    /* if bar visible
+     *    if feature disabled
+     *      close bar
+     *      disable button
+     *    if in root dir
+     *      enable button
+     * else
+     *    if bar was previously displayed and in same tab
+     *       if in snapshot
+     *         redisplay
+     *         re-align
+     *         disable button
+     *       if root dir
+     *         redisplay
+     *         re-align
+     *         enable button
+     *       else
+     *          scan
+     *    else
+     *      if feature enabled
+     *        scan
+     *        disable button
+     *
+     *
+     *  NOTE : action_restore_callback display bar when enabled
+     */
+
+    if (visible)
+    {
+        if (!time_slider_enabled)
+        {
+            close_clicked_callback (NULL, bar);
+            return;
+        }
+        if (new_dir == bar->priv->dir)
+            enable_button = TRUE;
+        if (caja_directory_is_a_snapshot_dir_of (new_dir, bar->priv->dir) || new_dir == bar->priv->dir)
+        {
+            show = TRUE;
+            do_cancel = TRUE;
+            enable_button = TRUE;
+        }
+        else
+            do_scan = TRUE;
+    }
+    else
+    { /* bar is not visible */
+        CajaWindowSlot *slot = caja_window_get_active_slot (window);
+
+        if (bar->priv->is_setup && slot == bar->priv->slot && time_slider_enabled) /* check if we can redisplay the bar */
+        {
+            if (caja_directory_is_a_snapshot_dir_of (new_dir, bar->priv->dir) && !bar->priv->explicit_user_hide)
+                show = TRUE;
+
+            if (bar->priv->explicit_user_hide)
+                enable_button = FALSE;
+
+            if (new_dir == bar->priv->dir)
+            {
+                show = TRUE;
+                enable_button = TRUE;
+            }
+            else
+                do_scan = TRUE;
+        }
+        else
+        { /* icon and throbber set is snapshot_data_ready */
+            if (time_slider_enabled)
+                do_scan = TRUE;
+        }
+
+    }
+
+    if (enable_button) /* if button enabled set the icon to normal */
+        caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (window), RESTORE_NORMAL);
+
+    caja_window_allow_restore (window, enable_button);
+
+
+    if (show)
+    {
+        gtk_widget_show (GTK_WIDGET (bar));
+        caja_zfs_set_snap (bar, new_dir);
+    }
+    else
+    {
+        gtk_widget_hide (GTK_WIDGET (bar));
+        if (bar->priv->action)
+            gtk_toggle_action_set_active (bar->priv->action, FALSE);
+    }
+
+    if (do_cancel)
+        g_cancellable_cancel (cancellable);
+
+    if (do_scan)
+    {
+        g_cancellable_reset (cancellable);
+        caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (window), RESTORE_SEARCH);
+        caja_window_slot_set_allow_stop (caja_window_get_active_slot (window), TRUE);
+        caja_directory_get_snapshots_async (new_dir,
+                snapshot_data_ready,
+                cancellable,
+                window);
+    }
+
+    /* {
+       GFile *file = caja_directory_get_location (new_dir);
+       char *path = g_file_get_path (file);
+
+       printf ("caja_zfs_bar_display %s\nenable_button : %s, show : %s, do_cancel : %s, do_scan : %s\n\n",
+       path,
+       enable_button ? "true" : "false",
+       show ? "true" : "false",
+       do_cancel ? "true" : "false",
+       do_scan ? "true" : "false");
+       g_free (path);
+       }*/
+
+}
+
+void
+caja_zfs_set_snap (CajaZfsBar *bar,
+                   CajaDirectory *dir)
+{
+    GList* match = NULL;
+    GList* snap_list = NULL;
+    gboolean in_snap = FALSE;
+    char real_path [PATH_MAX+1];
+    GFile *file;
+    char* path;
+    int pos;
+    int set_pos = -2;
+
+    if (!bar->priv->is_setup)
+        return;
+
+    file = caja_directory_get_location (dir);
+    path = g_file_get_path (file);
+    ts_realpath (path, real_path);
+
+
+    if (ts_is_in_remote_backup (real_path))
+    {
+    /*FIXME: we do not have it */
+        //mate_vfs_init();
+        g_object_ref (dir);
+    }
+
+    in_snap = ts_is_in_snapshot (real_path);
+
+    if (in_snap)
+    {
+        snap_list = caja_directory_get_snapshots (bar->priv->dir);
+        match = g_list_find_custom (snap_list, real_path, (GCompareFunc)match_func);
+    }
+    g_free (path);
+    g_object_unref (file);
+
+    timescale_set_position (TIMESCALE (bar->priv->scale), match ? ((ZfsDataSet*)match->data)->mountpoint : NULL);
+    update_delete_or_snap_button (bar, in_snap);
+
+    /*  printf ("caja_zfs_set_snap current_path %s real_path %s match %s\n", bar->priv->current_path, real_path,
+        match ? "found" : "not found");*/
+
+    if (bar->priv->current_path && (strcmp (bar->priv->current_path, real_path) != 0))
+    {
+        bar->priv->set_only = TRUE;
+        bar->priv->set_only = FALSE;
+
+    }
+}
+
+static void
+snapshot_data_ready_from_change (CajaDirectory *dir,
+                                 GCancellable  *cancellable,
+                                 gpointer       callback_data)
+{
+    CajaZfsBar *bar = CAJA_ZFS_BAR (callback_data);
+
+    snapshot_data_ready (dir, cancellable, bar->priv->slot->pane->window);
+    update_range (bar);
+    gtk_widget_set_sensitive (bar->priv->scale, TRUE);
+}
+
+static void
+zfs_dir_change_callback (ZfsSnapDirMonitor *monitor_data,
+                         CajaZfsBar *bar)
+{
+    gtk_widget_set_sensitive (bar->priv->scale, FALSE);
+
+    g_cancellable_reset (bar->priv->slot->find_zfs_snapshots_cancellable);
+    caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (bar->priv->slot->pane->window), RESTORE_SEARCH);
+    caja_window_slot_set_allow_stop (bar->priv->slot, TRUE);
+    caja_directory_get_snapshots_async (bar->priv->dir,
+            snapshot_data_ready_from_change,
+            bar->priv->slot->find_zfs_snapshots_cancellable,
+            bar);
+}
+
+static char*
+get_backup_dir (GList *snaplist)
+{
+    GList *tmp = snaplist;
+
+    while (tmp)
+    {
+        ZfsDataSet *snap = (ZfsDataSet*) tmp->data;
+        if (snap->type == 0)
+        {
+            char **root_split = NULL;
+            char *result = NULL;
+            root_split = g_strsplit (snap->mountpoint, snap->name, 2);
+            /*printf (" name: %s\n mountpoint: %s\n mtime_str :%s\n space used : %s\n size in kilobytes : %f\n",
+              snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str, snap->used_space); */
+            result = g_strdup (root_split[0]);
+            if (root_split)
+                g_strfreev (root_split);
+            return result;
+        }
+        tmp = tmp->next;
+    }
+    return NULL;
+}
+
+void
+caja_zfs_bar_setup (CajaZfsBar* bar,
+                    CajaDirectory *dir,
+                    CajaWindowSlot *active_slot,
+                    GtkToggleAction* action)
+{
+    GFile *file;
+    char *path, *zfs_dir, *backup_dir = NULL;
+    bar->priv->dir = dir;
+    g_object_ref (dir);
+    bar->priv->slot = active_slot;
+    g_object_ref (active_slot);
+
+    bar->priv->action = action;
+    set_scale_range (bar, TRUE);
+    bar->priv->is_setup = TRUE;
+    bar->priv->explicit_user_hide = FALSE;
+
+    file = caja_directory_get_location (dir);
+    path = g_file_get_path (file);
+    zfs_dir = ts_get_snapshot_dir (path);
+    backup_dir = get_backup_dir (caja_directory_get_snapshots (dir));
+
+    bar->priv->zfs_dir_monitor_data = monitor_zfs_snap_directory (zfs_dir,
+            backup_dir,
+            (ZfsDirChangeCallback) zfs_dir_change_callback,
+            bar);
+    caja_zfs_set_snap (bar, dir);
+    g_free (path);
+    g_free (zfs_dir);
+    if (backup_dir)
+        g_free(backup_dir);
+    g_object_unref (file);
+}
+
+static void
+zfs_bar_show_column (GtkWidget *widget, gpointer user_data)
+{
+    char **visible_columns;
+    gboolean restore_col_visible = FALSE;
+    int i = 0;
+    GPtrArray *ret = NULL;
+
+    visible_columns = g_settings_get_strv (caja_list_view_preferences,
+                                     CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+
+    ret = g_ptr_array_new ();
+
+    /* convert visible_columns in ptr array without "restore_info" */
+    while (visible_columns[i])
+    {
+        if (strcmp (visible_columns [i], "restore_info") == 0)
+        {
+            restore_col_visible = TRUE;
+            break;
+        }
+        else
+            g_ptr_array_add (ret, g_strdup (visible_columns [i]));
+        i++;
+    }
+
+    g_strfreev (visible_columns);
+
+    if (restore_col_visible)
+    {
+        if (!gtk_widget_get_visible (widget)) /* hide bar remove pref */
+        {
+            char **col_array;
+            g_ptr_array_add (ret, NULL);
+            col_array = (char **)g_ptr_array_free (ret, FALSE);
+            g_settings_set_strv (caja_list_view_preferences,
+                             CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+                                 (const char * const *)col_array);
+            g_strfreev (col_array);
+            ret = NULL;
+        }
+    }
+    else
+    {
+        if (gtk_widget_get_visible (widget))
+        {
+            char **col_array;
+            g_ptr_array_add (ret,strdup ("restore_info"));
+            g_ptr_array_add (ret,NULL);
+            col_array = (char **)g_ptr_array_free (ret, FALSE);
+            g_settings_set_strv (caja_list_view_preferences,
+                             CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+                                 (const char * const *)col_array);
+            g_strfreev (col_array);
+            ret = NULL;
+        }
+
+    }
+
+    if (ret)
+        g_ptr_array_free (ret, TRUE);
+
+}
+
+static void
+zfs_bar_hidden (GtkWidget *widget, gpointer user_data)
+{
+    CajaZfsBar *bar = CAJA_ZFS_BAR (user_data);
+    monitor_zfs_snap_directory_cancel (bar->priv->zfs_dir_monitor_data);
+    bar->priv->zfs_dir_monitor_data = NULL;
+    caja_directory_cancel_restore_info (bar->priv->dir);
+}
+
+GtkWidget *
+caja_zfs_bar_new ()
+{
+    GObject *bar;
+    CajaZfsBar *zfs_bar;
+
+    bar = g_object_new (CAJA_TYPE_ZFS_BAR, NULL);
+
+    g_signal_connect_object (bar, "show", G_CALLBACK (zfs_bar_show_column), bar, 0);
+    g_signal_connect_object (bar, "hide", G_CALLBACK (zfs_bar_show_column), bar, 0);
+    g_signal_connect_object (bar, "hide", G_CALLBACK (zfs_bar_hidden), bar, 0);
+
+    zfs_bar_show_column (GTK_WIDGET (bar), NULL);
+
+    ts_is_restore_column_enabled_init ();
+
+    zfs_bar = CAJA_ZFS_BAR (bar);
+
+    return GTK_WIDGET (bar);
+}
+
components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch
New file
@@ -0,0 +1,26 @@
--- caja-1.28.0/src/caja-window-slot.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-slot.h    2024-02-26 08:42:41.082428896 +0100
@@ -113,6 +113,7 @@
     gpointer open_callback_user_data;
     GCancellable *find_mount_cancellable;
+    GCancellable *find_zfs_snapshots_cancellable;
     gboolean visible;
 };
--- caja-1.28.0/src/caja-window-slot.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-slot.c    2024-02-26 08:42:41.082244159 +0100
@@ -686,6 +686,13 @@
         slot->find_mount_cancellable = NULL;
     }
+    if (slot->find_zfs_snapshots_cancellable)
+    {
+        g_cancellable_cancel (slot->find_zfs_snapshots_cancellable);
+        g_object_unref (slot->find_zfs_snapshots_cancellable);
+        slot->find_zfs_snapshots_cancellable = NULL;
+    }
+
     slot->pane = NULL;
     g_free (slot->title);
components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch
New file
@@ -0,0 +1,183 @@
--- caja-1.28.0/src/caja-navigation-window-ui.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window-ui.xml    2024-02-26 08:42:41.080238291 +0100
@@ -63,6 +63,7 @@
     <toolitem name="Up" action="Up"/>
     <toolitem name="Stop" action="Stop"/>
     <toolitem name="Reload" action="Reload"/>
+    <toolitem name="Restore" action="Restore"/>
     <separator/>
     <toolitem name="Home" action="Home"/>
     <toolitem name="Computer" action="Go to Computer"/>
--- caja-1.28.0/src/caja-navigation-window.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window.h    2024-02-26 09:27:56.105335195 +0100
@@ -67,10 +67,18 @@
     /** UI stuff **/
     CajaSidePane *sidebar;
+    GtkWidget    *zfs_bar;
+
     /* Current views stuff */
     GList *sidebar_panels;
 };
+typedef enum {
+  RESTORE_NORMAL,
+  RESTORE_SEARCH,
+  RESTORE_NO
+} CajaNavigationRestoreIconType;
+
 struct _CajaNavigationWindowClass
 {
     CajaWindowClass parent_spot;
@@ -91,6 +99,9 @@
 void     caja_navigation_window_hide_sidebar         (CajaNavigationWindow *window);
 void     caja_navigation_window_show_sidebar         (CajaNavigationWindow *window);
 gboolean caja_navigation_window_sidebar_showing      (CajaNavigationWindow *window);
+gboolean Caja_navigation_window_zfs_bar_showing      (CajaNavigationWindow *window);
+void     Caja_navigation_window_set_restore_icon     (CajaNavigationWindow* window,
+                                                      CajaNavigationRestoreIconType type);
 void     caja_navigation_window_add_sidebar_panel    (CajaNavigationWindow *window,
         CajaSidebar          *sidebar_panel);
 void     caja_navigation_window_remove_sidebar_panel (CajaNavigationWindow *window,
--- caja-1.28.0/src/caja-navigation-window.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window.c    2024-02-26 08:42:41.080677444 +0100
@@ -71,6 +71,7 @@
 #include "caja-notebook.h"
 #include "caja-window-manage-views.h"
 #include "caja-navigation-window-pane.h"
+#include "caja-zfs-bar.h"
 #define MAX_TITLE_LENGTH 180
@@ -107,6 +108,13 @@
 };
 static void
+restore_pref_changed (CajaWindow *window)
+{
+    g_assert (CAJA_IS_WINDOW (window));
+    caja_window_reload (window, FALSE);
+}
+
+static void
 caja_navigation_window_init (CajaNavigationWindow *window)
 {
     GtkUIManager *ui_manager;
@@ -167,6 +175,16 @@
     ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
     toolbar = gtk_ui_manager_get_widget (ui_manager, "/Toolbar");
+
+    /* add custom icon */
+    caja_navigation_window_set_restore_icon (window, RESTORE_SEARCH);
+
+    /* add preference callback */
+    g_signal_connect_swapped (caja_desktop_preferences,
+                              g_strconcat ("changed::", CAJA_PREFERENCES_ENABLE_TIME_SLIDER, NULL),
+                              G_CALLBACK (restore_pref_changed),
+                              (gpointer) window);
+
     gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
     window->details->toolbar = toolbar;
     gtk_widget_set_hexpand (toolbar, TRUE);
@@ -183,6 +201,12 @@
     caja_navigation_window_allow_back (window, FALSE);
     caja_navigation_window_allow_forward (window, FALSE);
+    window->zfs_bar = caja_zfs_bar_new ();
+
+    gtk_grid_attach(GTK_GRID (CAJA_WINDOW (window)->details->grid),
+                      window->zfs_bar,
+                      0, 2, 1, 1);
+
     g_signal_connect_swapped (caja_preferences,
                               "changed::" CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY,
                               G_CALLBACK(always_use_location_entry_changed),
@@ -348,6 +372,59 @@
     }
 }
+void
+caja_navigation_window_set_restore_icon (CajaNavigationWindow* window,
+                                             CajaNavigationRestoreIconType type)
+{
+    static gboolean init = 0;
+    static GdkPixbuf *normal = NULL;
+    static GdkPixbuf *search = NULL;
+    static GdkPixbuf *no = NULL;
+    GdkPixbuf *pb = NULL;
+    GtkWidget *image = NULL;
+    GtkAction* action = gtk_ui_manager_get_action (caja_window_get_ui_manager (CAJA_WINDOW (window)), "/Toolbar/Restore");
+
+    if (!init)
+    {
+        char *path = caja_pixmap_file ("restore.png");
+        normal = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        path = caja_pixmap_file ("restore-search.png");
+        search = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        path = caja_pixmap_file ("restore-no.png");
+        no = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        init = TRUE;
+    }
+
+    switch (type)
+    {
+      case RESTORE_NORMAL :
+        pb = normal;
+        break;
+      case RESTORE_SEARCH:
+        pb = search;
+        break;
+      case RESTORE_NO:
+        pb = no;
+        break;
+    }
+
+    image = gtk_image_new_from_pixbuf (pb);
+    g_object_ref (image);
+    gtk_widget_show (image);
+    GSList *tmp = gtk_action_get_proxies (action);
+    for (tmp; tmp ; tmp = tmp->next)
+    {
+        GtkWidget *proxy = (GtkWidget *)tmp->data;
+        if (GTK_IS_TOOL_BUTTON (proxy))
+        {
+            gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (proxy), image);
+        }
+    }
+}
+
 static void
 side_pane_close_requested_callback (GtkWidget *widget,
                                     gpointer user_data)
@@ -1207,6 +1284,7 @@
 static void
 real_window_close (CajaWindow *window)
 {
+    caja_zfs_bar_cancel_tasks (window);
     caja_navigation_window_save_geometry (CAJA_NAVIGATION_WINDOW (window));
 }
@@ -1428,6 +1506,19 @@
     caja_navigation_window_update_split_view_actions_sensitivity (window);
 }
+
+gboolean
+caja_navigation_window_zfs_bar_showing (CajaNavigationWindow *window)
+{
+    g_return_val_if_fail (CAJA_IS_NAVIGATION_WINDOW (window), FALSE);
+
+    if (window->zfs_bar != NULL)
+    {
+        return gtk_widget_get_visible(GTK_WIDGET (window->zfs_bar));
+    }
+    return FALSE;
+}
+
 gboolean
 caja_navigation_window_split_view_showing (CajaNavigationWindow *window)
 {
components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch
New file
@@ -0,0 +1,20 @@
--- caja-1.28.0/icons/Makefile.am.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/icons/Makefile.am    2024-02-26 08:42:41.073927090 +0100
@@ -11,6 +11,17 @@
     erase.png \
     knob.png \
     thumbnail_frame.png \
+    camera.png \
+    group_neg.png \
+    group.png \
+    mask.png \
+    other_neg.png \
+    other.png \
+    restore-no.png \
+    restore-search.png \
+    restore.png \
+    user_neg.png \
+    user.png \
     $(NULL)
 EXTRA_DIST = $(icon_DATA)
components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch
New file
@@ -0,0 +1,2835 @@
--- caja-1.28.0/libcaja-private/caja-column-utilities.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-column-utilities.c    2024-02-26 08:42:41.074153382 +0100
@@ -182,6 +182,14 @@
     caja_module_extension_list_free (providers);
+    columns = g_list_append (columns,
+                         g_object_new (CAJA_TYPE_COLUMN,
+                                           "name", "restore_info",
+                                           "attribute", "restore_info",
+                                           "label", _("Restore information"),
+                                           "description", _("Restore information of the file."),
+                                           NULL));
+
     return columns;
 }
--- caja-1.28.0/libcaja-private/caja-directory-async.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory-async.c    2024-02-26 08:46:37.475055194 +0100
@@ -767,6 +767,11 @@
         REQUEST_SET_TYPE (request, REQUEST_FILESYSTEM_INFO);
     }
+    if (file_attributes & CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)
+    {
+        REQUEST_SET_TYPE (request, REQUEST_RESTORE_INFO);
+    }
+
     return request;
 }
@@ -5161,6 +5166,19 @@
     }
 }
+void
+caja_directory_cancel_restore_info (CajaDirectory *directory)
+{
+    if (CAJA_IS_DIRECTORY (directory))
+    {
+        if (directory->details->restore_cancel)
+        {
+            g_cancellable_cancel (directory->details->restore_cancel);
+            directory->details->restore_cancel = NULL;
+        }
+    }
+}
+
 static void
 cancel_loading_attributes (CajaDirectory *directory,
                            CajaFileAttributes file_attributes)
@@ -5213,6 +5231,11 @@
         mount_cancel (directory);
     }
+    if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO))
+    {
+        caja_directory_cancel_restore_info (directory);
+    }
+
     caja_directory_async_state_changed (directory);
 }
@@ -5263,6 +5286,10 @@
     {
         cancel_mount_for_file (directory, file);
     }
+    if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO))
+    {
+        caja_directory_cancel_restore_info (directory);
+    }
     caja_directory_async_state_changed (directory);
 }
--- caja-1.28.0/libcaja-private/caja-directory-private.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory-private.h    2024-02-26 08:42:41.075455631 +0100
@@ -64,6 +64,7 @@
     REQUEST_THUMBNAIL,
     REQUEST_MOUNT,
     REQUEST_FILESYSTEM_INFO,
+    REQUEST_RESTORE_INFO,
     REQUEST_TYPE_LAST
 } RequestType;
@@ -144,6 +145,10 @@
     guint64 free_space; /* (guint)-1 for unknown */
     time_t free_space_read; /* The time free_space was updated, or 0 for never */
+
+    GCancellable *restore_cancel;
+    /* zfs snapshot info */
+    GList *zfs_snapshots;
 };
 CajaDirectory *caja_directory_get_existing                    (GFile                     *location);
--- caja-1.28.0/libcaja-private/caja-directory.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory.c    2024-02-26 08:42:41.075943421 +0100
@@ -40,6 +40,7 @@
 #include "caja-metadata.h"
 #include "caja-desktop-directory.h"
 #include "caja-vfs-directory.h"
+#include "caja-zfs.h"
 enum
 {
@@ -120,6 +121,8 @@
     directory->details->low_priority_queue = caja_file_queue_new ();
     directory->details->extension_queue = caja_file_queue_new ();
     directory->details->free_space = (guint64)-1;
+    directory->details->zfs_snapshots = NULL;
+    directory->details->restore_cancel = NULL;
 }
 CajaDirectory *
@@ -191,6 +194,16 @@
     g_assert (directory->details->file_list == NULL);
     g_hash_table_destroy (directory->details->file_hash);
+    if (directory->details->zfs_snapshots)
+    {
+        ts_free_snapshots (directory->details->zfs_snapshots);
+    }
+
+    if (directory->details->restore_cancel)
+    {
+      g_cancellable_cancel (directory->details->restore_cancel);
+    }
+
     caja_file_queue_destroy (directory->details->high_priority_queue);
     caja_file_queue_destroy (directory->details->low_priority_queue);
     caja_file_queue_destroy (directory->details->extension_queue);
@@ -219,6 +232,21 @@
     caja_file_list_free (files);
 }
+static gboolean
+time_slider_enabled = TRUE;
+
+gboolean
+caja_is_time_slider_enabled ()
+{
+    return time_slider_enabled;
+}
+
+static void time_slider_pref_changed_callback (gpointer callback_data)
+{
+    time_slider_enabled = g_settings_get_boolean (caja_preferences,
+                                               CAJA_PREFERENCES_ENABLE_TIME_SLIDER);
+}
+
 static void
 collect_all_directories (gpointer key, gpointer value, gpointer callback_data)
 {
@@ -454,6 +482,7 @@
 {
     CajaDirectory *directory;
     char *uri;
+    char *path;
     uri = g_file_get_uri (location);
@@ -472,6 +501,8 @@
     else
     {
         directory = CAJA_DIRECTORY (g_object_new (CAJA_TYPE_VFS_DIRECTORY, NULL));
+        path = g_file_get_path (location);
+        g_free (path);
     }
     set_directory_location (directory, location);
@@ -495,6 +526,206 @@
            g_file_is_native (directory->details->location);
 }
+typedef struct
+{
+    CajaDirectory    *dir;
+    GCancellable    *cancel;
+    TsReadyCallback  callback;
+    gpointer         callback_user_data;
+} QuerySnapshotsAsyncData;
+
+
+static void
+snapshot_list_ready_callback (GObject *source_object,
+        GAsyncResult *res,
+        gpointer user_data)
+{
+    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+    QuerySnapshotsAsyncData *data = (QuerySnapshotsAsyncData*) user_data;
+
+    if (!g_cancellable_is_cancelled (data->cancel))
+    {
+        data->dir->details->zfs_snapshots = g_simple_async_result_get_op_res_gpointer (simple);
+    }
+
+    data->callback (data->dir, data->cancel, data->callback_user_data);
+}
+
+void
+caja_directory_get_snapshots_async (CajaDirectory *directory,
+        TsReadyCallback ready_callback,
+        GCancellable *cancel,
+        gpointer      callback_user_data)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->location == NULL)
+        return;
+
+    if (directory->details->zfs_snapshots)
+    {
+        ts_free_snapshots (directory->details->zfs_snapshots);
+        directory->details->zfs_snapshots = NULL;
+    }
+
+    if (caja_is_time_slider_enabled ())
+    {
+        QuerySnapshotsAsyncData *data;
+        data = g_new0 (QuerySnapshotsAsyncData,1);
+        data->dir = directory;
+        data->cancel = cancel;
+        data->callback = ready_callback;
+        data->callback_user_data = callback_user_data;
+
+        ts_get_snapshots_for_dir_async (directory->details->location,
+                snapshot_list_ready_callback,
+                cancel,
+                data);
+    }
+}
+
+gboolean
+caja_directory_has_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->zfs_snapshots)
+        return TRUE;
+
+    return FALSE;
+}
+
+int
+caja_directory_get_num_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->zfs_snapshots)
+    {
+        int i = 0;
+        GList *tmp;
+        for (tmp = directory->details->zfs_snapshots;tmp;tmp = tmp->next)
+            i++;
+        return i;
+    }
+    return 0;
+}
+
+gboolean
+caja_directory_is_in_snapshot (CajaDirectory *directory)
+{
+    char *directory_uri;
+    gboolean result = FALSE;
+
+    g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+
+    directory_uri = caja_directory_get_uri (directory);
+
+    result = ts_is_in_snapshot (directory_uri);
+
+    g_free (directory_uri);
+
+    return result;
+}
+
+GList *
+caja_directory_get_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    return directory->details->zfs_snapshots;
+}
+
+void
+caja_directory_remove_snapshot (CajaDirectory *directory,
+                                ZfsDataSet *snap)
+{
+    if (directory->details->zfs_snapshots)
+    {
+        directory->details->zfs_snapshots = g_list_remove (directory->details->zfs_snapshots, snap);
+        ts_free_zfs_dataset (snap);
+    }
+}
+
+/* return true if snapdir dir path is a dir or subdir of refdir */
+gboolean
+caja_directory_is_a_snapshot_dir_of (CajaDirectory *snapdir,
+                                     CajaDirectory *refdir)
+{
+
+    gboolean result = FALSE;
+
+    if (caja_directory_is_in_snapshot (snapdir))
+    {
+        char snapdir_root_real_path [PATH_MAX+1];
+        char refdir_real_path [PATH_MAX+1];
+        CajaDirectory *snapdir_root = caja_directory_get_snap_root (snapdir);
+        GFile *snapdir_root_file = caja_directory_get_location (snapdir_root);
+        GFile *refdir_file = caja_directory_get_location (refdir);
+        char* snapdir_root_path = g_file_get_path (snapdir_root_file);
+        char* refdir_path = g_file_get_path (refdir_file);
+
+        if (ts_realpath (snapdir_root_path, snapdir_root_real_path) &&
+                ts_realpath (refdir_path, refdir_real_path))
+        {
+            if (g_strrstr (snapdir_root_real_path,refdir_real_path))
+                result = TRUE;
+        }
+
+        g_free (snapdir_root_path);
+        g_free (refdir_path);
+        g_object_unref (snapdir_root_file);
+        g_object_unref (refdir_file);
+        g_object_unref (snapdir_root);
+    }
+
+    return result;
+}
+
+CajaDirectory *
+caja_directory_get_snap_root (CajaDirectory      *directory)
+{
+    char *directory_uri, *snap_root;
+    char *zfs, *iter;
+    int count = 0;
+    CajaDirectory *new_dir;
+
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    directory_uri = caja_directory_get_uri (directory);
+
+
+    if (!caja_directory_is_in_snapshot (directory))
+    {
+        g_free (directory_uri);
+        return directory;
+    }
+
+    /*remove .zfs/snapshot/blah/ */
+    zfs = g_strrstr (directory_uri, ".zfs/snapshot/");
+    iter = zfs;
+
+    if (iter)
+    {
+        iter += sizeof (".zfs/snapshot/");
+        while (*iter != '/' && *iter != '\0')
+            iter++;
+
+        if (*iter == '/')
+            iter++;
+
+        *zfs = '\0';
+        snap_root = g_strdup_printf ("%s%s", directory_uri, iter);
+
+        *zfs = 'a';
+        g_free (directory_uri);
+        new_dir = caja_directory_get_by_uri (snap_root);
+        g_free (snap_root);
+        return new_dir;
+    }
+    return directory;
+}
+
 gboolean
 caja_directory_is_in_trash (CajaDirectory *directory)
 {
--- caja-1.28.0/libcaja-private/caja-directory.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory.h    2024-02-26 08:42:41.076213417 +0100
@@ -29,6 +29,7 @@
 #include <gio/gio.h>
 #include "caja-file-attributes.h"
+#include "caja-zfs.h"
 G_BEGIN_DECLS
@@ -215,6 +216,24 @@
 gboolean           caja_directory_is_in_trash              (CajaDirectory         *directory);
+/* ZFS snasphots management. */
+typedef void (*TsReadyCallback) (CajaDirectory *directory, GCancellable *cancellable, gpointer           callback_data);
+
+void               caja_directory_get_snapshots_async      (CajaDirectory         *directory,
+                                                            TsReadyCallback        ready_callback,
+                                                            GCancellable          *cancel,
+                                                            gpointer               callback_user_data);
+gboolean           caja_directory_has_snapshots            (CajaDirectory         *directory);
+gboolean           caja_directory_is_in_snapshot           (CajaDirectory         *directory);
+int                caja_directory_get_num_snapshots        (CajaDirectory         *directory);
+GList *            caja_directory_get_snapshots            (CajaDirectory         *directory);
+void               caja_directory_remove_snapshot          (CajaDirectory         *directory,
+                                                            ZfsDataSet            *snap);
+CajaDirectory *    caja_directory_get_snap_root            (CajaDirectory         *directory);
+gboolean           caja_directory_is_a_snapshot_dir_of     (CajaDirectory         *snapdir,
+                                                            CajaDirectory         *refdir);
+void               caja_directory_cancel_restore_info      (CajaDirectory         *directory);
+
 /* Return false if directory contains anything besides a Caja metafile.
  * Only valid if directory is monitored. Used by the Trash monitor.
  */
@@ -234,6 +253,8 @@
 gboolean           caja_directory_is_editable              (CajaDirectory         *directory);
+gboolean           caja_is_time_slider_enabled             ();
+
 G_END_DECLS
 #endif /* CAJA_DIRECTORY_H */
--- caja-1.28.0/libcaja-private/caja-file-attributes.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file-attributes.h    2024-02-26 08:42:41.076404101 +0100
@@ -42,6 +42,7 @@
     CAJA_FILE_ATTRIBUTE_THUMBNAIL = 1 << 8,
     CAJA_FILE_ATTRIBUTE_MOUNT = 1 << 9,
     CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 10,
+    CAJA_FILE_ATTRIBUTE_RESTORE_INFO = 1 << 12,
 } CajaFileAttributes;
 #endif /* CAJA_FILE_ATTRIBUTES_H */
--- caja-1.28.0/libcaja-private/caja-file-private.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file-private.h    2024-02-26 08:42:41.076641510 +0100
@@ -154,6 +154,13 @@
     /* Mount for mountpoint or the references GMount for a "mountable" */
     GMount *mount;
+    /* Time slider file difference information */
+    char *restore_info;
+
+    /* Snapshot directory for versions */
+    char *snapshot_directory;
+    GCancellable *has_snapshot_cancel;
+
     /* boolean fields: bitfield to save space, since there can be
            many CajaFile objects. */
@@ -201,6 +208,13 @@
     eel_boolean_bit is_thumbnailing               : 1;
+    eel_boolean_bit restore_info_is_up_to_date    : 1;
+    eel_boolean_bit restore_info_in_progress      : 1;
+
+    eel_boolean_bit has_snap_versions_is_up_to_date    : 1;
+    eel_boolean_bit has_snap_versions_in_progress      : 1;
+    eel_boolean_bit has_snap_versions                  : 1;
+
     /* TRUE if the file is open in a spatial window */
     eel_boolean_bit has_open_window               : 1;
--- caja-1.28.0/libcaja-private/caja-file.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file.c    2024-02-26 08:42:41.077938703 +0100
@@ -71,6 +71,7 @@
 #include "caja-ui-utilities.h"
 #include "caja-vfs-file.h"
 #include "caja-saved-search-file.h"
+#include "caja-zfs.h"
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
@@ -149,7 +150,8 @@
     attribute_where_q,
     attribute_link_target_q,
     attribute_volume_q,
-    attribute_free_space_q;
+    attribute_free_space_q,
+        attribute_restore_info_q;;
 static void     caja_file_info_iface_init                (CajaFileInfoIface *iface);
 static char *   caja_file_get_owner_as_string            (CajaFile          *file,
@@ -159,6 +161,7 @@
                                   GFileInfo             *info);
 static const char * caja_file_peek_display_name (CajaFile *file);
 static const char * caja_file_peek_display_name_collation_key (CajaFile *file);
+static void invalidate_restore_info (CajaFile *file);
 static void file_mount_unmounted (GMount *mount,  gpointer data);
 static void metadata_hash_free (GHashTable *hash);
@@ -497,6 +500,15 @@
     g_clear_pointer (&file->details->filesystem_id, g_ref_string_release);
     file->details->filesystem_id = NULL;
+        g_free (file->details->restore_info);
+        file->details->restore_info = NULL;
+        invalidate_restore_info (file);
+        g_free (file->details->snapshot_directory);
+        file->details->snapshot_directory = NULL;
+        file->details->has_snap_versions_in_progress = FALSE;
+        file->details->has_snap_versions_is_up_to_date = FALSE;
+        file->details->has_snap_versions = FALSE;
+
     clear_metadata (file);
 }
@@ -815,6 +827,11 @@
     g_free (file->details->activation_uri);
     g_free (file->details->compare_by_emblem_cache);
+        g_free (file->details->restore_info);
+        if (file->details->snapshot_directory) {
+                g_free (file->details->snapshot_directory);
+        }
+
     if (file->details->thumbnail) {
         g_object_unref (file->details->thumbnail);
     }
@@ -4835,6 +4852,242 @@
     NULL
 };
+/* Following code is copied from Rhythmbox rb-cut-and-paste-code.c */
+
+/* Legal conversion specifiers, as specified in the C standard. */
+#define C_STANDARD_STRFTIME_CHARACTERS "aAbBcdHIjmMpSUwWxXyYZ"
+#define C_STANDARD_NUMERIC_STRFTIME_CHARACTERS "dHIjmMSUwWyY"
+#define SUS_EXTENDED_STRFTIME_MODIFIERS "EO"
+
+/**
+ * eel_strdup_strftime:
+ *
+ * Cover for standard date-and-time-formatting routine strftime that returns
+ * a newly-allocated string of the correct size. The caller is responsible
+ * for g_free-ing the returned string.
+ *
+ * Besides the buffer management, there are two differences between this
+ * and the library strftime:
+ *
+ *   1) The modifiers "-" and "_" between a "%" and a numeric directive
+ *      are defined as for the GNU version of strftime. "-" means "do not
+ *      pad the field" and "_" means "pad with spaces instead of zeroes".
+ *   2) Non-ANSI extensions to strftime are flagged at runtime with a
+ *      warning, so it's easy to notice use of the extensions without
+ *      testing with multiple versions of the library.
+ *
+ * @format: format string to pass to strftime. See strftime documentation
+ * for details.
+ * @time_pieces: date/time, in struct format.
+ *
+ * Return value: Newly allocated string containing the formatted time.
+ **/
+
+static char *
+eel_strdup_strftime (const char *format, struct tm *time_pieces)
+{
+  g_autoptr(GString) string = NULL;
+  const char *remainder, *percent;
+  char code[4], buffer[512];
+  char *piece, *result;
+  g_autofree gchar *converted = NULL;
+  size_t string_length;
+  gboolean strip_leading_zeros, turn_leading_zeros_to_spaces;
+  char modifier;
+  int i;
+
+  /* Format could be translated, and contain UTF-8 chars,
+   * so convert to locale encoding which strftime uses */
+  converted = g_locale_from_utf8 (format, -1, NULL, NULL, NULL);
+  if (!converted)
+    converted = g_strdup (format);
+
+  string = g_string_new ("");
+  remainder = converted;
+
+  /* Walk from % character to % character. */
+  for (;;) {
+    percent = strchr (remainder, '%');
+    if (percent == NULL) {
+      g_string_append (string, remainder);
+      break;
+    }
+    g_string_append_len (string, remainder,
+                         percent - remainder);
+
+    /* Handle the "%" character. */
+    remainder = percent + 1;
+    switch (*remainder) {
+      case '-':
+        strip_leading_zeros = TRUE;
+        turn_leading_zeros_to_spaces = FALSE;
+        remainder++;
+        break;
+      case '_':
+        strip_leading_zeros = FALSE;
+        turn_leading_zeros_to_spaces = TRUE;
+        remainder++;
+        break;
+      case '%':
+        g_string_append_c (string, '%');
+        remainder++;
+        continue;
+      case '\0':
+        g_warning ("Trailing %% passed to eel_strdup_strftime");
+        g_string_append_c (string, '%');
+        continue;
+      default:
+        strip_leading_zeros = FALSE;
+        turn_leading_zeros_to_spaces = FALSE;
+        break;
+    }
+
+    modifier = 0;
+    if (strchr (SUS_EXTENDED_STRFTIME_MODIFIERS, *remainder) != NULL) {
+      modifier = *remainder;
+      remainder++;
+
+      if (*remainder == 0) {
+        g_warning ("Unfinished %%%c modifier passed to eel_strdup_strftime", modifier);
+        break;
+      }
+    }
+
+    if (strchr (C_STANDARD_STRFTIME_CHARACTERS, *remainder) == NULL) {
+      g_warning ("eel_strdup_strftime does not support "
+                 "non-standard escape code %%%c",
+                 *remainder);
+    }
+
+    /* Convert code to strftime format. We have a fixed
+     * limit here that each code can expand to a maximum
+     * of 512 bytes, which is probably OK. There's no
+     * limit on the total size of the result string.
+     */
+    i = 0;
+    code[i++] = '%';
+    if (modifier != 0) {
+#ifdef HAVE_STRFTIME_EXTENSION
+      code[i++] = modifier;
+#endif
+    }
+    code[i++] = *remainder;
+    code[i++] = '\0';
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+    /* Format string under control of caller, since this is a wrapper for strftime. */
+    string_length = strftime (buffer, sizeof (buffer),
+                              code, time_pieces);
+#pragma GCC diagnostic pop
+    if (string_length == 0) {
+      /* We could put a warning here, but there's no
+       * way to tell a successful conversion to
+       * empty string from a failure.
+       */
+      buffer[0] = '\0';
+    }
+
+    /* Strip leading zeros if requested. */
+    piece = buffer;
+    if (strip_leading_zeros || turn_leading_zeros_to_spaces) {
+      if (strchr (C_STANDARD_NUMERIC_STRFTIME_CHARACTERS, *remainder) == NULL) {
+        g_warning ("eel_strdup_strftime does not support "
+                   "modifier for non-numeric escape code %%%c%c",
+                   remainder[-1],
+                   *remainder);
+      }
+      if (*piece == '0') {
+        do {
+          piece++;
+        } while (*piece == '0');
+        if (!g_ascii_isdigit (*piece)) {
+          piece--;
+        }
+      }
+      if (turn_leading_zeros_to_spaces) {
+        memset (buffer, ' ', piece - buffer);
+        piece = buffer;
+      }
+    }
+    remainder++;
+
+    /* Add this piece. */
+    g_string_append (string, piece);
+  }
+
+  /* Convert the string back into utf-8. */
+  result = g_locale_to_utf8 (string->str, -1, NULL, NULL, NULL);
+
+  return result;
+}
+
+char *
+caja_date_as_string (time_t time_raw, gboolean use_smallest)
+{
+    struct tm *ttime;
+    const char **formats;
+    const char *width_template;
+    const char *format;
+    char *date_string;
+    char *result;
+    GDate *today;
+    GDate *date;
+    guint32 date_age;
+    int i;
+
+    ttime = localtime (&time_raw);
+
+    if (!use_smallest) {
+        if (date_format_pref == CAJA_DATE_FORMAT_LOCALE) {
+          return eel_strdup_strftime ("%c", ttime);
+        } else if (date_format_pref == CAJA_DATE_FORMAT_ISO) {
+          return eel_strdup_strftime ("%Y-%m-%d %H:%M:%S",ttime);
+        }
+    }
+
+    date = g_date_new ();
+    g_date_set_time_t (date, time_raw);
+
+    today = g_date_new ();
+    g_date_set_time_t (today, time (NULL));
+
+    /* Overflow results in a large number; fine for our purposes. */
+    date_age = (g_date_get_julian (today) -
+                g_date_get_julian (date));
+
+    g_date_free (date);
+    g_date_free (today);
+
+    /* Format varies depending on how old the date is. This minimizes
+     * the length (and thus clutter & complication) of typical dates
+     * while providing sufficient detail for recent dates to make
+     * them maximally understandable at a glance. Keep all format
+     * strings separate rather than combining bits & pieces for
+     * internationalization's sake.
+     */
+
+    if (date_age == 0) {
+        formats = TODAY_TIME_FORMATS;
+    } else if (date_age == 1) {
+        formats = YESTERDAY_TIME_FORMATS;
+    } else if (date_age < 7) {
+        formats = CURRENT_WEEK_TIME_FORMATS;
+    } else {
+        formats = CURRENT_WEEK_TIME_FORMATS;
+    }
+
+    if (!use_smallest)
+      format = _(formats[1]);
+    else
+      {
+        int i=0;
+        while (formats[i] != NULL)
+          i++;
+        format = _(formats[i-3]);
+      }
+    return eel_strdup_strftime (format, ttime);
+}
+
 static char *
 caja_file_fit_date_as_string (CajaFile *file,
                   CajaDateType date_type,
@@ -6608,6 +6861,9 @@
     if (attribute_q == attribute_free_space_q) {
         return caja_file_get_volume_free_space (file);
     }
+    if (attribute_q == attribute_restore_info_q) {
+                return caja_file_get_restore_info_async (file);
+        }
     extension_attribute = NULL;
@@ -7654,6 +7910,616 @@
 }
+
+gboolean
+caja_file_is_in_snapshot (CajaFile *file)
+{
+  char *file_uri = caja_file_get_uri (file);
+  gboolean result = ts_is_in_snapshot (file_uri);
+  g_free (file_uri);
+  return result;
+}
+
+static gboolean caja_file_in_snap_exist_in_current (CajaFile *file, GCancellable *cancel)
+{
+  /* get path without /.zfs/snapshot/blah/ */
+  /* test is file exist */
+  char *file_uri = caja_file_get_uri (file);
+  char *file_uri_without_snap = NULL;
+  gboolean result = FALSE;
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (file_uri);
+      return FALSE;
+    }
+
+  file_uri_without_snap = ts_remove_snapshot_dir (file_uri);
+
+  if (file_uri_without_snap)
+    {
+      GFile* root_file = g_file_new_for_uri (file_uri_without_snap);
+      char *path = g_file_get_path (root_file);
+
+      if (path)
+    {
+      result =  g_file_test (path, G_FILE_TEST_EXISTS);
+      g_free (path);
+    }
+      g_object_unref (root_file);
+      g_free (file_uri_without_snap);
+
+    }
+
+  g_free (file_uri);
+
+  return result;
+}
+
+
+char * caja_file_in_snapshot_get_info (CajaFile *file, GCancellable *cancel)
+{
+  char *info = NULL;
+  GFile *then_gfile = caja_file_get_location (file);
+  char *then_path = g_file_get_path (then_gfile);
+  g_object_unref (then_gfile);
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (then_gfile);
+      g_free (then_path);
+      return g_strdup ("cancelled");
+    }
+  if (then_path)
+    {
+      struct stat64 now;
+      struct stat64 then;
+      char *now_path = ts_remove_snapshot_dir (then_path);
+
+      if (lstat64 (now_path, &now) == 0)
+    {
+      if (lstat64 (then_path, &then) == 0)
+        {
+
+          if (now.st_mtime != then.st_mtime)
+        {
+          if (now.st_size == then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, same size as latest version"));
+          else if (now.st_size > then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, smaller than latest version"));
+          else if ( now.st_size < then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, bigger than latest version"));
+        }
+          else
+        /* SUN_BRANDING */
+        info = g_strdup (_("identical to latest version"));
+        }
+      else
+        info = g_strdup_printf ("FIXME no then %s", then_path);
+    }
+      else
+    /* SUN_BRANDING */
+    info = g_strdup (_("not present in latest version"));
+
+      g_free (now_path);
+      g_free (then_path);
+    }
+
+  return info;
+}
+
+static char * restore_string (char *str, GCancellable *cancel)
+{
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (str);
+      return g_strdup (_("unknown"));
+    }
+  else
+    return str;
+}
+
+gint time_cmp (time_t *a,
+           time_t *b)
+{
+  if (*a == *b)
+    return 0;
+  if (*a > *b)
+    return 1;
+  if (*a < *b)
+    return -1;
+
+}
+
+char *
+caja_file_get_num_snapshot_version (CajaFile *file,
+                                        GCancellable *cancel,
+                                        gboolean stop_at_first)
+{
+  GList *tmp = NULL;
+  GList *tmp2 = NULL;
+  GList *time = NULL;
+  time_t* now_time = NULL;
+  char *result = NULL;
+  int version = 0;
+  CajaFile *parent = NULL;
+  CajaDirectory *dir = NULL;
+  char *snapdir = NULL;
+
+  if (CAJA_IS_FILE (file))
+    {
+      parent = caja_file_get_parent (file);
+      if (parent)
+    {
+      dir = caja_directory_get_for_file (parent);
+      g_object_unref (parent);
+    }
+    }
+  if (dir)
+    {
+      struct stat64 now;
+      struct stat64 then;
+      char snap_name[PATH_MAX+1];
+      char *name = caja_file_get_name (file);
+
+      g_object_ref (dir);
+      tmp = caja_directory_get_snapshots (dir);
+
+      GFile *now_gfile = caja_file_get_location (file);
+      char *now_path = g_file_get_path (now_gfile);
+      g_object_unref (now_gfile);
+
+      if (now_path)
+    {
+      if (lstat64 (now_path, &now) != 0)
+        {
+          g_free (now_path);
+          g_object_unref (dir);
+          return NULL;
+        }
+    }
+
+      g_free (now_path);
+
+      time = NULL;
+
+      /* get list of mtime for all files in snapshots */
+
+      now_time = g_new0 (time_t, 1);
+      *now_time = now.st_mtim.tv_sec;
+      time = g_list_prepend (time, now_time);
+
+
+      for (tmp; tmp; tmp = tmp->next)
+    {
+      g_snprintf (snap_name, sizeof(snap_name), "%s/%s",
+             ((ZfsDataSet *) tmp->data)->mountpoint,
+             name);
+      if (g_cancellable_is_cancelled (cancel))
+        goto cancel;
+      if (lstat64 (snap_name, &then) == 0)
+        {
+          if (g_list_find_custom (time, &then.st_mtim.tv_sec, (GCompareFunc) time_cmp) == NULL)
+        { /*insert in list only is unique */
+          time_t* snap_time = g_new0 (time_t, 1);
+          *snap_time = then.st_mtim.tv_sec;
+          time = g_list_prepend (time, snap_time);
+                  if (stop_at_first)
+                    {
+                      snapdir = g_strdup (((ZfsDataSet *) tmp->data)->mountpoint);
+                      goto cancel;
+                    }
+        }
+        }
+
+    }
+cancel:
+      g_free (name);
+      g_object_unref (dir);
+    }
+
+
+  for (tmp = time; tmp; tmp = tmp->next)
+    {
+      g_free ((time_t*) tmp->data);
+      version++;
+    }
+
+  /* remove current version */
+  version--;
+
+  g_list_free (time);
+
+  if (version == 0)
+    {
+      if (stop_at_first)
+        return NULL;
+      else /*SUN_BRANDING*/
+        return restore_string (g_strdup_printf (_("no other version")), cancel);
+    }
+
+  if (stop_at_first)
+    return snapdir;
+  else
+    return restore_string (g_strdup_printf ("%d %s", version,
+                                            /* SUN_BRANDING */
+                                            version > 1 ? _("other versions") : /* SUN_BRANDING */ _("other version")),
+                           cancel);
+}
+
+static gboolean worker_thread_started = FALSE;
+
+typedef void (*ReadyCallback) (gpointer          data,
+                   GCancellable      *cancellable);
+typedef void (*WorkerFunction) (gpointer          data,
+                   GCancellable      *cancellable);
+typedef struct {
+  gpointer        data;
+  gpointer        return_data;
+  ReadyCallback        ready_callback;
+  WorkerFunction    worker_func;
+  GCancellable        *cancellable;
+} QueryData;
+
+static void
+caja_file_get_restore_info (gpointer data,
+                GCancellable       *cancellable)
+{
+  QueryData *qdata = (QueryData*) data;
+  CajaFile *file = CAJA_FILE (qdata->data);
+  char *result = NULL;
+
+  /*{
+    struct timespec ts;
+    ts.tv_sec = 1;
+    ts.tv_nsec = 0;
+    nanosleep (&ts, NULL);
+  }
+
+    {
+      GFile *f = caja_file_get_location (file);
+      char *path = g_file_get_uri (f);
+      printf ("start restore info for %s", path);
+      g_free (path);
+      g_object_unref (f);
+    }*/
+  if (!g_cancellable_is_cancelled (cancellable))
+    {
+
+      if (caja_file_is_directory (file))
+    {
+      CajaDirectory *dir = caja_directory_get_for_file (file);
+      g_object_ref (dir);
+      if (caja_directory_is_in_snapshot (dir))
+        {
+          if (!caja_file_in_snap_exist_in_current (file, cancellable))
+        /* SUN_BRANDING */
+        result = g_strdup (_("not present in latest version"));
+          else
+        /* SUN_BRANDING */
+        result = g_strdup (_("present in latest version"));
+        }
+      else
+        {
+          int version = caja_directory_get_num_snapshots (dir);
+
+          if (version == 0)
+        /* SUN_BRANDING */
+        result = g_strdup (_("no version"));
+          else
+        result = g_strdup_printf ("%d %s",version,
+                      /* SUN_BRANDING */
+                      version > 1 ? _("versions") : /* SUN_BRANDING */ _("version"));
+        }
+      g_object_unref (dir);
+    }
+      else
+    {
+      if (caja_file_is_in_snapshot (file))
+          result = caja_file_in_snapshot_get_info (file, cancellable);
+      else
+          result = caja_file_get_num_snapshot_version (file, cancellable, FALSE);
+    }
+    }
+
+/*    {
+      printf ("is %s\n", result);
+    }*/
+
+
+  qdata->return_data = restore_string (result, cancellable);
+}
+
+
+static void restore_information_ready_callback (gpointer data,
+                        GCancellable *cancellable)
+{
+  QueryData *qdata = (QueryData*) data;
+  CajaFile *file = (CajaFile*) qdata->data;
+  char *return_data = qdata->return_data;
+
+  if (!CAJA_IS_FILE (file))
+    return;
+
+  file->details->restore_info_in_progress = FALSE;
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      file->details->restore_info = g_strdup (_("unknown"));
+      invalidate_restore_info (file);
+      if (return_data)
+    g_free (return_data);
+    }
+  else
+    {
+      file->details->restore_info_is_up_to_date = TRUE;
+      file->details->restore_info = return_data;
+    }
+
+  caja_file_changed (file);
+  caja_file_unref (file);
+}
+
+
+static gboolean
+complete_in_idle_cb (gpointer data)
+{
+  QueryData *qdata = (QueryData*)data;
+  qdata->ready_callback (data, qdata->cancellable);
+  g_free (qdata);
+  return FALSE;
+}
+
+static void
+worker_queue_finished_callback (GObject *source_object,
+                GAsyncResult *res,
+                gpointer user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GCancellable *cancel = (GCancellable*) user_data;
+
+  worker_thread_started = FALSE;
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      return;
+    }
+
+  g_simple_async_result_get_op_res_gpointer (simple);
+
+}
+
+static void
+worker_queue_func (GSimpleAsyncResult *res,
+           GObject            *object,
+           GCancellable       *cancellable)
+{
+  QueryData *data = NULL;
+
+  GTimeVal timeout;
+  GAsyncQueue *queue = (GAsyncQueue*) g_simple_async_result_get_op_res_gpointer (res);
+  g_async_queue_ref (queue);
+
+  g_get_current_time (&timeout);
+  g_time_val_add (&timeout, 3000000);
+
+  data = g_async_queue_timed_pop (queue, &timeout);
+
+  while (data)
+    {
+      GSource *source;
+
+      /* only call the worker fct if not cancel
+       * but execute ready function anyway */
+      if (!g_cancellable_is_cancelled (data->cancellable))
+    data->worker_func (data, data->cancellable);
+
+      /*call ready callback in main loop/thread */
+      source = g_idle_source_new ();
+      g_source_set_priority (source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (source, complete_in_idle_cb, data, NULL);
+      g_source_attach (source, NULL);
+      g_source_unref (source);
+
+      /* pop next one */
+      g_get_current_time (&timeout);
+      g_time_val_add (&timeout, 3000000);
+      data = g_async_queue_timed_pop (queue, &timeout);
+    }
+
+  g_async_queue_unref (queue);
+}
+
+char * caja_file_get_restore_info_async (CajaFile *file)
+{
+  if (!caja_is_time_slider_enabled ())
+    return NULL;
+
+  if (!ts_is_restore_column_enabled ())
+    return NULL;
+
+  if (file->details->restore_info_is_up_to_date)
+    {
+      /*if ( file->details->restore_info == NULL)
+    return g_strdup ("null cached info");*/
+      return g_strdup (file->details->restore_info);
+    }
+
+  if (file->details->restore_info_in_progress)
+    return g_strdup ("...");
+  else
+    {
+      static GAsyncQueue *queue = NULL;
+      QueryData *data  = NULL;
+
+      if (!file->details->directory)
+    return g_strdup ("no directory element\n");
+
+      if (!caja_directory_has_snapshots (file->details->directory) && !caja_file_is_in_snapshot (file))
+    return g_strdup ("doesn't have snap nor is in snap\n");
+
+      if (!file->details->directory->details->restore_cancel)
+    {
+      file->details->directory->details->restore_cancel = g_cancellable_new ();
+    }
+      else
+    {
+      if (g_cancellable_is_cancelled (file->details->directory->details->restore_cancel))
+        return NULL;
+    }
+
+      g_free (file->details->restore_info);
+      file->details->restore_info = NULL;
+      file->details->restore_info_in_progress = TRUE;
+
+      if (!queue)
+    queue = g_async_queue_new ();
+
+      data = g_new0 (QueryData, 1);
+      data->data = file;
+      caja_file_ref (file);
+      data->cancellable = file->details->directory->details->restore_cancel;
+      data->ready_callback = restore_information_ready_callback;
+      data->worker_func = caja_file_get_restore_info;
+
+      g_async_queue_push (queue, data);
+
+      if (!worker_thread_started)
+    {
+      GSimpleAsyncResult *res;
+      worker_thread_started = TRUE;
+
+      res = g_simple_async_result_new (G_OBJECT (file),
+                       worker_queue_finished_callback,
+                       NULL,
+                       (gpointer) worker_queue_func);
+
+      g_simple_async_result_set_op_res_gpointer (res, queue, NULL);
+      g_simple_async_result_run_in_thread (res,
+                           worker_queue_func,
+                           G_PRIORITY_DEFAULT,
+                           data->cancellable);
+    }
+
+      return g_strdup ("...");
+    }
+}
+
+HasSnapshotResult
+caja_file_has_snapshot_version (CajaFile *file)
+{
+    if (file->details->has_snap_versions_is_up_to_date)
+        return (file->details->has_snap_versions);
+    return UNKNOWN_STATE;
+}
+
+typedef struct {
+  CajaFile              *file;
+  GCancellable              *cancel;
+  FileHasSnapshotCallback    callback;
+  gpointer             callback_user_data;
+  char                      *snap_dir;
+} HasSnapshotAsyncData;
+
+typedef void (*HasSnapReadyCallback) (CajaDirectory *file,
+                                      GCancellable    *cancel,
+                                      gpointer           callback_data);
+
+
+static void has_snapshot_ready_callback (GObject *source_object,
+                                         GAsyncResult *res,
+                                         gpointer user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  HasSnapshotAsyncData *data = (HasSnapshotAsyncData*) user_data;
+
+  if (g_cancellable_is_cancelled (data->cancel))
+    {
+      data->file->details->has_snap_versions_in_progress = FALSE;
+      data->file->details->has_snap_versions_is_up_to_date = FALSE;
+      if (data->file->details->snapshot_directory)
+        g_free (data->file->details->snapshot_directory);
+
+      data->file->details->has_snapshot_cancel = NULL;
+    }
+  else
+    {
+      data->file->details->has_snap_versions_in_progress = FALSE;
+      data->file->details->has_snap_versions_is_up_to_date = TRUE;
+      if (data->file->details->snapshot_directory)
+        g_free (data->file->details->snapshot_directory);
+      data->file->details->snapshot_directory = g_simple_async_result_get_op_res_gpointer (simple);
+      if (data->file->details->snapshot_directory)
+        data->file->details->has_snap_versions = TRUE;
+      else
+        data->file->details->has_snap_versions = FALSE;
+    }
+  data->callback (data->callback_user_data);
+}
+char *
+caja_file_get_snapshot_dir (CajaFile *file)
+{
+  return file->details->snapshot_directory;
+}
+void caja_file_real_get_snapshot_version (GSimpleAsyncResult *res,
+                                  GObject            *object,
+                              GCancellable       *cancellable)
+{
+  CajaFile *file = CAJA_FILE (object);
+  char *snap_info = caja_file_get_num_snapshot_version (file, cancellable, TRUE);
+
+  if (!snap_info) /* scan for .zfs directory*/
+    snap_info = ts_get_not_zfs_snapshot_dir (caja_file_get_location (file));
+/*
+  {
+    struct timespec ts;
+    ts.tv_sec = 4;
+    ts.tv_nsec = 0;
+    nanosleep (&ts, NULL);
+  }
+*/
+  if (snap_info)
+    g_simple_async_result_set_op_res_gpointer (res, snap_info, (GDestroyNotify) NULL);
+  else
+    g_simple_async_result_set_op_res_gpointer (res, NULL, (GDestroyNotify) NULL);
+}
+
+void caja_file_get_snapshot_version (CajaFile *file,
+                                         FileHasSnapshotCallback callback,
+                                         GCancellable *cancel,
+                                         gpointer user_data)
+{
+    HasSnapshotAsyncData *data;
+    GSimpleAsyncResult *res;
+
+    if (file->details->has_snap_versions_in_progress)
+    {
+        g_cancellable_cancel(file->details->has_snapshot_cancel);
+        file->details->has_snapshot_cancel = NULL;
+        file->details->has_snap_versions_in_progress = FALSE;
+    }
+
+    file->details->has_snapshot_cancel = cancel;
+    file->details->has_snap_versions_in_progress = TRUE;
+    file->details->has_snap_versions_is_up_to_date  = FALSE;
+
+    data = g_new0 (HasSnapshotAsyncData, 1);
+    data->file = file;
+    data->cancel = cancel;
+    data->callback = callback;
+    data->callback_user_data = user_data;
+
+    res = g_simple_async_result_new (G_OBJECT (file),
+                                     has_snapshot_ready_callback,
+                                     data,
+                                    (gpointer) caja_file_real_get_snapshot_version);
+    g_simple_async_result_run_in_thread (res, caja_file_real_get_snapshot_version,
+                                         G_PRIORITY_DEFAULT, cancel);
+}
+
 void
 caja_file_mark_gone (CajaFile *file)
 {
@@ -7920,6 +8786,12 @@
     file->details->mount_is_up_to_date = FALSE;
 }
+static void
+invalidate_restore_info (CajaFile *file)
+{
+        file->details->restore_info_is_up_to_date = FALSE;
+}
+
 void
 caja_file_invalidate_extension_info_internal (CajaFile *file)
 {
@@ -7974,6 +8846,9 @@
     if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) {
         invalidate_thumbnail (file);
     }
+        if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO)) {
+                invalidate_restore_info (file);
+        }
     if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) {
         invalidate_mount (file);
     }
@@ -8054,7 +8929,8 @@
         CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT |
         CAJA_FILE_ATTRIBUTE_EXTENSION_INFO |
         CAJA_FILE_ATTRIBUTE_THUMBNAIL |
-        CAJA_FILE_ATTRIBUTE_MOUNT;
+        CAJA_FILE_ATTRIBUTE_MOUNT |
+                CAJA_FILE_ATTRIBUTE_RESTORE_INFO ;
 }
 void
@@ -8621,6 +9497,7 @@
     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_restore_info_q = g_quark_from_static_string ("restore_info");
     G_OBJECT_CLASS (class)->finalize = finalize;
     G_OBJECT_CLASS (class)->constructor = caja_file_constructor;
--- caja-1.28.0/libcaja-private/caja-file.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file.h    2024-02-26 08:42:41.078215891 +0100
@@ -194,6 +194,7 @@
         const char                     *mime_type);
 gboolean                caja_file_is_launchable                     (CajaFile                   *file);
 gboolean                caja_file_is_symbolic_link                  (CajaFile                   *file);
+gboolean                caja_file_is_in_snapshot                    (CajaFile                   *file);
 gboolean                caja_file_is_mountpoint                     (CajaFile                   *file);
 GMount *                caja_file_get_mount                         (CajaFile                   *file);
 char *                  caja_file_get_volume_free_space             (CajaFile                   *file);
@@ -250,6 +251,27 @@
 CajaFile *          caja_file_get_trash_original_file           (CajaFile                   *file);
+/* Time slider */
+char *                  caja_file_get_num_snapshot_version          (CajaFile                   *file,
+                                                                     GCancellable               *cancel,
+                                                                     gboolean                    stop_at_first);
+char *                  caja_file_get_restore_info_async            (CajaFile                   *file);
+
+typedef enum {
+    NO,
+    YES,
+    UNKNOWN_STATE
+} HasSnapshotResult;
+
+HasSnapshotResult       caja_file_has_snapshot_version              (CajaFile                   *file);
+char *                  caja_file_get_snapshot_dir                  (CajaFile                   *file);
+typedef void (*FileHasSnapshotCallback)    (gpointer user_data);
+
+void                    caja_file_get_snapshot_version              (CajaFile                   *file,
+                                                                     FileHasSnapshotCallback     callback,
+                                                                     GCancellable               *cancel,
+                                                                     gpointer                    user_data);
+
 /* Permissions. */
 gboolean                caja_file_can_get_permissions               (CajaFile                   *file);
 gboolean                caja_file_can_set_permissions               (CajaFile                   *file);
--- caja-1.28.0/libcaja-private/caja-global-preferences.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-global-preferences.h    2024-02-26 08:42:41.078422380 +0100
@@ -70,6 +70,9 @@
 #define CAJA_PREFERENCES_USE_IEC_UNITS            "use-iec-units"
 #define CAJA_PREFERENCES_SHOW_ICONS_IN_LIST_VIEW    "show-icons-in-list-view"
+/* Time slider */
+#define CAJA_PREFERENCES_ENABLE_TIME_SLIDER             "enable-time-slider"
+
 /* Mouse */
 #define CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS     "mouse-use-extra-buttons"
 #define CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON        "mouse-forward-button"
--- caja-1.28.0/libcaja-private/caja-zfs.c.orig    2024-02-26 08:42:41.078919123 +0100
+++ caja-1.28.0/libcaja-private/caja-zfs.c    2024-02-26 08:42:41.078857872 +0100
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+
+#include <stdio.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "caja-zfs.h"
+#include <time.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdint.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <eel/eel-glib-extensions.h>
+#include <sys/mnttab.h>
+#include <sys/mkdev.h>
+#include <libscf.h>
+#include <dirent.h>
+#include <sys/utsname.h>
+#include  "caja-global-preferences.h"
+#define ZFS_SNAPSHOT_DIR ".zfs/snapshot/"
+#define ZFS_BACKUP_DIR ".time-slider/rsync"
+
+#ifndef ZFS_MAXNAMELEN
+#ifdef ZFS_MAX_DATASET_NAME_LEN
+#define ZFS_MAXNAMELEN ZFS_MAX_DATASET_NAME_LEN
+#else
+#define ZFS_MAXNAMELEN 256
+#endif
+#endif
+
+
+char* ts_realpath (char * dir, char *resolved_name)
+{
+  char real_dir[PATH_MAX+1];
+  char real_path[PATH_MAX+1];
+  gboolean  found = FALSE;
+  struct stat64 dir_stat64;
+  char *result;
+
+  result = realpath(dir, real_dir);
+
+  if (!result)
+    return NULL;
+
+  if (stat64 (real_dir, &dir_stat64) == 0)
+    {
+      if (strcmp (dir_stat64.st_fstype, "lofs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          if (strcmp (mtab.mnt_fstype, "lofs") == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+            {
+              if (strcmp (real_dir, mtab.mnt_mountp) == 0)
+            strcpy (real_path, mtab.mnt_special);
+              else
+            {
+              gchar **split;
+              split = g_strsplit (real_dir, mtab.mnt_mountp, 2);
+              /*split 2nd part contains path without mount point */
+              g_snprintf (real_path,sizeof(real_path),"%s%s",mtab.mnt_special,split[1]);
+              g_strfreev (split);
+            }
+              found = TRUE;
+              break;
+            }
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+  if (found)
+      return strcpy (resolved_name, real_path);
+  else
+      return strcpy (resolved_name, real_dir);
+}
+
+static void ts_set_snapshot_used_space (zfs_handle_t *zhp, ZfsDataSet *snap)
+{
+  gchar buf[ZFS_MAXNAMELEN];
+  if (zfs_prop_get(zhp, ZFS_PROP_USED, buf, sizeof (buf), NULL, NULL, 0, B_FALSE) == 0)
+    {
+      char unit[10];
+      char format_float[5] = "%f%s";
+      char format_int[5] = "%d%s";
+      char *format = format_int;
+      int   used_space_int = 0;
+      gboolean success = FALSE;
+
+      snap->used_space_str = g_strdup (buf);
+
+      if (strchr (buf, '.'))
+    {
+      format = format_float;
+      if (sscanf(buf, format,&snap->used_space,unit) == 2)
+        success = TRUE;
+    }
+      else
+    {
+      if (sscanf(buf, format,&used_space_int,unit) == 2)
+        {
+          success = TRUE;
+          snap->used_space = (float) used_space_int;
+        }
+    }
+      if (strcmp (buf, "0") == 0)
+    {
+      g_free (snap->used_space_str);
+      snap->used_space_str = g_strdup ("0 K");
+      success = TRUE;
+    }
+
+      if (success)
+    {
+      if (strcmp (unit, "M") == 0)
+        snap->used_space *= 1024;
+      if (strcmp (unit, "G") == 0)
+        snap->used_space *= 1024 * 1024;
+    }
+      else
+    {
+      g_free (snap->used_space_str);
+      /* SUN_BRANDING */
+      snap->used_space_str = g_strdup (_("Unknown"));
+    }
+    }
+  else
+    {
+      g_free (snap->used_space_str);
+      /* SUN_BRANDING */
+      snap->used_space_str = g_strdup (_("Unknown"));
+    }
+}
+
+static void ts_set_snapshot_mtime_and_time_diff (zfs_handle_t *zhp, ZfsDataSet *snap)
+{
+  GDate now;
+  GDate then;
+  time_t time_now;
+  gint days_diff;
+  const gchar *format;
+  gchar *locale_format = NULL;
+  gchar buf[ZFS_MAXNAMELEN];
+  gchar *date_str = NULL;
+
+  if (zfs_prop_get(zhp, ZFS_PROP_CREATION, buf, sizeof (buf), NULL, NULL, 0, B_TRUE) == 0)
+    {
+      struct tm tms;
+
+      sscanf (buf, "%llu", &snap->mtime);
+      snap->mtime_str = caja_date_as_string (snap->mtime, FALSE);
+    }
+
+}
+
+void print_snap_list (char *dir, GList *snap_list)
+{
+  GList *tmp;
+  printf ("list of snapshots for %s :\n", dir);
+  for (tmp = snap_list; tmp->next; tmp = tmp->next)
+    {
+      ZfsDataSet *snap = (ZfsDataSet*) tmp->data;
+      printf (" name: %s\n mountpoint: %s\n mtime_str :%s\n space used : %s\n size in kilobytes : %f\n",
+          snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str, snap->used_space);
+
+    }
+  printf ("\n");
+}
+
+static GString *
+dump_zds (ZfsDataSet *zds)
+{
+  GString *msg;
+  gchar *type;
+
+  if (!zds)
+    return NULL;
+
+  msg = g_string_new ("");
+  g_string_printf (msg,
+           "\tname: %s\n"
+           "\tmountpoint: %s\n"
+           "\ttype: %s\n",
+           zds->name,zds->mountpoint, zfs_type_to_name(zds->type));
+  if (zds->snapshots)
+    {
+      GList *tmp;
+      g_string_append_printf(msg,"\tsnapshots :\n");
+      for (tmp=zds->snapshots;tmp;tmp = tmp->next)
+    {
+      ZfsDataSet *tmp_zds= (ZfsDataSet*) tmp->data;
+      g_string_append_printf (msg,"\t\tname: %s\n\t\tpath: %s\n",
+                  tmp_zds->name,
+                  tmp_zds->mountpoint);
+    }
+    }
+  g_string_append_printf (msg, "\n");
+  return msg;
+}
+
+
+static void
+dump_sds (SearchDataSet *sds)
+{
+  GString *msg;
+  gchar *type;
+  GList *tmp;
+
+  if (!sds)
+    {
+      printf ("Search DataSet is empty\n");
+      return;
+    }
+
+  msg = g_string_new ("");
+  g_string_printf (msg, "DDS Dump:\n"
+           "\tsearched_path: %s\n",
+           sds->searched_path);
+
+  g_string_append_printf (msg, "Zfs Data set :\n");
+  for (tmp=sds->datasets;tmp;tmp=tmp->next)
+    {
+      GString * zds_dump = dump_zds ((ZfsDataSet *)tmp->data);
+      g_string_append_printf (msg,"%s",zds_dump->str);
+      g_string_free (zds_dump, TRUE);
+    }
+  g_string_append_printf (msg, "\n");
+  printf ("%s", msg->str);
+  g_string_free (msg, TRUE);
+}
+
+static ZfsDataSet*
+ts_new_zfs_dataset (SearchDataSet* sds)
+{
+    ZfsDataSet *zds;
+    zds = g_new0 (ZfsDataSet, 1);
+    zds->search_dataset = sds;
+    return zds;
+}
+
+void
+ts_free_zfs_dataset (ZfsDataSet* zds)
+{
+    if (!zds)
+      return;
+    if (zds->name)
+      g_free (zds->name);
+    if (zds->mountpoint)
+      g_free (zds->mountpoint);
+    if (zds->mtime_str)
+      g_free (zds->mtime_str);
+    if (zds->used_space_str)
+      g_free (zds->used_space_str);
+
+    if (zds->snapshots)
+      {
+        GList *tmp;
+        for (tmp = zds->snapshots;tmp;tmp = tmp->next)
+          ts_free_zfs_dataset ((ZfsDataSet*)tmp->data);
+      }
+    g_free (zds);
+}
+
+static SearchDataSet *
+ts_new_search_dataset (GCancellable *cancel)
+{
+    SearchDataSet *sds;
+    sds = g_new0 (SearchDataSet, 1);
+    sds->cancel = cancel;
+    return sds;
+}
+static void
+ts_free_search_dataset (SearchDataSet *sds)
+{
+    if (!sds)
+      return;
+    if (sds->searched_path)
+      g_free (sds->searched_path);
+    if (sds->mountpoint)
+      g_free (sds->mountpoint);
+    if (sds->datasets)
+      {
+        GList *tmp;
+        for (tmp = sds->datasets;tmp;tmp = tmp->next)
+          ts_free_zfs_dataset ((ZfsDataSet*)tmp->data);
+      }
+    g_free (sds);
+}
+
+static char* construct_check_snapshot_path (SearchDataSet *sds, char* mountpoint, const char *name, char *searched_path)
+{
+  gchar *result = NULL;
+  gchar **split;
+  gchar **split2;
+
+  gchar *snap_name = NULL;
+  gchar *remaining_path = NULL;
+
+  /* get the snapshot name part pool@snap-name we are only interested in snap-name split[1] */
+  split = g_strsplit (name,"@",2);
+  /* get the path after the mountpoint */
+  split2 = g_strsplit (searched_path, mountpoint, 2);
+
+  if (split && split[1])
+      snap_name = split[1];
+
+  if (split2 && split2[1])
+      remaining_path = split2[1];
+
+/*  printf ("mountpoint : %s \nname : %s \nsearched_path: %s\n", mountpoint, name, searched_path);
+  printf ("split %s at @ = [%s] [%s]\n", name, split[0],split[1]);
+  printf ("split %s at [%s] = [%s] [%s]\n", searched_path, mountpoint, split2[0],split2[1]);
+  printf ("%s/.zfs/snapshot/%s/%s\n\n", mountpoint, split[1], split2[1]);*/
+
+  if (snap_name && remaining_path)
+    if (strcmp(mountpoint, "/") == 0)
+      result = g_strdup_printf ("/.zfs/snapshot/%s/%s", snap_name, remaining_path);
+    else
+      result = g_strdup_printf ("%s/.zfs/snapshot/%s/%s", mountpoint, snap_name, remaining_path);
+
+  g_strfreev (split);
+  g_strfreev (split2);
+
+  /* don't test for file presence if searched path is the same as the mount point */
+  if (sds->searched_path_match_mp)
+      return result;
+
+  if (result && g_file_test (result, G_FILE_TEST_IS_DIR))
+      {
+    char real_dir[PATH_MAX+1];
+    if (!ts_realpath(result, real_dir))
+      {
+        g_free (result);
+        result = NULL;
+      }
+    else
+      {
+        g_free (result);
+        result = g_strdup (real_dir);
+      }
+    return result;
+      }
+
+  g_free (result);
+  return NULL;
+}
+
+static int
+snapshot_callback (zfs_handle_t *zhp, void *data)
+{
+  ZfsDataSet *main_zds = (ZfsDataSet*) data;
+
+  /* only add snapshot dir that exist */
+
+  if (zfs_get_type (zhp) == ZFS_TYPE_SNAPSHOT && !g_cancellable_is_cancelled (main_zds->search_dataset->cancel))
+    {
+      const char* name = zfs_get_name (zhp);
+      char *snap_path = construct_check_snapshot_path (main_zds->search_dataset,
+                               main_zds->mountpoint,
+                               name,
+                               main_zds->search_dataset->searched_path);
+      if (snap_path)
+    {
+      ZfsDataSet *zds = ts_new_zfs_dataset (main_zds->search_dataset);
+      zds->name = g_strdup (name);
+      zds->type = ZFS_TYPE_SNAPSHOT;
+      zds->mountpoint = snap_path;
+      ts_set_snapshot_mtime_and_time_diff (zhp, zds);
+      ts_set_snapshot_used_space (zhp, zds);
+      main_zds->snapshots = g_list_append (main_zds->snapshots,zds);
+    }
+    }
+  zfs_close (zhp);
+  return 0;
+}
+
+
+static struct mnttab *
+mygetmntent(FILE *f)
+{
+  static struct mnttab mt;
+  int status;
+
+  if ((status = getmntent(f, &mt)) == 0)
+    return (&mt);
+
+  return (NULL);
+}
+
+static char *
+is_fs_mounted (const char *fs_name)
+{
+  FILE           *mnttab;
+  struct mnttab    *mntp;
+
+
+  mnttab = fopen (MNTTAB,"r");
+
+  while ((mntp = mygetmntent(mnttab)) != NULL)
+    {
+      if (mntp->mnt_fstype == (char *)0 || strcmp(mntp->mnt_fstype, "zfs") != 0)
+    continue;
+      if (strcmp (mntp->mnt_special, fs_name) == 0)
+    {
+      fclose (mnttab);
+      return g_strdup (mntp->mnt_mountp);
+    }
+  }
+  fclose (mnttab);
+  return NULL;
+}
+
+static char* rsync_get_smf_dir()
+{
+  char data_store[MAXPATHLEN];
+
+  int retval = -1;
+
+  scf_handle_t    *handle = NULL;
+  scf_scope_t    *sc = NULL;
+  scf_service_t    *svc = NULL;
+  scf_instance_t *inst = NULL;
+  scf_propertygroup_t    *pg = NULL;
+  scf_property_t    *prop = NULL;
+  scf_value_t    *value = NULL;
+  scf_iter_t    *value_iter = NULL;
+
+
+  /* connect to the current SMF global repository */
+  handle = scf_handle_create(SCF_VERSION);
+
+  /* allocate scf resources */
+  sc = scf_scope_create(handle);
+  svc = scf_service_create(handle);
+  inst = scf_instance_create (handle);
+  pg = scf_pg_create(handle);
+  prop = scf_property_create(handle);
+  value = scf_value_create(handle);
+  value_iter = scf_iter_create(handle);
+
+  char *result = NULL;
+
+  /* if failed to allocate resources, exit */
+  if (handle == NULL || sc == NULL || svc == NULL || pg == NULL ||
+      prop == NULL || value == NULL || value_iter == NULL) {
+    /* scf handles allocation failed. */
+    goto out;
+  }
+
+  /* bind scf handle to the running svc.configd daemon */
+  if (scf_handle_bind(handle) == -1) {
+    /* scf binding failed. */
+    goto out;
+  }
+
+  /* get the scope of the localhost in the current repository */
+  if (scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) == -1) {
+    /* Getting scf scope failed.*/
+    goto out;
+  }
+
+  /* get the service within the scope */
+  if (scf_scope_get_service(sc, "application/time-slider/plugin", svc) == -1) {
+    /* failed getting service */
+    goto out;
+  }
+
+  /* get the instance within the service */
+  if (scf_service_get_instance(svc, "rsync", inst) == -1)
+    goto out;
+
+
+  /* get the property group within the instance */
+  if (scf_instance_get_pg(inst, "rsync", pg) == -1) {
+      /* Getting property group failed.  */
+    goto out;
+  }
+
+  /*
+   * Now get the properties.
+   */
+  if (scf_pg_get_property(pg, "target_dir", prop) == -1) {
+    goto out;
+  }
+
+  if (scf_property_get_value(prop, value) == -1) {
+    goto out;
+  }
+
+  data_store[0] = 0;
+  if (scf_value_get_astring(value, data_store, MAXPATHLEN) == -1) {
+    goto out;
+  }
+  else {
+    result = strdup (data_store);
+  }
+
+out:
+  /* destroy scf pointers */
+  if (value != NULL)
+    scf_value_destroy(value);
+  if (value_iter != NULL)
+    scf_iter_destroy(value_iter);
+  if (prop != NULL)
+    scf_property_destroy(prop);
+  if (pg != NULL)
+    scf_pg_destroy(pg);
+  if (inst != NULL)
+    scf_instance_destroy (inst);
+  if (svc != NULL)
+    scf_service_destroy(svc);
+  if (sc != NULL)
+    scf_scope_destroy(sc);
+  if (handle != NULL)
+    scf_handle_destroy(handle);
+
+  return result;
+}
+
+static char *rsync_get_dir (zfs_handle_t *zhp)
+{
+  nvlist_t *propval;
+
+  if (nvlist_lookup_nvlist(zfs_get_user_props(zhp),
+               "org.opensolaris:time-slider-rsync", &propval) == 0)
+    {
+      boolean_t ret_bool = FALSE;
+      char *strval;
+      char *dir;
+      nvlist_lookup_string(propval, ZPROP_VALUE, &strval);
+
+      if (strcmp (strval, "true") == 0)
+    {
+      dir = rsync_get_smf_dir ();
+      if (dir)
+        return dir;
+    }
+    }
+  return NULL;
+}
+
+void sync_backups_add (zfs_handle_t *zhp, ZfsDataSet *main_zds)
+{
+  char *rsync_dir = rsync_get_dir (zhp);
+  DIR *d;
+  struct dirent *dir;
+  char *fs_rsync_dir;
+  struct utsname machine;
+
+  if (!rsync_dir)
+    return;
+
+  /* format SMF backup dir , TIMESLIDER, nodename from uname, path, .time-slider/rsync */
+  if (uname (&machine) == -1)
+    return;
+
+  fs_rsync_dir = g_strdup_printf ("%s/TIMESLIDER/%s/%s/%s/",
+                  rsync_dir,
+                  machine.nodename,
+                  main_zds->name,
+                  ZFS_BACKUP_DIR);
+
+  if (!g_file_test (fs_rsync_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_free (rsync_dir);
+      g_free (fs_rsync_dir);
+      return;
+    }
+
+  d = opendir (fs_rsync_dir);
+
+  if (!d)
+    {
+      g_free (rsync_dir);
+      g_free (fs_rsync_dir);
+      return;
+    }
+
+  while ((dir = readdir (d)))
+    {
+      if (strstr (dir->d_name, "zfs-auto-snap_"))
+    { /* got a snap copy dir */
+      char **comma_split = NULL;
+      char **freq_split = NULL;
+      struct tm tms;
+      ZfsDataSet *zds = NULL;
+
+      /* extract creation time from dir name */
+      comma_split = g_strsplit (dir->d_name, "_", 2);
+      /* printf ("comma_split[1] = %s\n", comma_split[1]); */
+      freq_split = g_strsplit (comma_split[1], "-", 2);
+      /* printf ("freq_split[1] = %s\n", freq_split[1]);  */
+
+      /* parse time string */
+      if (strptime (freq_split[1], "%Y-%m-%d-%Hh%M", &tms) != NULL)
+        {
+          zds = ts_new_zfs_dataset (main_zds->search_dataset);
+          zds->name = g_strdup (dir->d_name);
+          zds->type = 0;
+          zds->mountpoint = g_strdup_printf ("%s%s/", fs_rsync_dir, dir->d_name);
+          zds->mtime = mktime (&tms);
+          zds->mtime_str =  caja_date_as_string (zds->mtime, FALSE);
+          zds->used_space_str = g_strdup (_("Separate Backup"));
+          main_zds->snapshots = g_list_append (main_zds->snapshots,zds);
+          /* printf ("in sync_backups_add adding %s %s\n", zds->name, zds->mountpoint); */
+        }
+      if (comma_split)
+        g_strfreev (comma_split);
+      if (freq_split)
+        g_strfreev (freq_split);
+    }
+    }
+
+  closedir (d);
+  g_free (rsync_dir);
+}
+
+static int
+zfs_callback (zfs_handle_t *zhp, void *data)
+{
+  char buf[ZFS_MAXPROPLEN];
+  char mounted[ZFS_MAXPROPLEN];
+  SearchDataSet *sds = (SearchDataSet*) data;
+
+  if (sds->match_found)
+    {
+      zfs_close (zhp);
+      return 0;
+    }
+
+  if (zfs_get_type (zhp) & sds->type & !g_cancellable_is_cancelled (sds->cancel))
+    {
+/*      struct timespec ts;
+      ts.tv_sec = 3;
+      ts.tv_nsec = 100000000;
+      nanosleep (&ts, NULL);*/
+
+      if (sds->prop >= ZFS_PROP_TYPE && sds->prop < ZFS_NUM_PROPS)
+    {
+      zfs_prop_get(zhp, sds->prop, buf, sizeof (buf), NULL, NULL,  0, TRUE);
+
+      zfs_prop_get(zhp, ZFS_PROP_MOUNTED, mounted, sizeof (mounted), NULL, NULL,  0, TRUE);
+
+      if ((strcmp (sds->mountpoint, buf) == 0) && (strcmp (mounted, "yes") == 0))
+        {
+          ZfsDataSet *zds = ts_new_zfs_dataset (sds);
+          zds->type = zfs_get_type (zhp);
+          zds->name = g_strdup (zfs_get_name(zhp));
+          zds->mountpoint = g_strdup (buf);
+          zfs_iter_snapshots (zhp, B_FALSE, snapshot_callback, zds);
+          sync_backups_add (zhp, zds);
+          sds->datasets = g_list_append (sds->datasets, zds);
+          sds->match_found = TRUE;
+        }
+      else if (strcmp ("legacy", buf) == 0)
+        { /* parse /etc/mnttab to get the mount point */
+          char *mountp = is_fs_mounted (zfs_get_name(zhp));
+          if (mountp)
+        {
+          if (strcmp (sds->mountpoint, mountp) == 0)
+            {
+              ZfsDataSet *zds = ts_new_zfs_dataset (sds);
+              zds->type = zfs_get_type (zhp);
+              zds->name = g_strdup (zfs_get_name(zhp));
+              zds->mountpoint = mountp;
+              zfs_iter_snapshots (zhp, B_FALSE, snapshot_callback, zds);
+              sync_backups_add (zhp, zds);
+              sds->datasets = g_list_append (sds->datasets, zds);
+              sds->match_found = TRUE;
+            }
+          else
+            g_free (mountp);
+        }
+        }
+    }
+      if (!sds->match_found)
+    zfs_iter_filesystems (zhp, zfs_callback, sds);
+    }
+  zfs_close (zhp);
+  return 0;
+}
+
+static SearchDataSet *
+ts_get_data_from_mountpoint (const char* searched_path, const char *mountpoint, GCancellable *cancel)
+{
+  static libzfs_handle_t *zfs_handle = NULL;
+  SearchDataSet *sds;
+
+  sds = ts_new_search_dataset (cancel);
+
+  sds->prop = ZFS_PROP_MOUNTPOINT;
+  sds->type = ZFS_TYPE_FILESYSTEM;
+  sds->searched_path = g_strdup (searched_path);
+  sds->mountpoint = g_strdup (mountpoint);
+
+  if (strcmp (searched_path, mountpoint) == 0)
+    sds->searched_path_match_mp = TRUE;
+
+  if (!zfs_handle)
+    {
+      if ((zfs_handle = libzfs_init()) == NULL) {
+    g_warning ("internal error: failed to initialize ZFS library\n");
+    ts_free_search_dataset (sds);
+    return NULL;
+      }
+    }
+  zfs_iter_root (zfs_handle, zfs_callback, sds);
+
+  return sds;
+}
+static gint
+snap_sort_by_age (gconstpointer a,
+          gconstpointer b)
+{
+  const ZfsDataSet *snap1 = a;
+  const ZfsDataSet *snap2 = b;
+
+  if (snap1->mtime == snap2->mtime)
+    return 0;
+  if (snap1->mtime < snap2->mtime)
+    return -1;
+  if (snap1->mtime > snap2->mtime)
+    return 1;
+
+}
+
+char*
+ts_get_zfs_filesystem (char *dir)
+{
+  char real_dir[PATH_MAX+1];
+  char filesystem[PATH_MAX+1];
+  gboolean  found_fs= FALSE;
+  struct stat64 dir_stat64;
+
+  if (!ts_realpath(dir, real_dir))
+    {
+      return NULL;
+    }
+    if (stat64 (real_dir, &dir_stat64) == 0)
+    { /* check is fs is zfs */
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+
+      /* get mount point */
+
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+        {
+          strcpy (filesystem, mtab.mnt_special);
+          found_fs = TRUE;
+          break;
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+    if (found_fs)
+      return g_strdup(filesystem);
+
+    return NULL;
+}
+
+static char * get_zfs_mountpoint (char *dir)
+{
+  char real_dir[PATH_MAX+1];
+  char mountpoint[PATH_MAX+1];
+  gboolean  found_mount_point = FALSE;
+  struct stat64 dir_stat64;
+
+  if (!ts_realpath(dir, real_dir))
+    {
+      return NULL;
+    }
+    if (stat64 (real_dir, &dir_stat64) == 0)
+    { /* check is fs is zfs */
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+
+      /* get mount point */
+
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+        {
+          strcpy (mountpoint, mtab.mnt_mountp);
+          found_mount_point = TRUE;
+          break;
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+    if (found_mount_point)
+      return g_strdup(mountpoint);
+
+    return NULL;
+}
+
+
+char *ts_get_snapshot_dir (char *dir)
+{
+  char *zfs_dir = get_zfs_mountpoint (dir);
+  if (zfs_dir)
+    {
+      char *snapshot_dir = g_strdup_printf ("%s/.zfs/snapshot", zfs_dir);
+      g_free (zfs_dir);
+      return snapshot_dir;
+    }
+  else
+    return NULL;
+}
+
+
+
+static void ts_get_snapshots_for_dir (GSimpleAsyncResult *res,
+                      GObject            *object,
+                      GCancellable       *cancellable)
+{
+  char *mountpoint = NULL;
+  char real_dir[PATH_MAX+1];
+  SearchDataSet *sds;
+  GList* snap_result = NULL;
+  GFile *file = G_FILE (object);
+  char *dir = g_file_get_path (file);
+
+  mountpoint = get_zfs_mountpoint (dir);
+
+
+  if (!mountpoint)
+    {
+      g_simple_async_result_set_op_res_gpointer (res, snap_result, (GDestroyNotify) NULL);
+      g_free (dir);
+      return;
+    }
+
+  ts_realpath(dir, real_dir);
+
+  sds = ts_get_data_from_mountpoint (real_dir, mountpoint, cancellable);
+
+  g_free (mountpoint);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      /* printf ("ts_get_snapshots_for_dir %s cancelled\n", dir); */
+      if (sds)
+    {
+      ts_free_search_dataset (sds);
+      sds = NULL;
+    }
+    }
+
+  if (sds)
+    {
+      GList *tmp;
+      for (tmp=sds->datasets;tmp;tmp=tmp->next)
+    {
+      ZfsDataSet *zds = (ZfsDataSet*) tmp->data;
+      if (zds->snapshots)
+        {
+          snap_result = g_list_concat (snap_result, zds->snapshots);
+          zds->snapshots = NULL;
+        }
+    }
+      ts_free_search_dataset (sds);
+    }
+
+  if (snap_result)
+    {
+      snap_result = g_list_sort (snap_result, (GCompareFunc)snap_sort_by_age);
+      /* print_snap_list (dir, snap_result);  */
+    }
+
+  g_free (dir);
+  g_simple_async_result_set_op_res_gpointer (res, snap_result, (GDestroyNotify) NULL);
+}
+
+
+GList *ts_get_snapshots_for_dir_async (GFile *file,
+                       GAsyncReadyCallback result_ready,
+                       GCancellable *cancel,
+                       gpointer  user_data)
+{
+   GSimpleAsyncResult *res;
+
+   res = g_simple_async_result_new (G_OBJECT (file), result_ready, user_data, (gpointer) ts_get_snapshots_for_dir);
+   g_simple_async_result_run_in_thread (res, ts_get_snapshots_for_dir, G_PRIORITY_DEFAULT, cancel);
+   return NULL;
+}
+
+
+void ts_free_snapshots (GList *snaps)
+{
+  if (snaps)
+    {
+      GList *tmp;
+      for (tmp=snaps;tmp;tmp=tmp->next)
+    ts_free_zfs_dataset ((ZfsDataSet*) tmp->data);
+      g_list_free (snaps);
+    }
+}
+
+gboolean ts_is_in_remote_backup (char *str)
+{
+    if (str != NULL)
+    {
+      if (g_strrstr (str, ZFS_BACKUP_DIR))
+    return TRUE;
+    }
+  return FALSE;
+}
+
+
+gboolean ts_is_in_snapshot (char * str)
+{
+  if (str != NULL)
+    {
+      if (g_strrstr (str, ZFS_SNAPSHOT_DIR))
+    return TRUE;
+      if (g_strrstr (str, ZFS_BACKUP_DIR))
+    return TRUE;
+    }
+  return FALSE;
+}
+
+char* ts_remove_snapshot_dir (char *str)
+{
+  if (ts_is_in_snapshot (str))
+    {
+      char *snap_root;
+      char *zfs, *iter, point;
+      int count = 0;
+
+      /*remove .zfs/snapshot/blah/ */
+      zfs = g_strrstr (str, ZFS_SNAPSHOT_DIR);
+      iter = zfs;
+
+      if (iter)
+    {
+      iter += sizeof (ZFS_SNAPSHOT_DIR);
+      while (*iter != '/' && *iter != '\0')
+        iter++;
+
+      if (*iter == '/')
+        iter++;
+
+      point = *zfs;
+      *zfs = '\0';
+      snap_root = g_strdup_printf ("%s%s", str, iter);
+
+      *zfs = point;
+      return snap_root;
+    }
+    }
+  return NULL;
+}
+
+
+static gboolean restore_col_enabled = FALSE;
+
+gboolean
+ts_is_restore_column_enabled ()
+{
+  return restore_col_enabled;
+}
+
+void ts_is_restore_column_enabled_init ();
+
+static void
+visible_columns_changed (gpointer callback_data)
+{
+  ts_is_restore_column_enabled_init ();
+}
+
+
+void ts_is_restore_column_enabled_init ()
+{
+  char **visible_columns;
+  static gboolean init = FALSE;
+  int i = 0;
+
+  if (!init)
+  {
+      g_signal_connect_swapped ( caja_list_view_preferences,
+                                 g_strconcat ("changed::", CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, NULL),
+                                 G_CALLBACK ( visible_columns_changed ),
+                                 NULL);
+      init = TRUE;
+  }
+
+  restore_col_enabled = FALSE;
+
+  visible_columns = g_settings_get_strv (caja_list_view_preferences,
+                                  CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+
+  while (visible_columns[i])
+    {
+      if (strcmp (visible_columns [i], "restore_info") == 0)
+    {
+      restore_col_enabled = TRUE;
+      break;
+    }
+      i++;
+    }
+  g_strfreev (visible_columns);
+}
+
+
+static GList * get_dir_entries (char *dir_path)
+{
+  const char *entry_name;
+  GDir *dir;
+  GList *dir_entries = NULL;
+  dir = g_dir_open (dir_path, 0, NULL);
+
+  while ((entry_name = g_dir_read_name (dir)) != NULL)
+    dir_entries = g_list_prepend (dir_entries, g_strdup (entry_name));
+
+  g_dir_close (dir);
+
+  return dir_entries;
+}
+
+static void free_dir_entries (GList *entries)
+{
+  g_list_foreach (entries, (GFunc)g_free, NULL);
+  g_list_free (entries);
+}
+
+static gboolean are_entries_identical (GList *old, GList *new)
+{
+  if (g_list_length (old) != g_list_length (new))
+    return FALSE;
+
+  for (old; old; old = old->next)
+    {
+      gboolean found = FALSE;
+      for (new; new; new = new->next)
+    {
+      if (strcmp (old->data, new->data) == 0)
+        {
+          found = TRUE;
+          break;
+        }
+    }
+      if (!found)
+    return FALSE;
+    }
+  return TRUE;
+}
+
+void monitor_zfs_snap_directory_cancel (ZfsSnapDirMonitor *monitor_data)
+{
+  if (monitor_data)
+    {
+      /* printf ("in monitor_zfs_snap_directory_cancel %s\n", monitor_data->path); */
+      g_source_remove (monitor_data->timeout_id);
+      free_dir_entries (monitor_data->entries);
+      g_free (monitor_data->path);
+      g_free (monitor_data);
+    }
+}
+
+static gboolean
+monitor_snap_dir (ZfsSnapDirMonitor *monitor_data)
+{
+  GList *new_entries;
+
+  if (!g_file_test (monitor_data->path, G_FILE_TEST_IS_DIR))
+    {
+      monitor_zfs_snap_directory_cancel (monitor_data);
+      return TRUE;
+    }
+
+  new_entries = get_dir_entries (monitor_data->path);
+
+  if (are_entries_identical (monitor_data->entries, new_entries))
+    {
+      free_dir_entries (new_entries);
+    }
+  else
+    {
+      free_dir_entries (monitor_data->entries);
+      monitor_data->entries = new_entries;
+      monitor_data->change_callback (monitor_data, monitor_data->user_data);
+    }
+
+  if (monitor_data->backup_path)
+    {
+      if (!g_file_test (monitor_data->backup_path, G_FILE_TEST_IS_DIR))
+    {
+      monitor_zfs_snap_directory_cancel (monitor_data);
+      return TRUE;
+    }
+
+      new_entries = get_dir_entries (monitor_data->backup_path);
+
+      if (are_entries_identical (monitor_data->backup_entries, new_entries))
+    {
+      free_dir_entries (new_entries);
+    }
+      else
+    {
+      free_dir_entries (monitor_data->backup_entries);
+      monitor_data->backup_entries = new_entries;
+      monitor_data->change_callback (monitor_data, monitor_data->user_data);
+    }
+    }
+  return TRUE;
+}
+
+
+ZfsSnapDirMonitor *monitor_zfs_snap_directory (char *path,
+                           char *backup_path,
+                           ZfsDirChangeCallback change_callback,
+                           gpointer data)
+{
+  ZfsSnapDirMonitor *monitor_data = g_new0 (ZfsSnapDirMonitor, 1);
+
+  /* printf ("start monitoring %s\n", path); */
+
+  monitor_data->path = g_strdup (path);
+  monitor_data->entries = get_dir_entries (path);
+  if (backup_path)
+    {
+      monitor_data->backup_path = g_strdup (backup_path);
+      monitor_data->backup_entries = get_dir_entries (backup_path);
+    }
+  monitor_data->change_callback = change_callback;
+  monitor_data->user_data = data;
+
+  monitor_data->timeout_id = g_timeout_add_seconds (5, (GSourceFunc)monitor_snap_dir, monitor_data);
+  return monitor_data;
+}
+
+char *
+ts_get_not_zfs_snapshot_dir (GFile *file)
+{
+  char tmp_path[PATH_MAX + 1];
+  gboolean found = FALSE;
+  gboolean end_path = FALSE;
+  GFile *d = g_file_get_parent(file);
+  GFile *tmp;
+  char *full_path = g_file_get_path (file);
+  char *stripped_path = g_file_get_path (d);
+  struct stat64 dir_stat64;
+
+  if (!full_path)
+     return NULL;
+
+  if (stat64 (full_path, &dir_stat64) == 0)
+    { /* check is fs is zfs if so don't try to check for nfs mounted .zfs dir*/
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+        end_path = TRUE;
+    }
+
+  while (!found && !end_path)
+    {
+      g_snprintf (tmp_path, sizeof(tmp_path), "%s/.zfs/snapshot", stripped_path);
+      if (g_file_test (tmp_path, G_FILE_TEST_IS_DIR))
+        {
+          GList *entries = get_dir_entries (tmp_path);
+          if (entries != NULL)
+            {
+              char *after_snap_path = full_path + strlen (stripped_path);
+
+              for (entries; entries; entries = entries->next)
+                {
+                  char test_path[PATH_MAX +1];
+                  g_sprintf (test_path, "%s/%s/%s", tmp_path,
+                             entries->data,
+                             after_snap_path);
+                  if (g_file_test (test_path, G_FILE_TEST_EXISTS))
+                    {
+                      found = TRUE;
+                      break;
+                    }
+                }
+              free_dir_entries (entries);
+            }
+        }
+      tmp = d;
+      d = g_file_get_parent (tmp);
+      g_object_unref (tmp);
+      g_free (stripped_path);
+      stripped_path=NULL;
+      if (d == NULL)
+        {
+          end_path = TRUE;
+        }
+      else
+        {
+          stripped_path = g_file_get_path (d);
+        }
+    }
+
+  g_free (full_path);
+
+  if (stripped_path)
+    g_free (stripped_path);
+
+  if (found)
+    return g_strdup (tmp_path);
+  else
+    return NULL;
+
+}
+
--- caja-1.28.0/libcaja-private/caja-zfs.h.orig    2024-02-26 08:42:41.079103041 +0100
+++ caja-1.28.0/libcaja-private/caja-zfs.h    2024-02-26 08:42:41.079046173 +0100
@@ -0,0 +1,75 @@
+#ifndef CAJA_ZFS_H
+#define CAJA_ZFS_H
+
+#include <glib.h>
+#include <libzfs.h>
+#include <gio/gio.h>
+
+typedef struct
+{
+  zfs_type_t    type;
+  zfs_prop_t    prop;
+  char           *searched_path;
+  char         *mountpoint;
+  GList        *datasets;
+  GCancellable *cancel;
+  gboolean    match_found;
+  gboolean    searched_path_match_mp;
+
+} SearchDataSet;
+
+typedef struct
+{
+  char          *name;
+  char          *mountpoint;
+  char          *mtime_str;
+  time_t       mtime;
+  float           used_space;
+  char          *used_space_str;
+  zfs_type_t       type;
+  GList          *snapshots;
+  SearchDataSet      *search_dataset;
+} ZfsDataSet;
+
+
+GList *ts_get_snapshots_for_dir_async    (GFile *file,
+                     GAsyncReadyCallback result_ready,
+                     GCancellable *cancel,
+                     gpointer  user_data);
+void ts_free_snapshots            (GList *snaps);
+void ts_free_zfs_dataset        (ZfsDataSet* zds);
+
+gboolean ts_is_in_snapshot        (char * str);
+gboolean ts_is_in_remote_backup        (char *str);
+char* ts_remove_snapshot_dir        (char *str);
+char *ts_get_snapshot_dir        (char *dir);
+char *ts_get_zfs_filesystem        (char *dir);
+char * ts_get_not_zfs_snapshot_dir      (GFile *file);
+gboolean ts_is_restore_column_enabled    ();
+void ts_is_restore_column_enabled_init    ();
+void print_snap_list            (char *dir, GList *snap_list);
+char* ts_realpath            (char * dir, char *resolved_name);
+
+char *
+caja_date_as_string            (time_t time_raw, gboolean use_smallest);
+
+typedef void (*ZfsDirChangeCallback)    (gpointer monitor_data,
+                     gpointer user_data);
+
+typedef struct
+{
+  char    *        path;
+  GList            *entries;
+  char    *        backup_path;
+  GList            *backup_entries;
+  guint            timeout_id;
+  ZfsDirChangeCallback    change_callback;
+  gpointer        user_data;
+} ZfsSnapDirMonitor;
+
+void monitor_zfs_snap_directory_cancel (ZfsSnapDirMonitor *monitor_data);
+ZfsSnapDirMonitor *monitor_zfs_snap_directory (char            *path,
+                           char            *backup_path,
+                           ZfsDirChangeCallback change_callback,
+                           gpointer            data);
+#endif /* CAJA_ZFS_H */
--- caja-1.28.0/libcaja-private/Makefile.am.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/Makefile.am    2024-02-26 08:42:41.079261409 +0100
@@ -37,7 +37,10 @@
     $(top_builddir)/eel/libeel-2.la \
     $(top_builddir)/libcaja-extension/libcaja-extension.la \
     $(CORE_LIBS) \
-    -lnotify
+    $(ZFS_LIBS) \
+    $(SCF_LIBS)     \
+    $(NVPAIR_LIBS) \
+    -lnotify \
     $(NULL)
 libcaja_private_la_SOURCES = \
@@ -184,6 +187,8 @@
     caja-window-slot-info.h \
     caja-undostack-manager.c \
     caja-undostack-manager.h \
+    caja-zfs.c \
+    caja-zfs.h \
     $(NULL)
 nodist_libcaja_private_la_SOURCES =\
--- caja-1.28.0/libcaja-private/org.mate.caja.gschema.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/org.mate.caja.gschema.xml    2024-02-26 08:42:41.079490993 +0100
@@ -82,6 +82,11 @@
       <summary>Switch tabs with [ctrl] + [tab]</summary>
       <description>If true, it enables the ability to switch tabs using [ctrl + tab] and [ctrl + shift + tab].</description>
     </key>
+    <key name="enable-time-slider" type="b">
+      <default>true</default>
+      <summary>Enables the visualization of the ZFS snaphots timeline.</summary>
+      <description>If set to true, the visualization of the ZFS snapshots timeline is enabled.</description>
+    </key>
     <key name="exit-with-last-window" type="b">
       <default>false</default>
       <summary>Caja will exit when last window destroyed.</summary>
components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch
New file
@@ -0,0 +1,10 @@
--- caja-1.28.0/src/caja-actions.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-actions.h    2024-02-26 08:42:41.079689398 +0100
@@ -28,6 +28,7 @@
 #define CAJA_ACTION_STOP "Stop"
 #define CAJA_ACTION_RELOAD "Reload"
+#define CAJA_ACTION_RESTORE "Restore"
 #define CAJA_ACTION_BACK "Back"
 #define CAJA_ACTION_UP "Up"
 #define CAJA_ACTION_UP_ACCEL "UpAccel"
components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch
New file
@@ -0,0 +1,20 @@
--- caja-1.28.0/src/caja-file-management-properties.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-file-management-properties.c    2024-02-26 08:42:41.080037863 +0100
@@ -71,6 +71,7 @@
 #define CAJA_FILE_MANAGEMENT_PROPERTIES_TREE_VIEW_FOLDERS_WIDGET "treeview_folders_checkbutton"
 #define CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTOMOUNT_OPEN "media_automount_open_checkbutton"
 #define CAJA_FILE_MANAGEMENT_PROPERTIES_MEDIA_AUTORUN_NEVER "media_autorun_never_checkbutton"
+#define CAJA_FILE_MANAGEMENT_PROPERTIES_ENABLE_TIME_SLIDER "time_slider_enabled_checkbutton"
 #define CAJA_FILE_MANAGEMENT_PROPERTIES_USE_IEC_UNITS_WIDGET "use_iec_units"
 #define CAJA_FILE_MANAGEMENT_PROPERTIES_SHOW_ICONS_IN_LIST_VIEW "show_icons_in_list_view"
@@ -1139,6 +1140,9 @@
     bind_builder_bool (builder, caja_preferences,
                        CAJA_FILE_MANAGEMENT_PROPERTIES_SHOW_BACKUP_WIDGET,
                        CAJA_PREFERENCES_SHOW_BACKUP_FILES);
+    bind_builder_bool (builder, caja_preferences,
+                       CAJA_FILE_MANAGEMENT_PROPERTIES_ENABLE_TIME_SLIDER,
+                       CAJA_PREFERENCES_ENABLE_TIME_SLIDER);
     bind_builder_bool (builder, caja_tree_sidebar_preferences,
                        CAJA_FILE_MANAGEMENT_PROPERTIES_TREE_VIEW_FOLDERS_WIDGET,
                        CAJA_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES);
components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch
New file
@@ -0,0 +1,10 @@
--- caja-1.28.0/src/caja-shell-ui.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-shell-ui.xml    2024-02-26 08:42:41.081168686 +0100
@@ -45,6 +45,7 @@
     <menu action="View">
         <menuitem name="Stop" action="Stop"/>
         <menuitem name="Reload" action="Reload"/>
+        <menuitem name="Restore" action="Restore"/>
         <separator/>
         <placeholder name="Show Hide Placeholder"/>
         <separator/>
components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch
New file
@@ -0,0 +1,184 @@
--- caja-1.28.0/src/caja-window-manage-views.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-manage-views.c    2024-02-26 08:42:41.081708726 +0100
@@ -30,6 +30,7 @@
 #include <gtk/gtk.h>
 #include <gdk/gdkx.h>
 #include <glib/gi18n.h>
+#include <stdlib.h>
 #include <eel/eel-accessibility.h>
 #include <eel/eel-debug.h>
@@ -71,6 +72,7 @@
 #include "caja-trash-bar.h"
 #include "caja-x-content-bar.h"
 #include "caja-navigation-window-pane.h"
+#include "caja-zfs-bar.h"
 /* FIXME bugzilla.gnome.org 41243:
  * We should use inheritance instead of these special cases
@@ -942,12 +944,48 @@
     end_location_change (slot);
+    GFile *old_file = NULL;
+    old_file = caja_window_slot_get_location (slot);
+
+    if (old_file)
+    {
+        directory = caja_directory_get (old_file);
+        if (directory)
+        {
+            caja_directory_cancel_restore_info (directory);
+            g_object_unref (directory);
+            directory = NULL;
+        }
+        g_object_unref (old_file);
+        old_file = NULL;
+    }
+
     caja_window_slot_set_allow_stop (slot, TRUE);
     caja_window_slot_set_status (slot, " ");
     g_assert (slot->pending_location == NULL);
     g_assert (slot->pending_selection == NULL);
+    directory = caja_directory_get (location);
+
+    /* if snap and (ts is enabled and displayed )
+     *  check if the directory exist
+     *    if it doesn't move to the next available one */
+    if (caja_directory_is_in_snapshot (directory) && caja_is_time_slider_enabled ())
+    {
+        if (gtk_widget_get_visible (GTK_WIDGET (CAJA_NAVIGATION_WINDOW (window)->zfs_bar)))
+        {
+            char *path = g_file_get_path (location);
+            if (!g_file_test (path, G_FILE_TEST_IS_DIR))
+            {
+                caja_zfs_bar_remove_and_skip_snap (CAJA_ZFS_BAR (CAJA_NAVIGATION_WINDOW (window)->zfs_bar), path);
+                g_free (path);
+                return;
+            }
+            g_free (path);
+        }
+    }
+
     slot->pending_location = g_object_ref (location);
     slot->location_change_type = type;
     slot->location_change_distance = distance;
@@ -959,8 +997,6 @@
     slot->open_callback = callback;
     slot->open_callback_user_data = user_data;
-    directory = caja_directory_get (location);
-
     /* The code to force a reload is here because if we do it
      * after determining an initial view (in the components), then
      * we end up fetching things twice.
@@ -1255,8 +1291,20 @@
         if (slot->location_change_type != CAJA_LOCATION_CHANGE_FALLBACK)
         {
             /* Look in metadata for view */
-            view_id = caja_file_get_metadata
-                      (file, CAJA_METADATA_KEY_DEFAULT_VIEW, NULL);
+            if (caja_file_is_in_snapshot (file))
+            {
+                CajaDirectory* root_dir = caja_zfs_bar_get_dir (CAJA_ZFS_BAR (CAJA_NAVIGATION_WINDOW (window)->zfs_bar));
+                if (root_dir)
+                {
+                    view_id = caja_file_get_metadata (caja_directory_get_corresponding_file (root_dir),
+                                                          CAJA_METADATA_KEY_DEFAULT_VIEW, NULL);
+                }
+            }
+            if (view_id == NULL)
+            {
+                view_id = caja_file_get_metadata (file, CAJA_METADATA_KEY_DEFAULT_VIEW, NULL);
+            }
+
             if (view_id != NULL &&
                     !caja_view_factory_view_supports_uri (view_id,
                             location,
@@ -1804,6 +1852,7 @@
 static void
 update_for_new_location (CajaWindowSlot *slot)
 {
+    CajaDirectory *directory;
     CajaWindow *window;
     GFile *new_location;
     CajaFile *file;
@@ -1857,6 +1906,30 @@
         caja_window_load_extension_menus (window);
     }
+    /* time slider pref can be just enabled so we need
+     * to rescan for snapshots */
+    directory = caja_directory_get (slot->location);
+
+    if (CAJA_IS_NAVIGATION_WINDOW (window))
+    {
+        if (slot->find_zfs_snapshots_cancellable != NULL)
+        {
+            g_cancellable_cancel (slot->find_zfs_snapshots_cancellable);
+            slot->find_zfs_snapshots_cancellable = NULL;
+        }
+        slot->find_zfs_snapshots_cancellable = g_cancellable_new ();
+        caja_zfs_bar_display (CAJA_ZFS_BAR (CAJA_NAVIGATION_WINDOW (window)->zfs_bar),
+                                  window,
+                                  directory,
+                                  slot->find_zfs_snapshots_cancellable);
+
+    }
+    else
+    {
+        caja_window_allow_restore (window, FALSE);
+    }
+    caja_directory_unref (directory);
+
     if (location_really_changed)
     {
         CajaDirectory *directory;
@@ -2227,12 +2300,22 @@
 caja_window_slot_stop_loading (CajaWindowSlot *slot)
 {
     CajaWindow *window;
+    CajaDirectory *directory;
     window = CAJA_WINDOW (slot->pane->window);
     g_assert (CAJA_IS_WINDOW (window));
     caja_view_stop_loading (slot->content_view);
+    if (slot->find_zfs_snapshots_cancellable)
+    {
+        g_cancellable_cancel (slot->find_zfs_snapshots_cancellable);
+    }
+
+    directory = caja_directory_get (slot->location);
+    caja_directory_cancel_restore_info (directory);
+    caja_directory_unref (directory);
+
     if (slot->new_content_view != NULL)
     {
         window->details->temporarily_ignore_view_signals = TRUE;
@@ -2293,11 +2376,22 @@
 caja_window_manage_views_close_slot (CajaWindowPane *pane,
                                      CajaWindowSlot *slot)
 {
+    CajaDirectory *directory;
+
     if (slot->content_view != NULL)
     {
         caja_window_slot_disconnect_content_view (slot, slot->content_view);
     }
+    if (slot->find_zfs_snapshots_cancellable)
+    {
+        g_cancellable_cancel (slot->find_zfs_snapshots_cancellable);
+    }
+
+    directory = caja_directory_get (slot->location);
+    caja_directory_cancel_restore_info (directory);
+    caja_directory_unref (directory);
+
     free_location_change (slot);
     cancel_viewed_file_changed_callback (slot);
 }
components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch
New file
@@ -0,0 +1,58 @@
--- caja-1.28.0/src/caja-window-menus.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-menus.c    2024-02-26 08:42:41.000000000 +0100
@@ -57,6 +57,8 @@
 #include "caja-window-private.h"
 #include "caja-desktop-window.h"
 #include "caja-search-bar.h"
+#include "caja-navigation-window.h"
+#include "caja-zfs-bar.h"
 #define MENU_PATH_EXTENSION_ACTIONS                     "/MenuBar/File/Extension Actions"
 #define POPUP_PATH_EXTENSION_ACTIONS                     "/background/Before Zoom Items/Extension Actions"
@@ -370,6 +372,33 @@
 }
 static void
+action_restore_callback (GtkToggleAction *action,
+                         gpointer user_data)
+{
+    CajaWindowSlot *slot;
+    GFile *directory;
+    CajaDirectory *n_dir;
+    GtkWidget *bar = CAJA_NAVIGATION_WINDOW (user_data)->zfs_bar;
+
+    slot = caja_window_get_active_slot (CAJA_WINDOW (user_data));
+    directory = caja_window_slot_get_location (slot);
+    n_dir = caja_directory_get (directory);
+
+    if (gtk_toggle_action_get_active (action))
+    {
+        caja_zfs_bar_setup (CAJA_ZFS_BAR (bar), n_dir, slot, action);
+        gtk_widget_show (bar);
+    }
+    else
+    {
+        caja_zfs_bar_hide (CAJA_ZFS_BAR (bar));
+        caja_window_reload (CAJA_WINDOW (user_data), FALSE);
+    }
+    g_object_unref (n_dir);
+    g_object_unref (directory);
+}
+
+static void
 action_zoom_in_callback (GtkAction *action,
                          gpointer user_data)
 {
@@ -962,6 +991,12 @@
 static const GtkToggleActionEntry main_toggle_entries[] =
 {
+    /* name, stock id */         { "Restore", NULL,
+    /* label, accelerator */       N_("R_estore"), "<control>E",
+    /* tooltip */                  N_("Browse the current location snapshot history"),
+        G_CALLBACK (action_restore_callback),
+        FALSE
+    },
     /* name, icon name */        { "Show Hidden Files", NULL,
         /* label, accelerator */       N_("Show _Hidden Files"), "<control>H",
         /* tooltip */                  N_("Toggle the display of hidden files in the current window"),
components/desktop/mate/caja/patches/12-timeslider-caja-window.patch
New file
@@ -0,0 +1,49 @@
--- caja-1.28.0/src/caja-window.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window.c    2024-02-26 08:42:41.082802543 +0100
@@ -424,6 +424,18 @@
                      set_allow_up, (window, allow));
 }
+void
+caja_window_allow_restore (CajaWindow *window, gboolean enable)
+{
+    GtkAction *action;
+
+    g_assert (CAJA_IS_WINDOW (window));
+
+    action = gtk_action_group_get_action (window->details->main_action_group,
+                                          CAJA_ACTION_RESTORE);
+    gtk_action_set_sensitive (action, enable);
+}
+
 static void
 update_cursor (CajaWindow *window)
 {
@@ -431,7 +443,7 @@
     slot = window->details->active_pane->active_slot;
-    if (slot->allow_stop)
+    if (slot && slot->allow_stop)
     {
         GdkDisplay *display;
         GdkCursor * cursor;
@@ -468,7 +480,18 @@
         if (slot == window->details->active_pane->active_slot)
         {
             G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+            if (!slot->allow_stop && slot->find_zfs_snapshots_cancellable != NULL)
+            {
+                /* if slot want to stop and snap find is finished/cancel then disable */
+                if (g_cancellable_is_cancelled (slot->find_zfs_snapshots_cancellable))
+                {
+                    gtk_action_set_sensitive (action, slot->allow_stop);
+                }
+            }
+            else
+            {
             gtk_action_set_sensitive (action, slot->allow_stop);
+        }
             G_GNUC_END_IGNORE_DEPRECATIONS;
         }
components/desktop/mate/caja/patches/13-timeslider-file-manager.patch
New file
@@ -0,0 +1,313 @@
--- caja-1.28.0/src/file-manager/caja-directory-view-ui.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/caja-directory-view-ui.xml    2024-02-26 08:42:41.083540685 +0100
@@ -71,6 +71,9 @@
             <menuitem name="Duplicate" action="Duplicate"/>
             <menuitem name="Create Link" action="Create Link"/>
             <menuitem name="Rename" action="Rename"/>
+            <menuitem name="Restore to" action="Restore to"/>
+            <menuitem name="Snapshot now" action="Snap Now"/>
+            <menuitem name="Scanning...." action="View Snap"/>
             <menu action="CopyToMenu">
                 <menuitem name="Copy to next pane" action="Copy to next pane"/>
                 <menuitem name="Copy to Home" action="Copy to Home"/>
@@ -168,6 +171,9 @@
     <placeholder name="File Actions">
         <menuitem name="Create Link" action="Create Link"/>
         <menuitem name="Rename" action="Rename"/>
+        <menuitem name="Restore to" action="Restore to"/>
+        <menuitem name="Snapshot now" action="Snap Now"/>
+        <menuitem name="Scanning...." action="View Snap"/>
         <menu action="CopyToMenu">
             <menuitem name="Copy to next pane" action="Copy to next pane"/>
             <menuitem name="Copy to Home" action="Copy to Home"/>
@@ -218,6 +224,7 @@
     </placeholder>
     <separator name="Location After Clipboard Separator"/>
     <placeholder name="Dangerous File Actions">
+        <menuitem name="Restore" action="Restore"/>
         <menuitem name="Trash" action="LocationTrash"/>
         <menuitem name="Delete" action="LocationDelete"/>
         <menuitem name="Restore From Trash" action="LocationRestoreFromTrash"/>
--- caja-1.28.0/src/file-manager/fm-actions.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/fm-actions.h    2024-02-26 08:42:41.083717777 +0100
@@ -58,6 +58,9 @@
 #define FM_ACTION_NEW_LAUNCHER "New Launcher"
 #define FM_ACTION_NEW_LAUNCHER_DESKTOP "New Launcher Desktop"
 #define FM_ACTION_RENAME "Rename"
+#define FM_ACTION_RESTORE_TO "Restore to"
+#define FM_ACTION_HAS_SNAPSHOT "View Snap"
+#define FM_ACTION_SNAP_NOW "Snap Now"
 #define FM_ACTION_DUPLICATE "Duplicate"
 #define FM_ACTION_CREATE_LINK "Create Link"
 #define FM_ACTION_SELECT_ALL "Select All"
--- caja-1.28.0/src/file-manager/fm-directory-view.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/fm-directory-view.c    2024-02-26 08:42:41.084993364 +0100
@@ -1002,6 +1002,77 @@
     return FALSE;
 }
+ static void
+action_snap_now (GtkAction *action,
+         gpointer callback_data)
+{
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+  GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+  char *path = g_file_get_path (file);
+  char *fs = ts_get_zfs_filesystem (path);
+  char *cmd = g_strdup_printf ("/usr/lib/time-slider-snapshot '%s' '%s'", path, fs);
+  mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (GTK_WIDGET (callback_data)),
+                    cmd, NULL);
+
+  g_free (cmd);
+  g_free (fs);
+  g_free (path);
+  g_object_unref (file);
+}
+
+static void
+action_restore_to_desktop_callback (GtkAction *action,
+                    gpointer callback_data)
+{
+  GList *locations = NULL;
+  GList *node;
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  char *desktop_directory = caja_get_desktop_directory_uri();
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+
+  if (selection == NULL)
+    return;
+
+  if (desktop_directory == NULL)
+    return;
+
+
+  for (node = selection; node != NULL; node = node->next)
+    {
+      locations = g_list_prepend (locations,
+                  caja_file_get_uri ((CajaFile *) node->data));
+    }
+
+  fm_directory_view_move_copy_items (locations, NULL, desktop_directory,
+                     GDK_ACTION_COPY, 0, 0, view);
+
+  caja_file_list_free (selection);
+}
+
+static void
+action_show_snapshot_versions_callback (GtkAction *action,
+                                        gpointer callback_data)
+{
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+  GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+  char *dir = caja_file_get_snapshot_dir (CAJA_FILE (selection->data));
+  char *file_path = g_file_get_path (file);
+  char real_file_path [PATH_MAX + 1];
+  if (ts_realpath (file_path, real_file_path))
+    {
+      char *cmd = g_strdup_printf ("/usr/lib/time-slider-version '%s' '%s'", dir,
+                                   real_file_path);
+      mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (GTK_WIDGET (callback_data)),
+                                        cmd, NULL);
+      g_free (cmd);
+    }
+
+  g_free (file_path);
+  g_object_unref (file);
+}
+
 static void
 action_trash_callback (GtkAction *action,
                gpointer callback_data)
@@ -1686,20 +1757,20 @@
         scripts_directory_uri = g_filename_to_uri(scripts_directory_path, NULL, NULL);
         scripts_directory_uri_length = strlen(scripts_directory_uri);
-        /* Support for GNOME Nautilus scripts
+        /* Support for GNOME Caja scripts
          */
-        char* nautilus_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "nautilus-scripts", NULL);
+        char* caja_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "caja-scripts", NULL);
-        if (g_file_test(nautilus_scripts_path, G_FILE_TEST_IS_DIR) == TRUE)
+        if (g_file_test(caja_scripts_path, G_FILE_TEST_IS_DIR) == TRUE)
         {
-            char* nautilus_syslink = g_build_filename(g_get_user_config_dir(), "caja", "scripts", "nautilus", NULL);
+            char* caja_syslink = g_build_filename(g_get_user_config_dir(), "caja", "scripts", "caja", NULL);
             /* If link already exists, or also any other kind of file/dir with same name, ignore it */
-            if (g_file_test(nautilus_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE &&
-                g_file_test(nautilus_syslink, G_FILE_TEST_EXISTS) == FALSE &&
-                g_file_test(nautilus_syslink, G_FILE_TEST_IS_DIR) == FALSE)
+            if (g_file_test(caja_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE &&
+                g_file_test(caja_syslink, G_FILE_TEST_EXISTS) == FALSE &&
+                g_file_test(caja_syslink, G_FILE_TEST_IS_DIR) == FALSE)
             {
                 /* Check if we need to create a link */
-                GDir* dir = g_dir_open(nautilus_scripts_path, 0, NULL);
+                GDir* dir = g_dir_open(caja_scripts_path, 0, NULL);
                 if (dir)
                 {
@@ -1713,20 +1784,20 @@
                     if (count > 0)
                     {
-                        /* Create link to nautilus folder */
-                        int res = symlink (nautilus_scripts_path, nautilus_syslink);
+                        /* Create link to caja folder */
+                        int res = symlink (caja_scripts_path, caja_syslink);
                         if (res != 0)
-                            g_warning ("Can't create symlink to nautilus scripts folder");
+                            g_warning ("Can't create symlink to caja scripts folder");
                     }
                     g_dir_close(dir);
                 }
             }
-            g_free(nautilus_syslink);
+            g_free(caja_syslink);
         }
-        g_free(nautilus_scripts_path);
+        g_free(caja_scripts_path);
     }
     g_free(scripts_directory_path);
@@ -7473,6 +7544,18 @@
   /* label, accelerator */       "RenameSelectAll", "<shift>F2",
   /* tooltip */                  NULL,
                                  G_CALLBACK (action_rename_select_all_callback) },
+  /* name, icon name */         { "Restore to", NULL,
+  /* label, accelerator */       N_("Restore to Desktop"), NULL,
+  /* tooltip */                  N_("Move each selected item to the Desktop"),
+                                 G_CALLBACK (action_restore_to_desktop_callback) },
+  /* name, stock id */         { "View Snap", NULL,
+  /* label, accelerator */       N_("View versions"), NULL,
+  /* tooltip */                  N_("View the versions of this file available in ZFS snapshots"),
+                                 G_CALLBACK (action_show_snapshot_versions_callback) },
+  /* name, stock id */         { "Snap Now", NULL,
+  /* label, accelerator */       N_("Snapshot now"), NULL,
+  /* tooltip */                  N_("Take a ZFS snapshot of this directory now"),
+                                 G_CALLBACK (action_snap_now) },
   /* name, icon name */        { "Trash", NULL,
   /* label, accelerator */       N_("Mo_ve to Trash"), NULL,
   /* tooltip */                  N_("Move each selected item to the Trash"),
@@ -8834,6 +8917,40 @@
     return FALSE;
 }
+typedef struct {
+  CajaFile              *file;
+  GCancellable              *cancel;
+  GtkAction                 *action;
+} HasSnapshotData;
+
+static void
+has_snapshot_ready_callback (gpointer user_data)
+{
+  GValue name = {0,};
+  HasSnapshotData *data = (HasSnapshotData*) user_data;
+  HasSnapshotResult result = caja_file_has_snapshot_version (data->file);
+
+  switch (result)
+    {
+    case UNKNOWN_STATE:
+    case NO:
+      gtk_action_set_sensitive (data->action, FALSE);
+      g_value_init (&name, G_TYPE_STRING);
+      /* SUN_BRANDING */
+      g_value_set_static_string (&name, _("No versions"));
+      g_object_set_property (G_OBJECT (data->action), "label", &name);
+      break;
+    case YES:
+      gtk_action_set_sensitive (data->action, TRUE);
+      g_value_init (&name, G_TYPE_STRING);
+      /* SUN_BRANDING */
+      g_value_set_static_string (&name, _("Explore versions"));
+      g_object_set_property (G_OBJECT (data->action), "label", &name);
+      break;
+    }
+  g_free (data);
+ }
+
 static void
 real_update_menus (FMDirectoryView *view)
 {
@@ -9211,6 +9328,75 @@
     gtk_action_set_sensitive (action, can_copy_files);
     G_GNUC_END_IGNORE_DEPRECATIONS;
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_RESTORE_TO);
+        gtk_action_set_visible (action, can_copy_files &&
+                                  caja_directory_is_in_snapshot (view->details->model));
+
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_SNAP_NOW);
+
+        if (selection_count == 1 && caja_file_is_directory (CAJA_FILE (selection->data)))
+            {
+              GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+              char *path = g_file_get_path (file);
+              char *fs = ts_get_zfs_filesystem (path);
+              if (fs)
+                {
+                  gtk_action_set_visible (action, TRUE);
+                  g_free (fs);
+                }
+              g_free (path);
+              g_object_unref (file);
+            }
+        else
+          gtk_action_set_visible (action, FALSE);
+
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_HAS_SNAPSHOT);
+   if (selection_count == 1)
+     {
+       GValue name = { 0, };
+       int result = caja_file_has_snapshot_version (CAJA_FILE (selection->data));
+
+       switch (result)
+         {
+         case NO:
+           gtk_action_set_visible (action, FALSE);
+           break;
+         case YES:
+           gtk_action_set_visible (action, TRUE);
+           gtk_action_set_sensitive (action, TRUE);
+           g_value_init (&name,G_TYPE_STRING);
+           /* SUN_BRANDING */
+           g_value_set_static_string (&name, _("Explore versions"));
+           g_object_set_property (G_OBJECT (action), "label", &name);
+           break;
+         case UNKNOWN_STATE:
+           gtk_action_set_visible (action, TRUE);
+           gtk_action_set_sensitive (action, FALSE);
+           g_value_init (&name,G_TYPE_STRING);
+           /* SUN_BRANDING */
+           g_value_set_static_string (&name, _("Scanning for versions"));
+           g_object_set_property (G_OBJECT (action), "label", &name);
+           break;
+         }
+       if (result == UNKNOWN_STATE)
+         {
+           HasSnapshotData *data = g_new0 (HasSnapshotData, 1);
+           data->action = action;
+           data->file = CAJA_FILE (selection->data);
+           data->cancel = g_cancellable_new ();
+           caja_file_get_snapshot_version (CAJA_FILE (selection->data),
+                                               has_snapshot_ready_callback,
+                                               data->cancel,
+                                               data);
+         }
+     }
+   else
+     gtk_action_set_visible (action, FALSE);
+
+
     real_update_paste_menu (view, selection, selection_count);
     disable_command_line = g_settings_get_boolean (mate_lockdown_preferences, CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE);
components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch
New file
@@ -0,0 +1,22 @@
--- caja-1.28.0/src/Makefile.am.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/Makefile.am    2024-02-26 08:42:41.085295973 +0100
@@ -35,6 +35,8 @@
     $(EXIF_LIBS) \
     $(EXEMPI_LIBS) \
     $(POPT_LIBS) \
+        $(SCF_LIBS) \
+        $(NVPAIR_LIBS) \
     -lnotify
     $(NULL)
@@ -159,6 +161,10 @@
     caja-zoom-action.h \
     caja-zoom-control.c \
     caja-zoom-control.h \
+    caja-zfs-bar.c \
+    caja-zfs-bar.h \
+    timescale.h \
+    timescale.c \
     $(NULL)
 nodist_caja_SOURCES = \
components/desktop/mate/caja/patches/15-timeslider-timescale.patch
New file
@@ -0,0 +1,1350 @@
--- caja-1.28.0/src/timescale.c.orig    2024-02-26 08:42:41.085817612 +0100
+++ caja-1.28.0/src/timescale.c    2024-02-26 08:42:41.085766910 +0100
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+#include "config.h"
+#include "timescale.h"
+#include <math.h>
+#include <stdlib.h>
+#include <libcaja-private/caja-zfs.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#define BAR_W_MAX 20
+#define BAR_SPACE 2
+
+
+typedef struct
+{
+    char          *name;
+    char          *mountpoint;
+    char          *mtime_str;
+    char          *mtime_short_str;
+    time_t       mtime;
+    float           used_space;
+    char          *used_space_str;
+    SnapType       type;
+    char          *type_str;
+} Snap;
+
+
+struct TimeScalePrivate
+{
+    GList* all_snaps;
+    GList* snaps;
+    int*    bar_x_end;
+    int   current_pos;
+    int   num_snaps;
+    char  *num_rev_string;
+    int    current_period;
+    GList *today;
+    GList *yesterday;
+    GList *this_week;
+    GList *last_week;
+    GList *this_month;
+    GList *last_month;
+    gboolean scrollbar_set;
+    gboolean key_pressed;
+    GtkWidget *darea;
+    GtkWidget *period;
+    GtkWidget *info;
+    GtkWidget *scrolled;
+    GtkWidget *label_tip;
+};
+
+enum {
+    VALUE_CHANGED,
+    LAST_SIGNAL
+};
+
+enum {
+    ALL,
+    TODAY,
+    YESTERDAY,
+    THIS_WEEK,
+    LAST_WEEK,
+    THIS_MONTH,
+    LAST_MONTH
+};
+
+enum {
+    COLUMN_INDEX,
+    COLUMN_STRING
+};
+
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (TimeScale, timescale, GTK_TYPE_HBOX)
+
+#define TIMESCALE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_TIMESCALE, TimeScalePrivate))
+
+static gboolean
+timescale_expose (GtkWidget *widget,
+            GdkEventExpose *event, TimeScale *ts);
+
+static gboolean
+query_tooltip (GtkWidget  *widget,
+        gint        x,
+        gint        y,
+        gboolean    keyboard_tip,
+        GtkTooltip *tooltip,
+        gpointer    data);
+static int
+key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale *ts);
+static void
+button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale *ts);
+
+static void
+print_snaps (GList *list)
+{
+    GList* tmp = list;
+    int i = 0;
+
+    while (tmp)
+    {
+        Snap *snap = (Snap*) tmp->data;
+        printf ("=-= %d =-=\nname: %s\nmountpoint: %s\nmtime: %s\nused_space: %s\n",i,
+                snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str);
+        i++;
+        tmp = tmp->next;
+    }
+}
+
+static char *
+get_num_snap_string (GList *snap_list)
+{
+    goffset total = 0;
+    int i = 0;
+    GList *tmp = snap_list;
+    char *num_rev;
+
+    char *size_str = NULL;
+
+    for (tmp; tmp; tmp = tmp->next)
+    {
+        Snap *snap = ((Snap*) tmp->data);
+        total += snap->used_space;
+        i++;
+    }
+
+    total *= 1024;
+
+    size_str = g_format_size (total);
+
+    /* SUN_BRANDING */
+    num_rev = g_strdup_printf (_("%d %s\n%s"),
+            i - 1 /* Account for "Now" snapshot */,
+            /* SUN_BRANDING */
+            ngettext ("snapshot", "snapshots", i),
+            size_str);
+    g_free (size_str);
+
+    return num_rev;
+}
+
+
+static char *
+get_date (GDate *date)
+{
+    return g_strdup_printf ("%d/%d/%d", g_date_get_day (date),
+            g_date_get_month (date),
+            g_date_get_year (date));
+}
+
+static GList *
+trim_list_by_date (GList *list, int type)
+{
+    GDate then;
+    GDate now;
+    GDate range_min;
+    GDate range_max;
+    GDateWeekday weekday;
+
+    time_t time_now;
+    int diff  = 0;
+    time_now = time (NULL);
+    g_date_set_time_t (&now, time_now);
+    g_date_set_time_t (&range_min, time_now);
+    GList *return_list = NULL;
+    int days_diff = 0;
+    gboolean range = FALSE;
+
+    switch (type)
+    {
+        case TODAY:
+            days_diff = 0;
+            break;
+        case YESTERDAY:
+            days_diff = 1;
+            break;
+        case THIS_WEEK:
+            weekday = g_date_get_weekday(&now);
+            days_diff = weekday - G_DATE_MONDAY;
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_subtract_days (&range_min, days_diff);
+            range = TRUE;
+            break;
+        case LAST_WEEK:
+            g_date_subtract_days (&range_min, 7);
+            weekday = g_date_get_weekday(&range_min);
+            days_diff = weekday - G_DATE_MONDAY;
+            g_date_subtract_days (&range_min, days_diff);
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_add_days (&range_max, 6);
+            range = TRUE;
+            break;
+        case THIS_MONTH:
+            g_date_set_dmy (&range_min, 1, g_date_get_month (&now),
+                    g_date_get_year (&now));
+            g_date_set_time_t (&range_max, time_now);
+            range = TRUE;
+            break;
+        case LAST_MONTH:
+            g_date_subtract_months (&range_min, 1);
+            g_date_set_dmy (&range_min, 1,
+                    g_date_get_month (&range_min),
+                    g_date_get_year (&range_min));
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_add_days (&range_max, g_date_get_days_in_month (g_date_get_month (&range_min), g_date_get_year (&range_min)) - 1);
+            range = TRUE;
+            break;
+    }
+
+    while (list)
+    {
+        Snap* snap = (Snap*) list->data;
+
+        if (snap->mtime != 0)
+        {
+            g_date_set_time_t (&then, snap->mtime);
+
+            if (!range)
+            {
+                if (g_date_get_julian (&now) - g_date_get_julian (&then) == days_diff)
+                    return_list = g_list_append (return_list, snap);
+            }
+            else
+            {
+                if (g_date_compare (&then, &range_min) >= 0 && g_date_compare (&then, &range_max) <= 0)
+                    return_list = g_list_append (return_list, snap);
+            }
+        }
+        list = list->next;
+    }
+    return return_list;
+}
+
+static void
+free_periods (TimeScale *ts)
+{
+    if (ts->priv->today)
+    {
+        g_list_free (ts->priv->today);
+        ts->priv->today = NULL;
+    }
+    if (ts->priv->yesterday)
+    {
+        g_list_free (ts->priv->yesterday);
+        ts->priv->yesterday = NULL;
+    }
+    if (ts->priv->this_week)
+    {
+        g_list_free (ts->priv->this_week);
+        ts->priv->this_week = NULL;
+    }
+    if (ts->priv->last_week)
+    {
+        g_list_free (ts->priv->last_week);
+        ts->priv->last_week = NULL;
+    }
+    if (ts->priv->this_month)
+    {
+        g_list_free (ts->priv->this_month);
+        ts->priv->this_month = NULL;
+    }
+    if (ts->priv->last_month)
+    {
+        g_list_free (ts->priv->last_month);
+        ts->priv->last_month = NULL;
+    }
+}
+
+static GtkListStore *
+create_periods (TimeScale *ts)
+{
+    GtkTreeIter iter;
+    GtkListStore* periods = gtk_list_store_new (3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_POINTER);
+
+    free_periods (ts);
+
+    gtk_list_store_append (periods, &iter);
+    gtk_list_store_set (periods, &iter, 0, ALL, 1, _("All"), 2, ts->priv->all_snaps, -1);
+
+    ts->priv->today = trim_list_by_date (ts->priv->all_snaps, TODAY);
+
+    if (ts->priv->today)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, TODAY, 1, _("Today"), 2, ts->priv->today, -1);
+    }
+
+    ts->priv->yesterday = trim_list_by_date (ts->priv->all_snaps, YESTERDAY);
+    if (ts->priv->yesterday)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, YESTERDAY, 1, _("Yesterday"), 2, ts->priv->yesterday, -1);
+    }
+
+    ts->priv->this_week = trim_list_by_date (ts->priv->all_snaps, THIS_WEEK);
+    if (ts->priv->this_week)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, THIS_WEEK, 1, _("This Week"), 2, ts->priv->this_week, -1);
+    }
+
+    ts->priv->last_week = trim_list_by_date (ts->priv->all_snaps, LAST_WEEK);
+    if (ts->priv->last_week)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, LAST_WEEK, 1, _("Last Week"), 2, ts->priv->last_week, -1);
+    }
+
+    ts->priv->this_month = trim_list_by_date (ts->priv->all_snaps, THIS_MONTH);
+    if (ts->priv->this_month)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, THIS_MONTH, 1, _("This Month"), 2, ts->priv->this_month, -1);
+    }
+
+    ts->priv->last_month = trim_list_by_date (ts->priv->all_snaps, LAST_MONTH);
+    if (ts->priv->last_month)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, LAST_MONTH, 1, _("Last Month"), 2, ts->priv->last_month, -1);
+    }
+
+    return periods;
+}
+
+static void
+period_changed (GtkComboBox *combo,
+        TimeScale *ts)
+{
+    gint type;
+    GtkTreePath *path;
+    GtkTreeModel *model;
+    GtkTreeIter  iter;
+    GList *period;
+
+    if (!gtk_combo_box_get_active_iter (combo, &iter))
+        return;
+
+    model = gtk_combo_box_get_model (combo);
+
+    gtk_tree_model_get (model, &iter, 0, &type, 2, &period, -1);
+
+    if (ts->priv->current_period == type)
+        return;
+
+    ts->priv->current_period = type;
+
+    ts->priv->snaps = period;
+
+    ts->priv->num_snaps = g_list_length (ts->priv->snaps);
+    if (ts->priv->bar_x_end)
+        g_free (ts->priv->bar_x_end);
+
+    ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps));
+
+    ts->priv->current_pos = -1;
+
+    if (ts->priv->num_rev_string)
+        g_free (ts->priv->num_rev_string);
+
+    ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps);
+    gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string);
+
+    gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX ) * ts->priv->num_snaps) + PADDING * 2, 60);
+
+    gtk_widget_queue_draw (ts->priv->darea);
+}
+
+static void
+timescale_init (TimeScale *ts)
+{
+    GtkWidget *vbox;
+    GtkCellRenderer *renderer;
+
+    ts->priv = TIMESCALE_GET_PRIVATE (ts);
+    ts->priv->snaps = NULL;
+    ts->priv->all_snaps = NULL;
+    ts->priv->num_snaps = 0;
+    ts->priv->bar_x_end = NULL;
+    ts->priv->current_pos = 0;
+    ts->priv->num_rev_string = NULL;
+    ts->priv->current_period = ALL;
+    ts->priv->today = NULL;
+    ts->priv->yesterday = NULL;
+    ts->priv->this_week = NULL;
+    ts->priv->last_week = NULL;
+    ts->priv->this_month = NULL;
+    ts->priv->last_month = NULL;
+    ts->priv->key_pressed = FALSE;
+
+    gtk_box_set_homogeneous (GTK_BOX (ts), FALSE);
+
+    /* setup drawing area */
+
+    ts->priv->darea = gtk_drawing_area_new ();
+
+    g_signal_connect(ts->priv->darea, "draw",
+            G_CALLBACK(timescale_expose), ts);
+
+    g_signal_connect (ts->priv->darea, "query-tooltip",
+            G_CALLBACK (query_tooltip), ts);
+
+    g_signal_connect(ts->priv->darea, "key-press-event",
+            G_CALLBACK(key_pressed), ts);
+    g_signal_connect(ts->priv->darea, "button_press_event",
+            G_CALLBACK(button_pressed), ts);
+    gtk_widget_set_can_focus(GTK_WIDGET (ts->priv->darea), TRUE);
+    gtk_widget_add_events (GTK_WIDGET (ts->priv->darea), GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK);
+    g_object_set (G_OBJECT (ts->priv->darea), "has-tooltip", TRUE, NULL);
+    gtk_widget_set_size_request (GTK_WIDGET (ts->priv->darea), -1, 60);
+
+    ts->priv->scrolled = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ts->priv->scrolled),
+            GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
+    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (ts->priv->scrolled),
+            ts->priv->darea);
+    gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (ts->priv->scrolled))), GTK_SHADOW_NONE);
+    gtk_widget_show (ts->priv->scrolled);
+
+    ts->priv->label_tip = gtk_label_new ("Hello");
+    gtk_widget_set_name (ts->priv->label_tip, "gtk-tooltip");
+    g_object_ref_sink (ts->priv->label_tip);
+    gtk_label_set_justify (GTK_LABEL (ts->priv->label_tip), GTK_JUSTIFY_CENTER);
+
+    /* setup period combo and snap info */
+
+    vbox = gtk_vbox_new (FALSE, 5);
+    ts->priv->period = gtk_combo_box_new ();
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ts->priv->period),
+            renderer,
+            TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (ts->priv->period), renderer,
+            "text", 1,
+            NULL);
+    g_signal_connect (ts->priv->period, "changed", G_CALLBACK (period_changed), ts);
+
+    ts->priv->info = gtk_label_new ("info\ninfo");
+    gtk_label_set_justify (GTK_LABEL (ts->priv->info), GTK_JUSTIFY_CENTER);
+    gtk_box_pack_start (GTK_BOX (vbox), ts->priv->period, FALSE, FALSE, 5);
+    gtk_box_pack_end (GTK_BOX (vbox), ts->priv->info, FALSE, FALSE, 5);
+
+    /* setup container */
+
+    gtk_box_pack_start (GTK_BOX (ts), vbox, FALSE, FALSE, 0);
+    gtk_box_pack_start (GTK_BOX (ts), ts->priv->scrolled, TRUE, TRUE, 0);
+
+    gtk_widget_set_can_focus(GTK_WIDGET (ts), TRUE);
+    gtk_widget_show_all (GTK_WIDGET (ts));
+}
+
+static float
+get_max_snap_size (GList *snaps)
+{
+    Snap *snap;
+    float max_size = 0;
+    while (snaps)
+    {
+        GList *next = snaps->next;
+        snap = (Snap*) snaps->data;
+        if (snap->used_space > max_size)
+            max_size = snap->used_space;
+        snaps = next;
+    }
+    return max_size;
+}
+
+static Snap*
+get_now_snap (TimeScale* ts)
+{
+    Snap *last_snap;
+    last_snap = g_new0 (Snap, 1);
+    last_snap->name = g_strdup (_("Now"));
+    last_snap->mountpoint = g_strdup (_("None"));
+    last_snap->mtime_str = g_strdup (_("Now"));
+    last_snap->mtime_short_str = g_strdup (_("Now"));
+    last_snap->used_space_str = g_strdup (_("-"));
+    last_snap->used_space = 0.0;
+    last_snap->type = NOW;
+    last_snap->type_str = g_strdup (_("Current Directory"));
+    return last_snap;
+}
+
+static char *
+get_type_str (SnapType type)
+{
+    switch (type)
+    {
+        case LOCAL_AUTOMATIC:
+            return g_strdup (_("Automatic Snapshot"));
+            break;
+        case LOCAL_MANUAL:
+            return g_strdup (_("Manual Snapshot"));
+            break;
+        case REMOTE_AUTOMATIC:
+            return g_strdup (_("Automatic Remote Backup"));
+            break;
+        case REMOTE_MANUAL:
+            return g_strdup (_("Manual Remote Backup"));
+            break;
+        default:
+            break;
+    }
+    return NULL;
+}
+
+static SnapType
+get_type (ZfsDataSet* snap)
+{
+    if (snap->type)
+    {
+        if (strstr (snap->name, "zfs-auto-snap"))
+            return LOCAL_AUTOMATIC;
+        else
+            return LOCAL_MANUAL;
+    }
+    else
+    {
+        if (strstr (snap->name, "zfs-auto-snap"))
+            return REMOTE_AUTOMATIC;
+        else
+            return REMOTE_MANUAL;
+    }
+}
+
+static GList *
+copy_zfs_list (GList *list)
+{
+    ZfsDataSet *snap;
+    GList *new_list = NULL;
+    Snap *ts_shot;
+    GList *tmp_list = list;
+    while (tmp_list)
+    {
+        snap = (ZfsDataSet*) tmp_list->data;
+        ts_shot = g_new0 (Snap, 1);
+        ts_shot->name = g_strdup (snap->name);
+        ts_shot->mountpoint = g_strdup (snap->mountpoint);
+        ts_shot->mtime_str = g_strdup (snap->mtime_str);
+        ts_shot->mtime_short_str = caja_date_as_string (snap->mtime, TRUE);
+        ts_shot->used_space_str = g_strdup (snap->used_space_str);
+        ts_shot->used_space = snap->used_space;
+        ts_shot->mtime = snap->mtime;
+        ts_shot->type = get_type (snap);
+        ts_shot->type_str = get_type_str (ts_shot->type);
+        new_list = g_list_append (new_list, ts_shot);
+        tmp_list = tmp_list->next;
+    }
+    return new_list;
+}
+
+static void
+free_snap (Snap *snap)
+{
+    if (snap->name)
+        g_free (snap->name);
+    if (snap->mountpoint)
+        g_free (snap->mountpoint);
+    if (snap->mtime_str)
+        g_free (snap->mtime_str);
+    if (snap->mtime_short_str)
+        g_free (snap->mtime_short_str);
+    if (snap->used_space_str)
+        g_free (snap->used_space_str);
+    if (snap->type_str)
+        g_free (snap->type_str);
+
+    g_free (snap);
+}
+
+static void
+free_snap_list (GList *list)
+{
+    GList *tmp_list = list;
+    while (tmp_list)
+    {
+        free_snap ((Snap*) tmp_list->data);
+        tmp_list = tmp_list->next;
+    }
+}
+
+
+void
+timescale_set_snapshots (TimeScale* ts, GList *list, int init_position)
+{
+    if (ts->priv->bar_x_end)
+        g_free (ts->priv->bar_x_end);
+
+    if (ts->priv->num_rev_string)
+        g_free (ts->priv->num_rev_string);
+
+    if (ts->priv->all_snaps)
+        free_snap_list (ts->priv->all_snaps);
+
+    ts->priv->all_snaps = copy_zfs_list (list);
+    ts->priv->all_snaps = g_list_append (ts->priv->all_snaps, get_now_snap(ts));
+    ts->priv->snaps = ts->priv->all_snaps;
+    ts->priv->num_snaps = g_list_length (ts->priv->snaps);
+
+    ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps));
+    ts->priv->current_pos = ts->priv->num_snaps - 1;
+
+    if (init_position >= 0 && init_position <= ts->priv->num_snaps - 1)
+        ts->priv->current_pos = init_position;
+
+    ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps);
+
+    gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string);
+    ts->priv->current_period = ALL;
+
+    gtk_combo_box_set_model (GTK_COMBO_BOX (ts->priv->period), GTK_TREE_MODEL (create_periods (ts)));
+    gtk_combo_box_set_active (GTK_COMBO_BOX (ts->priv->period), ALL);
+
+    gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX) * ts->priv->num_snaps) + PADDING * 2, 60);
+    ts->priv->scrollbar_set = FALSE;
+}
+
+GtkWidget*
+timescale_new ()
+{
+    return g_object_new (TYPE_TIMESCALE, NULL);
+}
+
+static void
+draw_type (cairo_t *cr, int x_c, int y_c, int w, SnapType type)
+{
+    int x = x_c - w/2;
+    int y = y_c - w/2;
+
+
+
+    switch (type)
+    {
+        case LOCAL_MANUAL:
+            cairo_move_to (cr,x,y_c);
+            cairo_line_to (cr,x+w/2,y_c-w/2);
+            cairo_line_to (cr,x+w,y_c);
+            /*y = y_c - w/4;
+              cairo_rectangle (cr, x, y, w, w/2);*/
+            break;
+        case LOCAL_AUTOMATIC:
+            cairo_arc (cr, x_c, y_c, w/2, M_PI, M_PI*2);
+            break;
+        case REMOTE_MANUAL:
+            /* cairo_rectangle (cr, x, y, w, w); */
+            cairo_move_to (cr,x,y_c);
+            cairo_line_to (cr,x+w/2,y_c+w/2);
+            cairo_line_to (cr,x+w,y_c);
+            cairo_line_to (cr,x+w/2,y_c-w/2);
+            break;
+        case REMOTE_AUTOMATIC:
+            cairo_arc (cr, x_c, y_c, w/2, 0.0, M_PI*2);
+            break;
+        default:
+            break;
+    }
+}
+
+static void
+draw_rounded_bar (cairo_t *cr, int x, int y, double w, double h, int r)
+{
+    /* bottom y instead of top */
+    y -= h;
+
+    if (h == 0 || h < (w / 2) + r)
+    {
+        y += h;
+        cairo_arc (cr, x+(w/2), y+.5, w/2, M_PI, M_PI*2);
+        cairo_line_to (cr,x,y+.5);
+        return;
+    }
+
+    /*    A ****BQ
+          H      C
+          *      *
+          G      D
+          F **** E */
+
+
+    cairo_arc (cr, x+(w/2), y+(w/2), w/2, M_PI, M_PI*2); /* arc from H to C */
+    cairo_line_to (cr,x+w,y+h-r);             /* Move to D */
+    cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h);   /* Curve to E */
+    cairo_line_to(cr, x+r,y+h);                 /* Line to F */
+    cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r);         /* Curve to G */
+    cairo_line_to(cr, x,y+(w/2));                 /* Line to H */
+
+}
+
+
+
+static void
+draw_rounded_rec (cairo_t *cr, int x, int y, double w, double h, int r)
+{
+    /* bottom y instead of top */
+    y -= h;
+
+    if (h == 0)
+    {
+        cairo_set_line_width (cr, 1);
+        cairo_move_to (cr,x,y - .5);
+        cairo_line_to (cr, x + w, y -.5);
+        return;
+    }
+
+    if (h < r * 2)
+        r = h / 2;
+
+    /*    A****BQ
+          H      C
+          *      *
+          G      D
+          F****E */
+
+    cairo_move_to (cr,x+r,y);                 /* Move to A */
+    cairo_line_to (cr,x+w-r,y);                      /* Straight line to B */
+    cairo_curve_to (cr,x+w,y,x+w,y,x+w,y+r);         /* Curve to C, Control points are both at Q */
+    cairo_line_to (cr,x+w,y+h-r);             /* Move to D */
+    cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h);   /* Curve to E */
+    cairo_line_to(cr, x+r,y+h);                 /* Line to F */
+    cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r);         /* Curve to G */
+    cairo_line_to(cr, x,y+r);                 /* Line to H */
+    cairo_curve_to(cr, x,y,x,y,x+r,y);             /*  Curve to A */
+}
+
+    static void
+set_cr_color (GtkWidget *widget, cairo_t* cr, SnapType type, double alpha)
+{
+    GtkStyle * s;
+
+    switch (type)
+    {
+        case LOCAL_MANUAL:
+        case REMOTE_MANUAL:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->dark[GTK_STATE_SELECTED]);
+            break;
+        case LOCAL_AUTOMATIC:
+        case REMOTE_AUTOMATIC:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->light[GTK_STATE_SELECTED]);
+            break;
+        case NOW:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->black);
+            break;
+        default:
+            break;
+    }
+}
+
+int get_snap_index_from_coord (TimeScale* ts, gdouble x, gdouble y)
+{
+    int i;
+
+    for (i = 0; i < ts->priv->num_snaps; i++)
+    {
+        if (x < ts->priv->bar_x_end[i])
+            return i;
+    }
+    return -1;
+}
+
+static gboolean
+timescale_expose (GtkWidget *widget,
+        GdkEventExpose *event, TimeScale* ts)
+{
+    PangoContext *context;
+    PangoFontMetrics *metrics;
+    PangoRectangle logical_rect;
+    gint ascent, descent;
+    cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
+    int i = 0;
+    double x;
+    int y;
+    int selected_x = -1;
+    int remaining_timeline_space;
+    int remaining_x_start;
+    int remaining_x_end;
+    int bar_space = BAR_SPACE;
+    int bar_w = BAR_W_MAX;
+    int line_padding = 3;
+    float bar_max_h_inc;
+    float bar_max_h;
+    int last_bar_x;
+    int line_height = 0;
+    int timeline_line_height = 0;
+    float max_size = 0;
+    GList *view_snaps = NULL;
+    int num_view_snaps = 0;
+    PangoLayout *layout = pango_cairo_create_layout (cr);
+    Snap *tmp_snap = NULL;
+    gboolean space_left = TRUE;
+    PangoFontDescription *timeline_font = NULL;
+
+    if (gtk_widget_has_focus (widget)) {
+        gtk_paint_focus (gtk_widget_get_style (widget), cr, gtk_widget_get_state (widget),
+                widget, NULL,
+                0, 0,
+                gtk_widget_get_allocated_width(widget)-1, gtk_widget_get_allocated_height(widget)-1);
+    }
+
+    if (!ts->priv->snaps)
+        goto end;
+
+    /* smaller font for timeline */
+
+    timeline_font = pango_font_description_copy_static ((gtk_widget_get_style (widget))->font_desc);
+    pango_font_description_set_size (timeline_font, pango_font_description_get_size (timeline_font) - (PANGO_SCALE*2));
+
+    /* determine space needed for 2 lines of text + padding with
+     * current widget->style->font_desc
+     * and widget->style->font_desc - 1*/
+
+    context = gtk_widget_get_pango_context (widget);
+    metrics = pango_context_get_metrics (context, (gtk_widget_get_style (widget))->font_desc,
+            pango_context_get_language (context));
+    ascent = pango_font_metrics_get_ascent (metrics);
+    descent = pango_font_metrics_get_descent (metrics);
+    pango_font_metrics_unref (metrics);
+
+    line_height = PANGO_PIXELS (ascent + descent);
+
+    metrics = pango_context_get_metrics (context, timeline_font,
+            pango_context_get_language (context));
+    ascent = pango_font_metrics_get_ascent (metrics);
+    descent = pango_font_metrics_get_descent (metrics);
+    pango_font_metrics_unref (metrics);
+
+    timeline_line_height = PANGO_PIXELS (ascent + descent);
+
+
+    x = PADDING;
+    bar_max_h = gtk_widget_get_allocated_height(widget) - ((line_height + timeline_line_height) + PADDING * 4);
+    y =  gtk_widget_get_allocated_height(widget) - (line_height + (PADDING * 2));
+
+    num_view_snaps = ts->priv->num_snaps;
+    view_snaps = ts->priv->snaps;
+
+    max_size = log ((float)get_max_snap_size (view_snaps));
+
+    bar_max_h_inc =  max_size / bar_max_h;
+
+    if (!ts->priv->scrollbar_set)
+    {
+        GtkAdjustment *adj;
+        adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled));
+        gtk_adjustment_set_value (adj, gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size (adj));
+        gtk_adjustment_set_step_increment(adj, BAR_W_MAX + BAR_SPACE);
+        gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        ts->priv->scrollbar_set = TRUE;
+    }
+
+    /* draw the bars */
+
+    cairo_set_line_width (cr, 1);
+
+    for (i = 0; i < num_view_snaps; i++)
+    {
+        tmp_snap = g_list_nth_data (view_snaps, i);
+        int rounded_radius = ROUNDED_RADIUS;
+        int height = 0;
+        gboolean draw_bar = TRUE;
+        double alpha = 1.0;
+
+        if (tmp_snap->used_space != 0)
+            height = (int) log (tmp_snap->used_space) / bar_max_h_inc;
+
+        /*printf ("drawing %d height %d size %f log of size %f\n", i,
+          height,
+          tmp_snap->used_space, log (tmp_snap->used_space));*/
+
+        if (height == 0 & tmp_snap->used_space != 0)
+            height = 1;
+
+        if (tmp_snap->type == REMOTE_AUTOMATIC ||
+                tmp_snap->type == REMOTE_MANUAL)
+            height = bar_max_h - bar_max_h_inc; /* placeholder until we get rsync size */
+
+        if (height < ROUNDED_RADIUS)
+            height = 0;
+
+        /* printf ("height %d name %s size %s\n", height, tmp_snap->name, tmp_snap->used_space_str);  */
+
+        if (i == ts->priv->current_pos)
+        {
+
+            cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1);
+            draw_rounded_rec (cr, x-1, y+2, bar_w+2, bar_max_h+8, ROUNDED_RADIUS);
+
+            cairo_fill (cr);
+            cairo_stroke(cr);
+
+            alpha = 0.5;
+            selected_x = x-1 + ((bar_w+2) / 2);
+
+            if (tmp_snap->type != NOW)
+            {
+                char *selected_time_size = g_strdup_printf ("%s - %s",
+                        tmp_snap->mtime_str,
+                        tmp_snap->used_space_str);
+                gdk_cairo_set_source_color (cr,  gtk_widget_get_style(widget)->text);
+                pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc);
+                pango_layout_set_text (layout, selected_time_size, -1);
+                g_free (selected_time_size);
+            }
+            else
+                pango_layout_set_text (layout, tmp_snap->mtime_str, -1);
+        }
+
+        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8);
+
+        ts->priv->bar_x_end[i] = x + bar_w;
+
+        if (draw_bar)
+        {
+            int tmp_y = y;
+            float tmp_w = bar_w;
+            set_cr_color (widget, cr, tmp_snap->type, 1.0);
+            if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == LOCAL_MANUAL)
+            {
+                tmp_y -= 1;
+                tmp_w -= 1;
+            }
+
+            if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == REMOTE_AUTOMATIC)
+                draw_rounded_bar (cr, x, tmp_y, tmp_w, height, rounded_radius);
+            else if (tmp_snap->type == LOCAL_MANUAL || tmp_snap->type == REMOTE_MANUAL)
+                draw_rounded_rec (cr, x, tmp_y, tmp_w, height, rounded_radius);
+            else /* Now Shape */
+                draw_type (cr, (x + ((bar_w+bar_space)/2)) - .5, y - (bar_max_h/2), bar_w - 4, REMOTE_MANUAL);
+
+            if (tmp_snap->type == REMOTE_MANUAL || tmp_snap->type == REMOTE_AUTOMATIC || tmp_snap->type == NOW)
+                cairo_fill (cr);
+        }
+
+        cairo_stroke(cr);
+
+        x += bar_w + bar_space;
+    }
+
+    last_bar_x = x;
+
+    /* ensure selected bar is visible on key press when scrollbar is enabled */
+
+    if (ts->priv->current_pos != -1 && ts->priv->key_pressed)
+    {
+
+        GtkAdjustment *adj;
+        adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled));
+        ts->priv->key_pressed = FALSE;
+
+        if (gtk_adjustment_get_value (adj) > selected_x)
+        {
+            gtk_adjustment_set_value (adj, selected_x - BAR_W_MAX);
+            gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        }
+        if (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj) < selected_x)
+        {
+            gtk_adjustment_set_value (adj, (selected_x + BAR_W_MAX) - gtk_adjustment_get_page_size (adj));
+            gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        }
+    }
+
+    pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc);
+
+    /* try to center the selected text */
+
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (ts->priv->current_pos != -1 && selected_x != -1)
+    {
+
+        int right_space = last_bar_x - selected_x;
+
+        if (logical_rect.width /2 > selected_x)
+            /* no space on the left, left align */
+            selected_x = PADDING;
+        else if (logical_rect.width /2 > right_space)
+            /* no space on the right, right align */
+            selected_x = last_bar_x - logical_rect.width;
+        else
+            selected_x -= logical_rect.width /2;
+
+        if (selected_x < 0)
+            selected_x = PADDING;
+
+        /* draw background */
+        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1);
+        draw_rounded_rec (cr, selected_x-2, line_height + 3 , logical_rect.width + 4, line_height+1, ROUNDED_RADIUS);
+        cairo_fill (cr);
+        cairo_stroke(cr);
+
+        /* then selected text */
+        gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text);
+        cairo_move_to(cr, selected_x, PADDING);
+        pango_cairo_show_layout (cr, layout);
+    }
+
+    /* timeline */
+
+    /* what to do
+     * try to draw oldest
+     * then "now" first
+     * then in between dates
+     * draw additional is possible starting with newest first */
+
+    x = PADDING;
+
+
+    /* Can the oldest time fit ? */
+    tmp_snap = g_list_nth_data (view_snaps, 0);
+    pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+    pango_layout_set_font_description (layout, timeline_font);
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (logical_rect.width >  gtk_widget_get_allocated_width(widget))
+        goto end;
+
+    /* draw anchor line */
+
+    x += bar_w/2 + .5;
+
+    cairo_set_line_width (cr, 1);
+    gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa);
+    cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1));
+    cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_line_to (cr, x+2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_stroke(cr);
+
+    x += 3;
+
+    gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa);
+    cairo_move_to(cr, x,  gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+    pango_cairo_show_layout (cr, layout);
+
+    remaining_timeline_space = last_bar_x - (x + logical_rect.width);
+
+    remaining_x_start = x + logical_rect.width;
+
+    /* try to draw last item */
+
+    tmp_snap = g_list_nth_data (view_snaps, num_view_snaps-1);
+    pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (remaining_timeline_space < (logical_rect.width + bar_w))
+        goto end;
+
+    remaining_timeline_space -= logical_rect.width + bar_w;
+
+    x = last_bar_x;
+
+
+    x -= bar_space + bar_w/2 + 0.5;
+
+    cairo_move_to (cr, x,  gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-2));
+    cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_line_to (cr, x-2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_stroke(cr);
+
+    cairo_move_to(cr, x - (logical_rect.width + 3.5),
+             gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+    pango_cairo_show_layout (cr, layout);
+
+    remaining_x_end = x - (logical_rect.width + 3.5);
+
+    /* now find the next bar that we can to the timeline and loop */
+
+
+    while (space_left)
+    {
+        int bar = get_snap_index_from_coord (ts, remaining_x_start, 0);
+        /* get the next snap */
+        bar++;
+        if (bar >= num_view_snaps)
+            goto end;
+
+        tmp_snap = g_list_nth_data (view_snaps, bar);
+        pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+
+        pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+        if (remaining_timeline_space < logical_rect.width + 4)
+            goto end;
+
+        /* get middle x coord of current bar */
+
+        x = PADDING + ((bar_w + bar_space) * bar) + bar_w / 2;
+
+        if ( x + 4 + logical_rect.width > remaining_x_end)
+            goto end;
+
+        /* draw anchor and text */
+        x += 0.5;
+
+        cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1));
+        cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+        cairo_line_to (cr, x+2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+        cairo_stroke(cr);
+
+        x += 3;
+
+        cairo_move_to(cr, x,
+                 gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+        pango_cairo_show_layout (cr, layout);
+
+        remaining_x_start = x + logical_rect.width;
+
+        remaining_timeline_space = remaining_x_end - remaining_x_start;
+
+        if (remaining_timeline_space <= 0)
+            space_left = FALSE;
+    }
+end:
+    if (timeline_font)
+        pango_font_description_free(timeline_font);
+    cairo_destroy(cr);
+
+    return FALSE;
+}
+
+static int
+key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale* ts)
+{
+    switch (event->keyval)
+    {
+        case GDK_KEY_KP_Left:
+        case GDK_KEY_KP_Up:
+        case GDK_KEY_Left:
+        case GDK_KEY_Up:
+
+            if (ts->priv->current_pos >= 1)
+            {
+                ts->priv->current_pos -= 1;
+                gtk_widget_queue_draw (widget);
+                ts->priv->key_pressed = TRUE;
+                g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+                /* printf ("key_pressed back %d\n", ts->priv->current_pos); */
+            }
+            return TRUE;
+        case GDK_KEY_KP_Right:
+        case GDK_KEY_KP_Down:
+        case GDK_KEY_Right:
+        case GDK_KEY_Down:
+            if (ts->priv->current_pos <= ts->priv->num_snaps - 2)
+            {
+                ts->priv->current_pos += 1;
+                gtk_widget_queue_draw (widget);
+                ts->priv->key_pressed = TRUE;
+                g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+                /* printf ("key_pressed forward %d\n", ts->priv->current_pos); */
+            }
+            return TRUE;
+
+        default:
+            return FALSE;
+    }
+}
+
+int timescale_get_position (TimeScale* ts)
+{
+    /* translate into all_snaps position */
+    gconstpointer data;
+    data = g_list_nth_data (ts->priv->snaps, ts->priv->current_pos);
+    return g_list_index (ts->priv->all_snaps, data);
+}
+
+static int match_func (ZfsDataSet *set, char *dir)
+{
+    return strcmp (set->mountpoint, dir);
+}
+
+void
+timescale_set_position (TimeScale* ts, char *mountpoint)
+{
+    gboolean redraw = FALSE;
+    int num_snaps = g_list_length (ts->priv->all_snaps);
+    if (mountpoint)
+    {
+        int pos;
+        GList* match = NULL;
+        match = g_list_find_custom (ts->priv->all_snaps, mountpoint, (GCompareFunc)match_func);
+        pos = g_list_index (ts->priv->all_snaps, match->data);
+        if (pos != -1 && ts->priv->current_pos != pos)
+        {
+            redraw = TRUE;
+            ts->priv->current_pos = pos;
+        }
+    }
+    else
+    { /* Now special case */
+        if (ts->priv->current_pos != num_snaps - 1)
+        {
+            redraw = TRUE;
+            ts->priv->current_pos = num_snaps - 1;
+        }
+    }
+
+    if (redraw)
+        gtk_widget_queue_draw (GTK_WIDGET (ts));
+}
+
+static void
+button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale* ts)
+{
+    int new_pos;
+
+    gtk_widget_grab_focus (widget);
+    new_pos = get_snap_index_from_coord (ts, event->x, event->y);
+
+    if (new_pos == -1)
+        return;
+
+    if (new_pos != ts->priv->current_pos)
+    {
+        ts->priv->current_pos = new_pos;
+        /* printf ("button_pressed (%g,%g) selected %d\n", event->x, event->y, ts->priv->current_pos); */
+        g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+        gtk_widget_queue_draw (widget);
+    }
+}
+
+static gboolean
+query_tooltip (GtkWidget  *widget,
+        gint        x,
+        gint        y,
+        gboolean    keyboard_tip,
+        GtkTooltip *tooltip,
+        gpointer    data)
+{
+    static gboolean set = FALSE;
+    static int old_pos = -1;
+    TimeScale* ts = TIMESCALE (data);
+    int new_pos = get_snap_index_from_coord (ts, x, y);
+    /* printf ("in query_tooltip new_pos %d total %d num_snap %d\n", new_pos, g_list_length (ts->priv->snaps), ts->priv->num_snaps); */
+    Snap *tmp_snap = NULL;
+    char *tip = NULL;
+
+    if (new_pos == -1)
+        return FALSE;
+
+    if (new_pos != old_pos)
+    {
+        old_pos = new_pos;
+        return FALSE;
+    }
+
+    if (new_pos >= ts->priv->num_snaps)
+        return FALSE;
+
+    tmp_snap = g_list_nth_data (ts->priv->snaps, new_pos);
+
+    tip = g_strdup_printf ("%s\nCreated: %s\nSize: %s\nName: %s", tmp_snap->type_str, tmp_snap->mtime_str, tmp_snap->used_space_str, tmp_snap->name);
+    gtk_label_set_text (GTK_LABEL(ts->priv->label_tip), tip);
+    gtk_tooltip_set_custom (tooltip, ts->priv->label_tip);
+    g_free (tip);
+    return TRUE;
+}
+
+static void
+timescale_class_init (TimeScaleClass *class)
+{
+    GtkWidgetClass *widget_class;
+    GtkScaleClass *scale_class;
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (class);
+    g_type_class_add_private (class, sizeof (TimeScalePrivate));
+
+    widget_class = GTK_WIDGET_CLASS (class);
+
+    signals[VALUE_CHANGED] =
+        g_signal_new ("value-changed",
+                G_TYPE_FROM_CLASS (object_class),
+                G_SIGNAL_RUN_LAST,
+                G_STRUCT_OFFSET (TimeScaleClass, value_changed),
+                NULL, NULL,
+                g_cclosure_marshal_VOID__VOID,
+                G_TYPE_NONE, 0);
+}
+
--- caja-1.28.0/src/timescale.h.orig    2024-02-26 08:42:41.085975314 +0100
+++ caja-1.28.0/src/timescale.h    2024-02-26 08:42:41.085928246 +0100
@@ -0,0 +1,58 @@
+#ifndef __TIMESCALE_H__
+#define __TIMESCALE_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+#define TYPE_TIMESCALE            (timescale_get_type ())
+#define TIMESCALE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TIMESCALE, TimeScale))
+#define TIMESCALE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TIMESCALE, TimeScaleClass))
+#define IS_TIMESCALE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TIMESCALE))
+#define IS_TIMESCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TIMESCALE))
+#define TIMESCALE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TIMESCALE, TimeScaleClass))
+
+
+typedef struct _TimeScale       TimeScale;
+typedef struct _TimeScaleClass  TimeScaleClass;
+typedef struct TimeScalePrivate TimeScalePrivate;
+
+struct _TimeScale
+{
+  GtkHBox hbox;
+  TimeScalePrivate *priv;
+};
+
+struct _TimeScaleClass
+{
+  GtkHBoxClass parent_class;
+  void (* value_changed)    (TimeScale *timescale);
+
+};
+
+
+GType      timescale_get_type       (void) G_GNUC_CONST;
+GtkWidget* timescale_new            ();
+void       timescale_set_snapshots (TimeScale* ts, GList *list, int init_position);
+int       timescale_get_position   (TimeScale* ts);
+void       timescale_set_position   (TimeScale* ts, char *mountpoint);
+
+
+#define ROUNDED_RADIUS 6
+#define PADDING 3
+
+typedef enum {
+  LOCAL_MANUAL,
+  LOCAL_AUTOMATIC,
+  REMOTE_MANUAL,
+  REMOTE_AUTOMATIC,
+  NOW
+} SnapType;
+
+
+G_END_DECLS
+
+#endif /* __TIMESCALE_H__ */
components/desktop/mate/caja/patches/Archiv/01-time-slider.patch.org
components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1
New file
@@ -0,0 +1,59 @@
--- caja-1.28.0/src/caja-zfs-bar.h.orig    2024-02-26 08:42:41.083373824 +0100
+++ caja-1.28.0/src/caja-zfs-bar.h    2024-02-26 08:42:41.083327328 +0100
@@ -0,0 +1,56 @@
+#ifndef __CAJA_ZFS_BAR_H
+#define __CAJA_ZFS_BAR_H
+
+#include <gtk/gtk.h>
+#include <libcaja-private/caja-directory.h>
+#include "caja-window-slot.h"
+
+G_BEGIN_DECLS
+
+#define CAJA_TYPE_ZFS_BAR         (caja_zfs_bar_get_type ())
+#define CAJA_ZFS_BAR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBar))
+#define CAJA_ZFS_BAR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CAJA_TYPE_ZFS_BAR, CajaZfsBarClass))
+#define CAJA_IS_ZFS_BAR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CAJA_TYPE_ZFS_BAR))
+#define CAJA_IS_ZFS_BAR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CAJA_TYPE_ZFS_BAR))
+#define CAJA_ZFS_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBarClass))
+
+typedef struct CajaZfsBarPrivate CajaZfsBarPrivate;
+
+typedef struct
+{
+  GtkEventBox eventbox;
+  CajaZfsBarPrivate *priv;
+} CajaZfsBar;
+
+typedef struct
+{
+  GtkEventBoxClass parent_class;
+} CajaZfsBarClass;
+
+GType                        caja_zfs_bar_get_type (void) G_GNUC_CONST;
+
+GtkWidget *            caja_zfs_bar_new ();
+
+void                caja_zfs_bar_setup (CajaZfsBar* bar,
+                                                    CajaDirectory *dir,
+                                                             CajaWindowSlot *active_slot,
+                                                     GtkToggleAction* action);
+
+void                caja_zfs_bar_display (CajaZfsBar *bar,
+                                                    CajaWindow *window,
+                                                      CajaDirectory *new_dir,
+                                                       GCancellable* cancellable);
+
+void                caja_zfs_set_snap (CajaZfsBar *bar,
+                                               CajaDirectory *dir);
+void                   caja_zfs_bar_remove_and_skip_snap
+                                      (CajaZfsBar *bar, char *path);
+
+void                caja_zfs_bar_hide (CajaZfsBar *bar);
+
+void                caja_zfs_bar_cancel_tasks (CajaWindow *window);
+CajaDirectory * caja_zfs_bar_get_dir (CajaZfsBar* bar);
+
+G_END_DECLS
+
+#endif /* __CAJA_ZFS_BAR_H */
components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2
New file
@@ -0,0 +1,854 @@
--- caja-1.28.0/src/caja-zfs-bar.c.orig    2024-02-26 08:42:41.083214681 +0100
+++ caja-1.28.0/src/caja-zfs-bar.c    2024-02-26 08:42:41.083165019 +0100
@@ -0,0 +1,851 @@
+/*
+ * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+#include "config.h"
+#include <strings.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include "caja-zfs-bar.h"
+#include "caja-window.h"
+#include "caja-window-private.h"
+#include "timescale.h"
+#include <libcaja-private/caja-zfs.h>
+#include <libcaja-private/caja-file.h>
+#include <libcaja-private/caja-global-preferences.h>
+#include "caja-window-slot.h"
+#include "caja-navigation-window.h"
+
+#define CAJA_ZFS_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CAJA_TYPE_ZFS_BAR, CajaZfsBarPrivate))
+
+struct CajaZfsBarPrivate
+{
+    CajaDirectory     *dir;
+    GtkWidget         *scale;
+    GtkWidget         *delete_button;
+    GdkColor           in_snapshot;
+    GtkToggleAction   *action;
+    CajaWindowSlot    *slot;
+    int                num_range_items;
+    gboolean           is_setup;
+    gboolean           set_only;
+    char              *current_path;
+    ZfsSnapDirMonitor *zfs_dir_monitor_data;
+    gboolean           in_snapshot_dir;
+    GtkWidget         *camera_image;
+    GtkWidget         *delete_image;
+    gboolean           explicit_user_hide;
+};
+
+G_DEFINE_TYPE (CajaZfsBar, caja_zfs_bar, GTK_TYPE_EVENT_BOX)
+
+static void
+close_clicked_callback (GtkWidget *widget,
+                        CajaZfsBar *bar);
+static void
+slider_moved_callback (TimeScale *ts,
+        CajaZfsBar *bar);
+static void
+update_delete_or_snap_button (CajaZfsBar *bar,
+        gboolean in_snap);
+
+static void
+caja_zfs_bar_class_init (CajaZfsBarClass *klass)
+{
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (klass, sizeof (CajaZfsBarPrivate));
+}
+
+static void
+set_scale_range (CajaZfsBar *bar, gboolean set_initial_position)
+{
+    int i;
+    GList *tmp;
+
+    if (bar->priv->dir)
+    {
+        i = caja_directory_get_num_snapshots (bar->priv->dir);
+
+        if (i==0)
+            return;
+
+        bar->priv->num_range_items = i;
+
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+
+        if (set_initial_position)
+            timescale_set_snapshots (TIMESCALE (bar->priv->scale), tmp, -1);
+        else
+            timescale_set_snapshots (TIMESCALE (bar->priv->scale), tmp, bar->priv->num_range_items);
+    }
+}
+
+static void
+update_range (CajaZfsBar *bar)
+{
+    if (caja_directory_get_snapshots (bar->priv->dir))
+    {
+        set_scale_range (bar, FALSE);
+        slider_moved_callback (TIMESCALE (bar->priv->scale), bar);
+    }
+    else
+        close_clicked_callback (NULL, bar);
+}
+
+static int
+match_func (ZfsDataSet *set, char *dir)
+{
+    /* remove trailing slash from dir */
+    char mountp[PATH_MAX+1];
+    int length = strlen (set->mountpoint);
+
+    if (set->mountpoint[length-1] == '/')
+    {
+        memcpy (mountp, set->mountpoint, length - 1);
+        mountp[length-1] = NULL;
+        return strcmp (mountp, dir);
+    }
+    else
+        return strcmp (set->mountpoint, dir);
+}
+
+void
+caja_zfs_bar_remove_and_skip_snap (CajaZfsBar *bar, char *path)
+{
+    GList* match = NULL;
+    GList* snap_list = NULL;
+    ZfsDataSet *snap;
+
+    snap_list = caja_directory_get_snapshots (bar->priv->dir);
+    match = g_list_find_custom (snap_list, path, (GCompareFunc)match_func);
+
+    if (match)
+    {
+        snap = (ZfsDataSet*) match->data;
+        caja_directory_remove_snapshot (bar->priv->dir, snap);
+        update_range (bar);
+    }
+}
+
+static void
+update_delete_or_snap_button (CajaZfsBar *bar, gboolean in_snap)
+{
+    if (in_snap)
+    {
+        if (!bar->priv->in_snapshot_dir)
+        { /*in main dir to snap */
+            bar->priv->in_snapshot_dir = TRUE;
+            g_object_ref (bar->priv->camera_image);
+            gtk_button_set_image (GTK_BUTTON (bar->priv->delete_button),
+                    bar->priv->delete_image);
+            gtk_widget_set_tooltip_text (bar->priv->delete_button,
+                    /* SUN_BRANDING */
+                    _("Delete this snapshot"));
+        }
+    }
+    else
+    {
+        if (bar->priv->in_snapshot_dir)
+        { /* in snap to main dir */
+            bar->priv->in_snapshot_dir = FALSE;
+            g_object_ref (bar->priv->delete_image);
+            gtk_button_set_image (GTK_BUTTON (bar->priv->delete_button),
+                    bar->priv->camera_image);
+            gtk_widget_set_tooltip_text (bar->priv->delete_button,
+                    /* SUN_BRANDING */
+                    _("Take a zfs snapshot of this directory now"));
+        }
+    }
+}
+
+static void
+slider_moved_callback (TimeScale *ts,
+                         CajaZfsBar *bar)
+{
+    GList *tmp;
+    ZfsDataSet *snap;
+    GFile *snap_file;
+    int pos = timescale_get_position (ts);
+
+    if (pos < bar->priv->num_range_items)
+    {
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+
+        snap = g_list_nth_data (tmp, pos);
+
+        snap_file = g_file_new_for_path (snap->mountpoint);
+    }
+    else
+    {
+        snap_file = caja_directory_get_location (bar->priv->dir);
+        pos = bar->priv->num_range_items;
+    }
+
+    if (!bar->priv->set_only)
+    {
+        gboolean in_snap = FALSE;
+        char *path = g_file_get_path (snap_file);
+
+        if (g_file_test (path, G_FILE_TEST_IS_DIR))
+        {
+            caja_window_slot_go_to (bar->priv->slot,
+                    snap_file,
+                    FALSE);
+            if (bar->priv->current_path)
+                g_free (bar->priv->current_path);
+            bar->priv->current_path = path;
+        }
+        else
+        { /* the snapshot diappeared try the next one */
+            g_free (path);
+            g_object_unref (snap_file);
+            /* remove snap from list */
+            caja_directory_remove_snapshot (bar->priv->dir, snap);
+            update_range (bar);
+            return;
+        }
+
+        in_snap = ts_is_in_snapshot (path);
+        update_delete_or_snap_button (bar, ts_is_in_snapshot (path));
+
+    }
+
+    g_object_unref (snap_file);
+
+}
+
+static void
+delete_clicked_callback (GtkWidget *widget,
+        CajaZfsBar *bar)
+{
+    GList *tmp;
+    ZfsDataSet *snap;
+    GFile *snap_file;
+    char *path;
+    char *full_command;
+    int pos = timescale_get_position (TIMESCALE (bar->priv->scale));
+
+    if (bar->priv->in_snapshot_dir)
+    {
+        tmp = caja_directory_get_snapshots (bar->priv->dir);
+        snap = g_list_nth_data (tmp, pos);
+        snap_file = g_file_new_for_path (snap->mountpoint);
+        path = g_file_get_path (snap_file);
+
+        g_object_unref (snap_file);
+        if (snap->type)
+        {
+            /*printf ("path %s snapshot to delete %s\n", path, snap->name);*/
+            full_command = g_strdup_printf ("/usr/lib/time-slider-delete '%s'", snap->name);
+        }
+        else
+        {
+            /*printf ("path %s backup to delete %s\n", path, snap->name);*/
+            full_command = g_strdup_printf ("/usr/lib/time-slider-delete '%s'", path);
+        }
+
+        mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (widget),
+                full_command, NULL);
+    }
+    else
+    {
+        path = g_file_get_path (caja_directory_get_location (bar->priv->dir));
+        char *fs = ts_get_zfs_filesystem (path);
+        /* printf ("take a snapshot of zfs fs %s for dir %s\n", fs, path); */
+        full_command = g_strdup_printf ("/usr/lib/time-slider-snapshot '%s' '%s'", path, fs);
+        mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (widget),
+                full_command, NULL);
+        g_free (fs);
+    }
+
+    g_free (full_command);
+    g_free (path);
+}
+
+static void
+close_clicked_callback (GtkWidget *widget,
+        CajaZfsBar *bar)
+{
+    GFile *snap_file = caja_directory_get_location (bar->priv->dir);
+
+    caja_window_slot_go_to (bar->priv->slot,
+            snap_file,
+            FALSE);
+    g_object_unref (snap_file);
+
+    bar->priv->is_setup = FALSE;
+
+    gtk_widget_hide (GTK_WIDGET (bar));
+    gtk_toggle_action_set_active (bar->priv->action, FALSE);
+
+}
+
+static void
+caja_zfs_bar_init (CajaZfsBar *bar)
+{
+    GtkWidget *hbox, *toolbar, *close, *delete, *image, *button_vbox;
+    GtkToolItem *item;
+    char *path;
+
+    bar->priv = CAJA_ZFS_BAR_GET_PRIVATE (bar);
+
+    bar->priv->dir = NULL;
+    bar->priv->num_range_items = 0;
+    bar->priv->action = NULL;
+    bar->priv->slot = NULL;
+    bar->priv->is_setup = FALSE;
+    bar->priv->set_only = FALSE;
+
+    /* GUI init */
+
+    toolbar = gtk_toolbar_new ();
+    gtk_widget_show (toolbar);
+
+    item = gtk_tool_item_new ();
+    gtk_widget_show (GTK_WIDGET (item));
+    gtk_tool_item_set_expand (item, TRUE);
+
+    hbox = gtk_hbox_new (FALSE, 2);
+    gtk_widget_show (GTK_WIDGET (hbox));
+
+    gtk_container_add (GTK_CONTAINER (item),hbox);
+
+    gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
+    gtk_container_add (GTK_CONTAINER (bar),toolbar);
+    gtk_container_set_border_width (GTK_CONTAINER (bar), 0);
+
+    /* buttons */
+
+    button_vbox = gtk_vbox_new (FALSE, 0);
+    gtk_widget_show (button_vbox);
+
+    close = gtk_button_new ();
+    gtk_button_set_relief (GTK_BUTTON (close), GTK_RELIEF_NONE);
+    gtk_widget_set_tooltip_text (close,
+            /* SUN_BRANDING */
+            _("Close Time Slider and return to original directory"));
+    gtk_widget_show (close);
+
+    g_signal_connect (close,
+            "clicked",
+            G_CALLBACK (close_clicked_callback),
+            bar);
+
+    image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
+            GTK_ICON_SIZE_MENU);
+    gtk_widget_show (image);
+
+    gtk_container_add (GTK_CONTAINER (close), image);
+
+    gtk_box_pack_start (GTK_BOX (button_vbox), close, FALSE, FALSE, 0);
+
+    delete = gtk_button_new ();
+    gtk_button_set_relief (GTK_BUTTON (delete), GTK_RELIEF_NONE);
+    gtk_widget_set_tooltip_text (delete,
+            /* SUN_BRANDING */
+            _("Take a zfs snapshot of this directory now"));
+    gtk_widget_show (delete);
+
+    g_signal_connect (delete,
+            "clicked",
+            G_CALLBACK (delete_clicked_callback),
+            bar);
+
+    {
+        GtkIconTheme *it =  gtk_icon_theme_get_default ();
+        GdkPixbuf * pb = gtk_icon_theme_load_icon (it,
+                "user-trash-full.png",
+                16,
+                GTK_ICON_LOOKUP_GENERIC_FALLBACK,
+                NULL);
+        bar->priv->delete_image = gtk_image_new_from_pixbuf (pb);
+        g_object_unref (pb);
+    }
+
+    path = caja_pixmap_file ("camera.png");
+    bar->priv->camera_image = gtk_image_new_from_file (path);
+    g_free (path);
+
+    gtk_widget_show (bar->priv->delete_image);
+    gtk_widget_show (bar->priv->camera_image);
+
+    gtk_container_add (GTK_CONTAINER (delete), bar->priv->camera_image);
+
+    gtk_box_pack_end (GTK_BOX (button_vbox), delete, FALSE, FALSE, 0);
+    gtk_box_pack_end (GTK_BOX (hbox), button_vbox, FALSE, FALSE, 0);
+
+    bar->priv->delete_button = delete;
+
+    bar->priv->scale = timescale_new();
+
+    g_signal_connect (bar->priv->scale,
+            "value-changed",
+            G_CALLBACK (slider_moved_callback),
+            bar);
+
+    gtk_widget_show (bar->priv->scale);
+
+    gtk_box_pack_start (GTK_BOX (hbox), bar->priv->scale, TRUE, TRUE, 0);
+
+}
+
+    CajaDirectory *
+caja_zfs_bar_get_dir (CajaZfsBar* bar)
+{
+    g_return_val_if_fail (CAJA_IS_ZFS_BAR (bar), NULL);
+    return bar->priv->dir;
+}
+
+static void
+snapshot_data_ready (CajaDirectory *dir,
+                     GCancellable  *cancellable,
+                     gpointer callback_data)
+{
+    CajaWindowSlot *slot;
+    GFile *location;
+    GFile *dir_location;
+    CajaWindow *window = (CajaWindow*)callback_data;
+
+    g_return_if_fail (CAJA_IS_WINDOW (window));
+
+    slot = caja_window_get_active_slot (window);
+    location = caja_window_slot_get_location (slot);
+    dir_location = caja_directory_get_location (dir);
+
+    if (g_cancellable_is_cancelled (cancellable) && g_file_equal (location, dir_location))
+    {
+        caja_navigation_window_set_spinner_active (CAJA_NAVIGATION_WINDOW (window), FALSE);
+        caja_navigation_window_set_restore_icon ( CAJA_NAVIGATION_WINDOW (window), RESTORE_NO);
+    }
+    else if (g_file_equal (location, dir_location))
+    {
+        char *path = g_file_get_path (dir_location);
+        g_cancellable_cancel (cancellable);
+        caja_window_slot_set_allow_stop (slot, FALSE);
+        caja_navigation_window_set_restore_icon ( CAJA_NAVIGATION_WINDOW (window),
+                caja_directory_has_snapshots (dir) ? RESTORE_NORMAL : RESTORE_NO);
+        if (caja_directory_has_snapshots (dir))
+            caja_window_allow_restore (window, TRUE);
+        else
+            caja_window_allow_restore (window, FALSE);
+        g_free (path);
+    }
+    else
+    {
+        caja_window_slot_set_allow_stop (slot, FALSE);
+    }
+    g_object_unref (location);
+    g_object_unref (dir_location);
+}
+
+
+void
+caja_zfs_bar_cancel_tasks (CajaWindow *window)
+{
+    if (CAJA_IS_WINDOW (window))
+    {
+        if (CAJA_IS_WINDOW_SLOT (window->details->active_pane->active_slot))
+        {
+            CajaDirectory *directory = NULL;
+            g_cancellable_cancel (window->details->active_pane->active_slot->find_zfs_snapshots_cancellable);
+            g_object_unref (window->details->active_pane->active_slot->find_zfs_snapshots_cancellable);
+            window->details->active_pane->active_slot->find_zfs_snapshots_cancellable = NULL;
+            directory = caja_directory_get (window->details->active_pane->active_slot->location);
+            if (directory)
+            {
+                caja_directory_cancel_restore_info (directory);
+                caja_directory_unref (directory);
+            }
+        }
+        if (CAJA_IS_NAVIGATION_WINDOW (window))
+        {
+            CajaZfsBar *bar = CAJA_ZFS_BAR (CAJA_NAVIGATION_WINDOW (window)->zfs_bar);
+            monitor_zfs_snap_directory_cancel (bar->priv->zfs_dir_monitor_data);
+            bar->priv->zfs_dir_monitor_data = NULL;
+        }
+    }
+}
+
+void
+caja_zfs_bar_hide (CajaZfsBar *bar)
+{
+    bar->priv->explicit_user_hide = TRUE;
+    close_clicked_callback (NULL, bar);
+}
+
+
+/* Display AND Scan */
+
+void
+caja_zfs_bar_display (CajaZfsBar *bar,
+                      CajaWindow *window,
+                      CajaDirectory *new_dir,
+                      GCancellable *cancellable)
+{
+    gboolean show = FALSE;
+    gboolean time_slider_enabled = g_settings_get_boolean (caja_preferences,
+                                                        CAJA_PREFERENCES_ENABLE_TIME_SLIDER);
+    gboolean visible = gtk_widget_get_visible (GTK_WIDGET (bar));
+    gboolean enable_button = FALSE;
+    gboolean do_scan = FALSE;
+    gboolean do_cancel = FALSE;
+
+
+    /* if bar visible
+     *    if feature disabled
+     *      close bar
+     *      disable button
+     *    if in root dir
+     *      enable button
+     * else
+     *    if bar was previously displayed and in same tab
+     *       if in snapshot
+     *         redisplay
+     *         re-align
+     *         disable button
+     *       if root dir
+     *         redisplay
+     *         re-align
+     *         enable button
+     *       else
+     *          scan
+     *    else
+     *      if feature enabled
+     *        scan
+     *        disable button
+     *
+     *
+     *  NOTE : action_restore_callback display bar when enabled
+     */
+
+    if (visible)
+    {
+        if (!time_slider_enabled)
+        {
+            close_clicked_callback (NULL, bar);
+            return;
+        }
+        if (new_dir == bar->priv->dir)
+            enable_button = TRUE;
+        if (caja_directory_is_a_snapshot_dir_of (new_dir, bar->priv->dir) || new_dir == bar->priv->dir)
+        {
+            show = TRUE;
+            do_cancel = TRUE;
+            enable_button = TRUE;
+        }
+        else
+            do_scan = TRUE;
+    }
+    else
+    { /* bar is not visible */
+        CajaWindowSlot *slot = caja_window_get_active_slot (window);
+
+        if (bar->priv->is_setup && slot == bar->priv->slot && time_slider_enabled) /* check if we can redisplay the bar */
+        {
+            if (caja_directory_is_a_snapshot_dir_of (new_dir, bar->priv->dir) && !bar->priv->explicit_user_hide)
+                show = TRUE;
+
+            if (bar->priv->explicit_user_hide)
+                enable_button = FALSE;
+
+            if (new_dir == bar->priv->dir)
+            {
+                show = TRUE;
+                enable_button = TRUE;
+            }
+            else
+                do_scan = TRUE;
+        }
+        else
+        { /* icon and throbber set is snapshot_data_ready */
+            if (time_slider_enabled)
+                do_scan = TRUE;
+        }
+
+    }
+
+    if (enable_button) /* if button enabled set the icon to normal */
+        caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (window), RESTORE_NORMAL);
+
+    caja_window_allow_restore (window, enable_button);
+
+
+    if (show)
+    {
+        gtk_widget_show (GTK_WIDGET (bar));
+        caja_zfs_set_snap (bar, new_dir);
+    }
+    else
+    {
+        gtk_widget_hide (GTK_WIDGET (bar));
+        if (bar->priv->action)
+            gtk_toggle_action_set_active (bar->priv->action, FALSE);
+    }
+
+    if (do_cancel)
+        g_cancellable_cancel (cancellable);
+
+    if (do_scan)
+    {
+        g_cancellable_reset (cancellable);
+        caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (window), RESTORE_SEARCH);
+        caja_window_slot_set_allow_stop (caja_window_get_active_slot (window), TRUE);
+        caja_directory_get_snapshots_async (new_dir,
+                snapshot_data_ready,
+                cancellable,
+                window);
+    }
+
+    /* {
+       GFile *file = caja_directory_get_location (new_dir);
+       char *path = g_file_get_path (file);
+
+       printf ("caja_zfs_bar_display %s\nenable_button : %s, show : %s, do_cancel : %s, do_scan : %s\n\n",
+       path,
+       enable_button ? "true" : "false",
+       show ? "true" : "false",
+       do_cancel ? "true" : "false",
+       do_scan ? "true" : "false");
+       g_free (path);
+       }*/
+
+}
+
+void
+caja_zfs_set_snap (CajaZfsBar *bar,
+                   CajaDirectory *dir)
+{
+    GList* match = NULL;
+    GList* snap_list = NULL;
+    gboolean in_snap = FALSE;
+    char real_path [PATH_MAX+1];
+    GFile *file;
+    char* path;
+    int pos;
+    int set_pos = -2;
+
+    if (!bar->priv->is_setup)
+        return;
+
+    file = caja_directory_get_location (dir);
+    path = g_file_get_path (file);
+    ts_realpath (path, real_path);
+
+
+    if (ts_is_in_remote_backup (real_path))
+    {
+    /*FIXME: we do not have it */
+        //mate_vfs_init();
+        g_object_ref (dir);
+    }
+
+    in_snap = ts_is_in_snapshot (real_path);
+
+    if (in_snap)
+    {
+        snap_list = caja_directory_get_snapshots (bar->priv->dir);
+        match = g_list_find_custom (snap_list, real_path, (GCompareFunc)match_func);
+    }
+    g_free (path);
+    g_object_unref (file);
+
+    timescale_set_position (TIMESCALE (bar->priv->scale), match ? ((ZfsDataSet*)match->data)->mountpoint : NULL);
+    update_delete_or_snap_button (bar, in_snap);
+
+    /*  printf ("caja_zfs_set_snap current_path %s real_path %s match %s\n", bar->priv->current_path, real_path,
+        match ? "found" : "not found");*/
+
+    if (bar->priv->current_path && (strcmp (bar->priv->current_path, real_path) != 0))
+    {
+        bar->priv->set_only = TRUE;
+        bar->priv->set_only = FALSE;
+
+    }
+}
+
+static void
+snapshot_data_ready_from_change (CajaDirectory *dir,
+                                 GCancellable  *cancellable,
+                                 gpointer       callback_data)
+{
+    CajaZfsBar *bar = CAJA_ZFS_BAR (callback_data);
+
+    snapshot_data_ready (dir, cancellable, bar->priv->slot->pane->window);
+    update_range (bar);
+    gtk_widget_set_sensitive (bar->priv->scale, TRUE);
+}
+
+static void
+zfs_dir_change_callback (ZfsSnapDirMonitor *monitor_data,
+                         CajaZfsBar *bar)
+{
+    gtk_widget_set_sensitive (bar->priv->scale, FALSE);
+
+    g_cancellable_reset (bar->priv->slot->find_zfs_snapshots_cancellable);
+    caja_navigation_window_set_restore_icon (CAJA_NAVIGATION_WINDOW (bar->priv->slot->pane->window), RESTORE_SEARCH);
+    caja_window_slot_set_allow_stop (bar->priv->slot, TRUE);
+    caja_directory_get_snapshots_async (bar->priv->dir,
+            snapshot_data_ready_from_change,
+            bar->priv->slot->find_zfs_snapshots_cancellable,
+            bar);
+}
+
+static char*
+get_backup_dir (GList *snaplist)
+{
+    GList *tmp = snaplist;
+
+    while (tmp)
+    {
+        ZfsDataSet *snap = (ZfsDataSet*) tmp->data;
+        if (snap->type == 0)
+        {
+            char **root_split = NULL;
+            char *result = NULL;
+            root_split = g_strsplit (snap->mountpoint, snap->name, 2);
+            /*printf (" name: %s\n mountpoint: %s\n mtime_str :%s\n space used : %s\n size in kilobytes : %f\n",
+              snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str, snap->used_space); */
+            result = g_strdup (root_split[0]);
+            if (root_split)
+                g_strfreev (root_split);
+            return result;
+        }
+        tmp = tmp->next;
+    }
+    return NULL;
+}
+
+void
+caja_zfs_bar_setup (CajaZfsBar* bar,
+                    CajaDirectory *dir,
+                    CajaWindowSlot *active_slot,
+                    GtkToggleAction* action)
+{
+    GFile *file;
+    char *path, *zfs_dir, *backup_dir = NULL;
+    bar->priv->dir = dir;
+    g_object_ref (dir);
+    bar->priv->slot = active_slot;
+    g_object_ref (active_slot);
+
+    bar->priv->action = action;
+    set_scale_range (bar, TRUE);
+    bar->priv->is_setup = TRUE;
+    bar->priv->explicit_user_hide = FALSE;
+
+    file = caja_directory_get_location (dir);
+    path = g_file_get_path (file);
+    zfs_dir = ts_get_snapshot_dir (path);
+    backup_dir = get_backup_dir (caja_directory_get_snapshots (dir));
+
+    bar->priv->zfs_dir_monitor_data = monitor_zfs_snap_directory (zfs_dir,
+            backup_dir,
+            (ZfsDirChangeCallback) zfs_dir_change_callback,
+            bar);
+    caja_zfs_set_snap (bar, dir);
+    g_free (path);
+    g_free (zfs_dir);
+    if (backup_dir)
+        g_free(backup_dir);
+    g_object_unref (file);
+}
+
+static void
+zfs_bar_show_column (GtkWidget *widget, gpointer user_data)
+{
+    char **visible_columns;
+    gboolean restore_col_visible = FALSE;
+    int i = 0;
+    GPtrArray *ret = NULL;
+
+    visible_columns = g_settings_get_strv (caja_list_view_preferences,
+                                     CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+
+    ret = g_ptr_array_new ();
+
+    /* convert visible_columns in ptr array without "restore_info" */
+    while (visible_columns[i])
+    {
+        if (strcmp (visible_columns [i], "restore_info") == 0)
+        {
+            restore_col_visible = TRUE;
+            break;
+        }
+        else
+            g_ptr_array_add (ret, g_strdup (visible_columns [i]));
+        i++;
+    }
+
+    g_strfreev (visible_columns);
+
+    if (restore_col_visible)
+    {
+        if (!gtk_widget_get_visible (widget)) /* hide bar remove pref */
+        {
+            char **col_array;
+            g_ptr_array_add (ret, NULL);
+            col_array = (char **)g_ptr_array_free (ret, FALSE);
+            g_settings_set_strv (caja_list_view_preferences,
+                             CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+                                 (const char * const *)col_array);
+            g_strfreev (col_array);
+            ret = NULL;
+        }
+    }
+    else
+    {
+        if (gtk_widget_get_visible (widget))
+        {
+            char **col_array;
+            g_ptr_array_add (ret,strdup ("restore_info"));
+            g_ptr_array_add (ret,NULL);
+            col_array = (char **)g_ptr_array_free (ret, FALSE);
+            g_settings_set_strv (caja_list_view_preferences,
+                             CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS,
+                                 (const char * const *)col_array);
+            g_strfreev (col_array);
+            ret = NULL;
+        }
+
+    }
+
+    if (ret)
+        g_ptr_array_free (ret, TRUE);
+
+}
+
+static void
+zfs_bar_hidden (GtkWidget *widget, gpointer user_data)
+{
+    CajaZfsBar *bar = CAJA_ZFS_BAR (user_data);
+    monitor_zfs_snap_directory_cancel (bar->priv->zfs_dir_monitor_data);
+    bar->priv->zfs_dir_monitor_data = NULL;
+    caja_directory_cancel_restore_info (bar->priv->dir);
+}
+
+GtkWidget *
+caja_zfs_bar_new ()
+{
+    GObject *bar;
+    CajaZfsBar *zfs_bar;
+
+    bar = g_object_new (CAJA_TYPE_ZFS_BAR, NULL);
+
+    g_signal_connect_object (bar, "show", G_CALLBACK (zfs_bar_show_column), bar, 0);
+    g_signal_connect_object (bar, "hide", G_CALLBACK (zfs_bar_show_column), bar, 0);
+    g_signal_connect_object (bar, "hide", G_CALLBACK (zfs_bar_hidden), bar, 0);
+
+    zfs_bar_show_column (GTK_WIDGET (bar), NULL);
+
+    ts_is_restore_column_enabled_init ();
+
+    zfs_bar = CAJA_ZFS_BAR (bar);
+
+    return GTK_WIDGET (bar);
+}
+
components/desktop/mate/caja/patches/Archiv/02-time-slider-part2.patch.org
components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1
New file
@@ -0,0 +1,10 @@
--- caja-1.28.0/src/caja-window-slot.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-slot.h    2024-02-26 08:42:41.082428896 +0100
@@ -113,6 +113,7 @@
     gpointer open_callback_user_data;
     GCancellable *find_mount_cancellable;
+    GCancellable *find_zfs_snapshots_cancellable;
     gboolean visible;
 };
components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2
New file
@@ -0,0 +1,16 @@
--- caja-1.28.0/src/caja-window-slot.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-slot.c    2024-02-26 08:42:41.082244159 +0100
@@ -686,6 +686,13 @@
         slot->find_mount_cancellable = NULL;
     }
+    if (slot->find_zfs_snapshots_cancellable)
+    {
+        g_cancellable_cancel (slot->find_zfs_snapshots_cancellable);
+        g_object_unref (slot->find_zfs_snapshots_cancellable);
+        slot->find_zfs_snapshots_cancellable = NULL;
+    }
+
     slot->pane = NULL;
     g_free (slot->title);
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1
New file
@@ -0,0 +1,10 @@
--- caja-1.28.0/src/caja-navigation-window-ui.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window-ui.xml    2024-02-26 08:42:41.080238291 +0100
@@ -63,6 +63,7 @@
     <toolitem name="Up" action="Up"/>
     <toolitem name="Stop" action="Stop"/>
     <toolitem name="Reload" action="Reload"/>
+    <toolitem name="Restore" action="Restore"/>
     <separator/>
     <toolitem name="Home" action="Home"/>
     <toolitem name="Computer" action="Go to Computer"/>
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2
New file
@@ -0,0 +1,31 @@
--- caja-1.28.0/src/caja-navigation-window.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window.h    2024-02-26 09:27:56.105335195 +0100
@@ -67,10 +67,18 @@
     /** UI stuff **/
     CajaSidePane *sidebar;
+    GtkWidget    *zfs_bar;
+
     /* Current views stuff */
     GList *sidebar_panels;
 };
+typedef enum {
+  RESTORE_NORMAL,
+  RESTORE_SEARCH,
+  RESTORE_NO
+} CajaNavigationRestoreIconType;
+
 struct _CajaNavigationWindowClass
 {
     CajaWindowClass parent_spot;
@@ -91,6 +99,9 @@
 void     caja_navigation_window_hide_sidebar         (CajaNavigationWindow *window);
 void     caja_navigation_window_show_sidebar         (CajaNavigationWindow *window);
 gboolean caja_navigation_window_sidebar_showing      (CajaNavigationWindow *window);
+gboolean Caja_navigation_window_zfs_bar_showing      (CajaNavigationWindow *window);
+void     Caja_navigation_window_set_restore_icon     (CajaNavigationWindow* window,
+                                                      CajaNavigationRestoreIconType type);
 void     caja_navigation_window_add_sidebar_panel    (CajaNavigationWindow *window,
         CajaSidebar          *sidebar_panel);
 void     caja_navigation_window_remove_sidebar_panel (CajaNavigationWindow *window,
components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3
New file
@@ -0,0 +1,142 @@
--- caja-1.28.0/src/caja-navigation-window.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-navigation-window.c    2024-02-26 08:42:41.080677444 +0100
@@ -71,6 +71,7 @@
 #include "caja-notebook.h"
 #include "caja-window-manage-views.h"
 #include "caja-navigation-window-pane.h"
+#include "caja-zfs-bar.h"
 #define MAX_TITLE_LENGTH 180
@@ -107,6 +108,13 @@
 };
 static void
+restore_pref_changed (CajaWindow *window)
+{
+    g_assert (CAJA_IS_WINDOW (window));
+    caja_window_reload (window, FALSE);
+}
+
+static void
 caja_navigation_window_init (CajaNavigationWindow *window)
 {
     GtkUIManager *ui_manager;
@@ -167,6 +175,16 @@
     ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
     toolbar = gtk_ui_manager_get_widget (ui_manager, "/Toolbar");
+
+    /* add custom icon */
+    caja_navigation_window_set_restore_icon (window, RESTORE_SEARCH);
+
+    /* add preference callback */
+    g_signal_connect_swapped (caja_desktop_preferences,
+                              g_strconcat ("changed::", CAJA_PREFERENCES_ENABLE_TIME_SLIDER, NULL),
+                              G_CALLBACK (restore_pref_changed),
+                              (gpointer) window);
+
     gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
     window->details->toolbar = toolbar;
     gtk_widget_set_hexpand (toolbar, TRUE);
@@ -183,6 +201,12 @@
     caja_navigation_window_allow_back (window, FALSE);
     caja_navigation_window_allow_forward (window, FALSE);
+    window->zfs_bar = caja_zfs_bar_new ();
+
+    gtk_grid_attach(GTK_GRID (CAJA_WINDOW (window)->details->grid),
+                      window->zfs_bar,
+                      0, 2, 1, 1);
+
     g_signal_connect_swapped (caja_preferences,
                               "changed::" CAJA_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY,
                               G_CALLBACK(always_use_location_entry_changed),
@@ -348,6 +372,59 @@
     }
 }
+void
+caja_navigation_window_set_restore_icon (CajaNavigationWindow* window,
+                                             CajaNavigationRestoreIconType type)
+{
+    static gboolean init = 0;
+    static GdkPixbuf *normal = NULL;
+    static GdkPixbuf *search = NULL;
+    static GdkPixbuf *no = NULL;
+    GdkPixbuf *pb = NULL;
+    GtkWidget *image = NULL;
+    GtkAction* action = gtk_ui_manager_get_action (caja_window_get_ui_manager (CAJA_WINDOW (window)), "/Toolbar/Restore");
+
+    if (!init)
+    {
+        char *path = caja_pixmap_file ("restore.png");
+        normal = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        path = caja_pixmap_file ("restore-search.png");
+        search = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        path = caja_pixmap_file ("restore-no.png");
+        no = gdk_pixbuf_new_from_file (path, NULL);
+        g_free (path);
+        init = TRUE;
+    }
+
+    switch (type)
+    {
+      case RESTORE_NORMAL :
+        pb = normal;
+        break;
+      case RESTORE_SEARCH:
+        pb = search;
+        break;
+      case RESTORE_NO:
+        pb = no;
+        break;
+    }
+
+    image = gtk_image_new_from_pixbuf (pb);
+    g_object_ref (image);
+    gtk_widget_show (image);
+    GSList *tmp = gtk_action_get_proxies (action);
+    for (tmp; tmp ; tmp = tmp->next)
+    {
+        GtkWidget *proxy = (GtkWidget *)tmp->data;
+        if (GTK_IS_TOOL_BUTTON (proxy))
+        {
+            gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (proxy), image);
+        }
+    }
+}
+
 static void
 side_pane_close_requested_callback (GtkWidget *widget,
                                     gpointer user_data)
@@ -1207,6 +1284,7 @@
 static void
 real_window_close (CajaWindow *window)
 {
+    caja_zfs_bar_cancel_tasks (window);
     caja_navigation_window_save_geometry (CAJA_NAVIGATION_WINDOW (window));
 }
@@ -1428,6 +1506,19 @@
     caja_navigation_window_update_split_view_actions_sensitivity (window);
 }
+
+gboolean
+caja_navigation_window_zfs_bar_showing (CajaNavigationWindow *window)
+{
+    g_return_val_if_fail (CAJA_IS_NAVIGATION_WINDOW (window), FALSE);
+
+    if (window->zfs_bar != NULL)
+    {
+        return gtk_widget_get_visible(GTK_WIDGET (window->zfs_bar));
+    }
+    return FALSE;
+}
+
 gboolean
 caja_navigation_window_split_view_showing (CajaNavigationWindow *window)
 {
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1
New file
@@ -0,0 +1,17 @@
--- caja-1.28.0/libcaja-private/caja-column-utilities.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-column-utilities.c    2024-02-26 08:42:41.074153382 +0100
@@ -182,6 +182,14 @@
     caja_module_extension_list_free (providers);
+    columns = g_list_append (columns,
+                         g_object_new (CAJA_TYPE_COLUMN,
+                                           "name", "restore_info",
+                                           "attribute", "restore_info",
+                                           "label", _("Restore information"),
+                                           "description", _("Restore information of the file."),
+                                           NULL));
+
     return columns;
 }
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10
New file
@@ -0,0 +1,12 @@
--- caja-1.28.0/libcaja-private/caja-global-preferences.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-global-preferences.h    2024-02-26 08:42:41.078422380 +0100
@@ -70,6 +70,9 @@
 #define CAJA_PREFERENCES_USE_IEC_UNITS            "use-iec-units"
 #define CAJA_PREFERENCES_SHOW_ICONS_IN_LIST_VIEW    "show-icons-in-list-view"
+/* Time slider */
+#define CAJA_PREFERENCES_ENABLE_TIME_SLIDER             "enable-time-slider"
+
 /* Mouse */
 #define CAJA_PREFERENCES_MOUSE_USE_EXTRA_BUTTONS     "mouse-use-extra-buttons"
 #define CAJA_PREFERENCES_MOUSE_FORWARD_BUTTON        "mouse-forward-button"
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11
New file
@@ -0,0 +1,1242 @@
--- caja-1.28.0/libcaja-private/caja-zfs.c.orig    2024-02-26 08:42:41.078919123 +0100
+++ caja-1.28.0/libcaja-private/caja-zfs.c    2024-02-26 08:42:41.078857872 +0100
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+
+#include <stdio.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "caja-zfs.h"
+#include <time.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdint.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <eel/eel-glib-extensions.h>
+#include <sys/mnttab.h>
+#include <sys/mkdev.h>
+#include <libscf.h>
+#include <dirent.h>
+#include <sys/utsname.h>
+#include  "caja-global-preferences.h"
+#define ZFS_SNAPSHOT_DIR ".zfs/snapshot/"
+#define ZFS_BACKUP_DIR ".time-slider/rsync"
+
+#ifndef ZFS_MAXNAMELEN
+#ifdef ZFS_MAX_DATASET_NAME_LEN
+#define ZFS_MAXNAMELEN ZFS_MAX_DATASET_NAME_LEN
+#else
+#define ZFS_MAXNAMELEN 256
+#endif
+#endif
+
+
+char* ts_realpath (char * dir, char *resolved_name)
+{
+  char real_dir[PATH_MAX+1];
+  char real_path[PATH_MAX+1];
+  gboolean  found = FALSE;
+  struct stat64 dir_stat64;
+  char *result;
+
+  result = realpath(dir, real_dir);
+
+  if (!result)
+    return NULL;
+
+  if (stat64 (real_dir, &dir_stat64) == 0)
+    {
+      if (strcmp (dir_stat64.st_fstype, "lofs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          if (strcmp (mtab.mnt_fstype, "lofs") == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+            {
+              if (strcmp (real_dir, mtab.mnt_mountp) == 0)
+            strcpy (real_path, mtab.mnt_special);
+              else
+            {
+              gchar **split;
+              split = g_strsplit (real_dir, mtab.mnt_mountp, 2);
+              /*split 2nd part contains path without mount point */
+              g_snprintf (real_path,sizeof(real_path),"%s%s",mtab.mnt_special,split[1]);
+              g_strfreev (split);
+            }
+              found = TRUE;
+              break;
+            }
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+  if (found)
+      return strcpy (resolved_name, real_path);
+  else
+      return strcpy (resolved_name, real_dir);
+}
+
+static void ts_set_snapshot_used_space (zfs_handle_t *zhp, ZfsDataSet *snap)
+{
+  gchar buf[ZFS_MAXNAMELEN];
+  if (zfs_prop_get(zhp, ZFS_PROP_USED, buf, sizeof (buf), NULL, NULL, 0, B_FALSE) == 0)
+    {
+      char unit[10];
+      char format_float[5] = "%f%s";
+      char format_int[5] = "%d%s";
+      char *format = format_int;
+      int   used_space_int = 0;
+      gboolean success = FALSE;
+
+      snap->used_space_str = g_strdup (buf);
+
+      if (strchr (buf, '.'))
+    {
+      format = format_float;
+      if (sscanf(buf, format,&snap->used_space,unit) == 2)
+        success = TRUE;
+    }
+      else
+    {
+      if (sscanf(buf, format,&used_space_int,unit) == 2)
+        {
+          success = TRUE;
+          snap->used_space = (float) used_space_int;
+        }
+    }
+      if (strcmp (buf, "0") == 0)
+    {
+      g_free (snap->used_space_str);
+      snap->used_space_str = g_strdup ("0 K");
+      success = TRUE;
+    }
+
+      if (success)
+    {
+      if (strcmp (unit, "M") == 0)
+        snap->used_space *= 1024;
+      if (strcmp (unit, "G") == 0)
+        snap->used_space *= 1024 * 1024;
+    }
+      else
+    {
+      g_free (snap->used_space_str);
+      /* SUN_BRANDING */
+      snap->used_space_str = g_strdup (_("Unknown"));
+    }
+    }
+  else
+    {
+      g_free (snap->used_space_str);
+      /* SUN_BRANDING */
+      snap->used_space_str = g_strdup (_("Unknown"));
+    }
+}
+
+static void ts_set_snapshot_mtime_and_time_diff (zfs_handle_t *zhp, ZfsDataSet *snap)
+{
+  GDate now;
+  GDate then;
+  time_t time_now;
+  gint days_diff;
+  const gchar *format;
+  gchar *locale_format = NULL;
+  gchar buf[ZFS_MAXNAMELEN];
+  gchar *date_str = NULL;
+
+  if (zfs_prop_get(zhp, ZFS_PROP_CREATION, buf, sizeof (buf), NULL, NULL, 0, B_TRUE) == 0)
+    {
+      struct tm tms;
+
+      sscanf (buf, "%llu", &snap->mtime);
+      snap->mtime_str = caja_date_as_string (snap->mtime, FALSE);
+    }
+
+}
+
+void print_snap_list (char *dir, GList *snap_list)
+{
+  GList *tmp;
+  printf ("list of snapshots for %s :\n", dir);
+  for (tmp = snap_list; tmp->next; tmp = tmp->next)
+    {
+      ZfsDataSet *snap = (ZfsDataSet*) tmp->data;
+      printf (" name: %s\n mountpoint: %s\n mtime_str :%s\n space used : %s\n size in kilobytes : %f\n",
+          snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str, snap->used_space);
+
+    }
+  printf ("\n");
+}
+
+static GString *
+dump_zds (ZfsDataSet *zds)
+{
+  GString *msg;
+  gchar *type;
+
+  if (!zds)
+    return NULL;
+
+  msg = g_string_new ("");
+  g_string_printf (msg,
+           "\tname: %s\n"
+           "\tmountpoint: %s\n"
+           "\ttype: %s\n",
+           zds->name,zds->mountpoint, zfs_type_to_name(zds->type));
+  if (zds->snapshots)
+    {
+      GList *tmp;
+      g_string_append_printf(msg,"\tsnapshots :\n");
+      for (tmp=zds->snapshots;tmp;tmp = tmp->next)
+    {
+      ZfsDataSet *tmp_zds= (ZfsDataSet*) tmp->data;
+      g_string_append_printf (msg,"\t\tname: %s\n\t\tpath: %s\n",
+                  tmp_zds->name,
+                  tmp_zds->mountpoint);
+    }
+    }
+  g_string_append_printf (msg, "\n");
+  return msg;
+}
+
+
+static void
+dump_sds (SearchDataSet *sds)
+{
+  GString *msg;
+  gchar *type;
+  GList *tmp;
+
+  if (!sds)
+    {
+      printf ("Search DataSet is empty\n");
+      return;
+    }
+
+  msg = g_string_new ("");
+  g_string_printf (msg, "DDS Dump:\n"
+           "\tsearched_path: %s\n",
+           sds->searched_path);
+
+  g_string_append_printf (msg, "Zfs Data set :\n");
+  for (tmp=sds->datasets;tmp;tmp=tmp->next)
+    {
+      GString * zds_dump = dump_zds ((ZfsDataSet *)tmp->data);
+      g_string_append_printf (msg,"%s",zds_dump->str);
+      g_string_free (zds_dump, TRUE);
+    }
+  g_string_append_printf (msg, "\n");
+  printf ("%s", msg->str);
+  g_string_free (msg, TRUE);
+}
+
+static ZfsDataSet*
+ts_new_zfs_dataset (SearchDataSet* sds)
+{
+    ZfsDataSet *zds;
+    zds = g_new0 (ZfsDataSet, 1);
+    zds->search_dataset = sds;
+    return zds;
+}
+
+void
+ts_free_zfs_dataset (ZfsDataSet* zds)
+{
+    if (!zds)
+      return;
+    if (zds->name)
+      g_free (zds->name);
+    if (zds->mountpoint)
+      g_free (zds->mountpoint);
+    if (zds->mtime_str)
+      g_free (zds->mtime_str);
+    if (zds->used_space_str)
+      g_free (zds->used_space_str);
+
+    if (zds->snapshots)
+      {
+        GList *tmp;
+        for (tmp = zds->snapshots;tmp;tmp = tmp->next)
+          ts_free_zfs_dataset ((ZfsDataSet*)tmp->data);
+      }
+    g_free (zds);
+}
+
+static SearchDataSet *
+ts_new_search_dataset (GCancellable *cancel)
+{
+    SearchDataSet *sds;
+    sds = g_new0 (SearchDataSet, 1);
+    sds->cancel = cancel;
+    return sds;
+}
+static void
+ts_free_search_dataset (SearchDataSet *sds)
+{
+    if (!sds)
+      return;
+    if (sds->searched_path)
+      g_free (sds->searched_path);
+    if (sds->mountpoint)
+      g_free (sds->mountpoint);
+    if (sds->datasets)
+      {
+        GList *tmp;
+        for (tmp = sds->datasets;tmp;tmp = tmp->next)
+          ts_free_zfs_dataset ((ZfsDataSet*)tmp->data);
+      }
+    g_free (sds);
+}
+
+static char* construct_check_snapshot_path (SearchDataSet *sds, char* mountpoint, const char *name, char *searched_path)
+{
+  gchar *result = NULL;
+  gchar **split;
+  gchar **split2;
+
+  gchar *snap_name = NULL;
+  gchar *remaining_path = NULL;
+
+  /* get the snapshot name part pool@snap-name we are only interested in snap-name split[1] */
+  split = g_strsplit (name,"@",2);
+  /* get the path after the mountpoint */
+  split2 = g_strsplit (searched_path, mountpoint, 2);
+
+  if (split && split[1])
+      snap_name = split[1];
+
+  if (split2 && split2[1])
+      remaining_path = split2[1];
+
+/*  printf ("mountpoint : %s \nname : %s \nsearched_path: %s\n", mountpoint, name, searched_path);
+  printf ("split %s at @ = [%s] [%s]\n", name, split[0],split[1]);
+  printf ("split %s at [%s] = [%s] [%s]\n", searched_path, mountpoint, split2[0],split2[1]);
+  printf ("%s/.zfs/snapshot/%s/%s\n\n", mountpoint, split[1], split2[1]);*/
+
+  if (snap_name && remaining_path)
+    if (strcmp(mountpoint, "/") == 0)
+      result = g_strdup_printf ("/.zfs/snapshot/%s/%s", snap_name, remaining_path);
+    else
+      result = g_strdup_printf ("%s/.zfs/snapshot/%s/%s", mountpoint, snap_name, remaining_path);
+
+  g_strfreev (split);
+  g_strfreev (split2);
+
+  /* don't test for file presence if searched path is the same as the mount point */
+  if (sds->searched_path_match_mp)
+      return result;
+
+  if (result && g_file_test (result, G_FILE_TEST_IS_DIR))
+      {
+    char real_dir[PATH_MAX+1];
+    if (!ts_realpath(result, real_dir))
+      {
+        g_free (result);
+        result = NULL;
+      }
+    else
+      {
+        g_free (result);
+        result = g_strdup (real_dir);
+      }
+    return result;
+      }
+
+  g_free (result);
+  return NULL;
+}
+
+static int
+snapshot_callback (zfs_handle_t *zhp, void *data)
+{
+  ZfsDataSet *main_zds = (ZfsDataSet*) data;
+
+  /* only add snapshot dir that exist */
+
+  if (zfs_get_type (zhp) == ZFS_TYPE_SNAPSHOT && !g_cancellable_is_cancelled (main_zds->search_dataset->cancel))
+    {
+      const char* name = zfs_get_name (zhp);
+      char *snap_path = construct_check_snapshot_path (main_zds->search_dataset,
+                               main_zds->mountpoint,
+                               name,
+                               main_zds->search_dataset->searched_path);
+      if (snap_path)
+    {
+      ZfsDataSet *zds = ts_new_zfs_dataset (main_zds->search_dataset);
+      zds->name = g_strdup (name);
+      zds->type = ZFS_TYPE_SNAPSHOT;
+      zds->mountpoint = snap_path;
+      ts_set_snapshot_mtime_and_time_diff (zhp, zds);
+      ts_set_snapshot_used_space (zhp, zds);
+      main_zds->snapshots = g_list_append (main_zds->snapshots,zds);
+    }
+    }
+  zfs_close (zhp);
+  return 0;
+}
+
+
+static struct mnttab *
+mygetmntent(FILE *f)
+{
+  static struct mnttab mt;
+  int status;
+
+  if ((status = getmntent(f, &mt)) == 0)
+    return (&mt);
+
+  return (NULL);
+}
+
+static char *
+is_fs_mounted (const char *fs_name)
+{
+  FILE           *mnttab;
+  struct mnttab    *mntp;
+
+
+  mnttab = fopen (MNTTAB,"r");
+
+  while ((mntp = mygetmntent(mnttab)) != NULL)
+    {
+      if (mntp->mnt_fstype == (char *)0 || strcmp(mntp->mnt_fstype, "zfs") != 0)
+    continue;
+      if (strcmp (mntp->mnt_special, fs_name) == 0)
+    {
+      fclose (mnttab);
+      return g_strdup (mntp->mnt_mountp);
+    }
+  }
+  fclose (mnttab);
+  return NULL;
+}
+
+static char* rsync_get_smf_dir()
+{
+  char data_store[MAXPATHLEN];
+
+  int retval = -1;
+
+  scf_handle_t    *handle = NULL;
+  scf_scope_t    *sc = NULL;
+  scf_service_t    *svc = NULL;
+  scf_instance_t *inst = NULL;
+  scf_propertygroup_t    *pg = NULL;
+  scf_property_t    *prop = NULL;
+  scf_value_t    *value = NULL;
+  scf_iter_t    *value_iter = NULL;
+
+
+  /* connect to the current SMF global repository */
+  handle = scf_handle_create(SCF_VERSION);
+
+  /* allocate scf resources */
+  sc = scf_scope_create(handle);
+  svc = scf_service_create(handle);
+  inst = scf_instance_create (handle);
+  pg = scf_pg_create(handle);
+  prop = scf_property_create(handle);
+  value = scf_value_create(handle);
+  value_iter = scf_iter_create(handle);
+
+  char *result = NULL;
+
+  /* if failed to allocate resources, exit */
+  if (handle == NULL || sc == NULL || svc == NULL || pg == NULL ||
+      prop == NULL || value == NULL || value_iter == NULL) {
+    /* scf handles allocation failed. */
+    goto out;
+  }
+
+  /* bind scf handle to the running svc.configd daemon */
+  if (scf_handle_bind(handle) == -1) {
+    /* scf binding failed. */
+    goto out;
+  }
+
+  /* get the scope of the localhost in the current repository */
+  if (scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) == -1) {
+    /* Getting scf scope failed.*/
+    goto out;
+  }
+
+  /* get the service within the scope */
+  if (scf_scope_get_service(sc, "application/time-slider/plugin", svc) == -1) {
+    /* failed getting service */
+    goto out;
+  }
+
+  /* get the instance within the service */
+  if (scf_service_get_instance(svc, "rsync", inst) == -1)
+    goto out;
+
+
+  /* get the property group within the instance */
+  if (scf_instance_get_pg(inst, "rsync", pg) == -1) {
+      /* Getting property group failed.  */
+    goto out;
+  }
+
+  /*
+   * Now get the properties.
+   */
+  if (scf_pg_get_property(pg, "target_dir", prop) == -1) {
+    goto out;
+  }
+
+  if (scf_property_get_value(prop, value) == -1) {
+    goto out;
+  }
+
+  data_store[0] = 0;
+  if (scf_value_get_astring(value, data_store, MAXPATHLEN) == -1) {
+    goto out;
+  }
+  else {
+    result = strdup (data_store);
+  }
+
+out:
+  /* destroy scf pointers */
+  if (value != NULL)
+    scf_value_destroy(value);
+  if (value_iter != NULL)
+    scf_iter_destroy(value_iter);
+  if (prop != NULL)
+    scf_property_destroy(prop);
+  if (pg != NULL)
+    scf_pg_destroy(pg);
+  if (inst != NULL)
+    scf_instance_destroy (inst);
+  if (svc != NULL)
+    scf_service_destroy(svc);
+  if (sc != NULL)
+    scf_scope_destroy(sc);
+  if (handle != NULL)
+    scf_handle_destroy(handle);
+
+  return result;
+}
+
+static char *rsync_get_dir (zfs_handle_t *zhp)
+{
+  nvlist_t *propval;
+
+  if (nvlist_lookup_nvlist(zfs_get_user_props(zhp),
+               "org.opensolaris:time-slider-rsync", &propval) == 0)
+    {
+      boolean_t ret_bool = FALSE;
+      char *strval;
+      char *dir;
+      nvlist_lookup_string(propval, ZPROP_VALUE, &strval);
+
+      if (strcmp (strval, "true") == 0)
+    {
+      dir = rsync_get_smf_dir ();
+      if (dir)
+        return dir;
+    }
+    }
+  return NULL;
+}
+
+void sync_backups_add (zfs_handle_t *zhp, ZfsDataSet *main_zds)
+{
+  char *rsync_dir = rsync_get_dir (zhp);
+  DIR *d;
+  struct dirent *dir;
+  char *fs_rsync_dir;
+  struct utsname machine;
+
+  if (!rsync_dir)
+    return;
+
+  /* format SMF backup dir , TIMESLIDER, nodename from uname, path, .time-slider/rsync */
+  if (uname (&machine) == -1)
+    return;
+
+  fs_rsync_dir = g_strdup_printf ("%s/TIMESLIDER/%s/%s/%s/",
+                  rsync_dir,
+                  machine.nodename,
+                  main_zds->name,
+                  ZFS_BACKUP_DIR);
+
+  if (!g_file_test (fs_rsync_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_free (rsync_dir);
+      g_free (fs_rsync_dir);
+      return;
+    }
+
+  d = opendir (fs_rsync_dir);
+
+  if (!d)
+    {
+      g_free (rsync_dir);
+      g_free (fs_rsync_dir);
+      return;
+    }
+
+  while ((dir = readdir (d)))
+    {
+      if (strstr (dir->d_name, "zfs-auto-snap_"))
+    { /* got a snap copy dir */
+      char **comma_split = NULL;
+      char **freq_split = NULL;
+      struct tm tms;
+      ZfsDataSet *zds = NULL;
+
+      /* extract creation time from dir name */
+      comma_split = g_strsplit (dir->d_name, "_", 2);
+      /* printf ("comma_split[1] = %s\n", comma_split[1]); */
+      freq_split = g_strsplit (comma_split[1], "-", 2);
+      /* printf ("freq_split[1] = %s\n", freq_split[1]);  */
+
+      /* parse time string */
+      if (strptime (freq_split[1], "%Y-%m-%d-%Hh%M", &tms) != NULL)
+        {
+          zds = ts_new_zfs_dataset (main_zds->search_dataset);
+          zds->name = g_strdup (dir->d_name);
+          zds->type = 0;
+          zds->mountpoint = g_strdup_printf ("%s%s/", fs_rsync_dir, dir->d_name);
+          zds->mtime = mktime (&tms);
+          zds->mtime_str =  caja_date_as_string (zds->mtime, FALSE);
+          zds->used_space_str = g_strdup (_("Separate Backup"));
+          main_zds->snapshots = g_list_append (main_zds->snapshots,zds);
+          /* printf ("in sync_backups_add adding %s %s\n", zds->name, zds->mountpoint); */
+        }
+      if (comma_split)
+        g_strfreev (comma_split);
+      if (freq_split)
+        g_strfreev (freq_split);
+    }
+    }
+
+  closedir (d);
+  g_free (rsync_dir);
+}
+
+static int
+zfs_callback (zfs_handle_t *zhp, void *data)
+{
+  char buf[ZFS_MAXPROPLEN];
+  char mounted[ZFS_MAXPROPLEN];
+  SearchDataSet *sds = (SearchDataSet*) data;
+
+  if (sds->match_found)
+    {
+      zfs_close (zhp);
+      return 0;
+    }
+
+  if (zfs_get_type (zhp) & sds->type & !g_cancellable_is_cancelled (sds->cancel))
+    {
+/*      struct timespec ts;
+      ts.tv_sec = 3;
+      ts.tv_nsec = 100000000;
+      nanosleep (&ts, NULL);*/
+
+      if (sds->prop >= ZFS_PROP_TYPE && sds->prop < ZFS_NUM_PROPS)
+    {
+      zfs_prop_get(zhp, sds->prop, buf, sizeof (buf), NULL, NULL,  0, TRUE);
+
+      zfs_prop_get(zhp, ZFS_PROP_MOUNTED, mounted, sizeof (mounted), NULL, NULL,  0, TRUE);
+
+      if ((strcmp (sds->mountpoint, buf) == 0) && (strcmp (mounted, "yes") == 0))
+        {
+          ZfsDataSet *zds = ts_new_zfs_dataset (sds);
+          zds->type = zfs_get_type (zhp);
+          zds->name = g_strdup (zfs_get_name(zhp));
+          zds->mountpoint = g_strdup (buf);
+          zfs_iter_snapshots (zhp, B_FALSE, snapshot_callback, zds);
+          sync_backups_add (zhp, zds);
+          sds->datasets = g_list_append (sds->datasets, zds);
+          sds->match_found = TRUE;
+        }
+      else if (strcmp ("legacy", buf) == 0)
+        { /* parse /etc/mnttab to get the mount point */
+          char *mountp = is_fs_mounted (zfs_get_name(zhp));
+          if (mountp)
+        {
+          if (strcmp (sds->mountpoint, mountp) == 0)
+            {
+              ZfsDataSet *zds = ts_new_zfs_dataset (sds);
+              zds->type = zfs_get_type (zhp);
+              zds->name = g_strdup (zfs_get_name(zhp));
+              zds->mountpoint = mountp;
+              zfs_iter_snapshots (zhp, B_FALSE, snapshot_callback, zds);
+              sync_backups_add (zhp, zds);
+              sds->datasets = g_list_append (sds->datasets, zds);
+              sds->match_found = TRUE;
+            }
+          else
+            g_free (mountp);
+        }
+        }
+    }
+      if (!sds->match_found)
+    zfs_iter_filesystems (zhp, zfs_callback, sds);
+    }
+  zfs_close (zhp);
+  return 0;
+}
+
+static SearchDataSet *
+ts_get_data_from_mountpoint (const char* searched_path, const char *mountpoint, GCancellable *cancel)
+{
+  static libzfs_handle_t *zfs_handle = NULL;
+  SearchDataSet *sds;
+
+  sds = ts_new_search_dataset (cancel);
+
+  sds->prop = ZFS_PROP_MOUNTPOINT;
+  sds->type = ZFS_TYPE_FILESYSTEM;
+  sds->searched_path = g_strdup (searched_path);
+  sds->mountpoint = g_strdup (mountpoint);
+
+  if (strcmp (searched_path, mountpoint) == 0)
+    sds->searched_path_match_mp = TRUE;
+
+  if (!zfs_handle)
+    {
+      if ((zfs_handle = libzfs_init()) == NULL) {
+    g_warning ("internal error: failed to initialize ZFS library\n");
+    ts_free_search_dataset (sds);
+    return NULL;
+      }
+    }
+  zfs_iter_root (zfs_handle, zfs_callback, sds);
+
+  return sds;
+}
+static gint
+snap_sort_by_age (gconstpointer a,
+          gconstpointer b)
+{
+  const ZfsDataSet *snap1 = a;
+  const ZfsDataSet *snap2 = b;
+
+  if (snap1->mtime == snap2->mtime)
+    return 0;
+  if (snap1->mtime < snap2->mtime)
+    return -1;
+  if (snap1->mtime > snap2->mtime)
+    return 1;
+
+}
+
+char*
+ts_get_zfs_filesystem (char *dir)
+{
+  char real_dir[PATH_MAX+1];
+  char filesystem[PATH_MAX+1];
+  gboolean  found_fs= FALSE;
+  struct stat64 dir_stat64;
+
+  if (!ts_realpath(dir, real_dir))
+    {
+      return NULL;
+    }
+    if (stat64 (real_dir, &dir_stat64) == 0)
+    { /* check is fs is zfs */
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+
+      /* get mount point */
+
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+        {
+          strcpy (filesystem, mtab.mnt_special);
+          found_fs = TRUE;
+          break;
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+    if (found_fs)
+      return g_strdup(filesystem);
+
+    return NULL;
+}
+
+static char * get_zfs_mountpoint (char *dir)
+{
+  char real_dir[PATH_MAX+1];
+  char mountpoint[PATH_MAX+1];
+  gboolean  found_mount_point = FALSE;
+  struct stat64 dir_stat64;
+
+  if (!ts_realpath(dir, real_dir))
+    {
+      return NULL;
+    }
+    if (stat64 (real_dir, &dir_stat64) == 0)
+    { /* check is fs is zfs */
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+    {
+      FILE            *fp;
+      struct extmnttab   mtab;
+      int             status;
+
+      /* get mount point */
+
+      fp = fopen (MNTTAB,"r");
+
+      resetmnttab(fp);
+      while ((status = getextmntent(fp, &mtab, sizeof (struct extmnttab))) == 0)
+        {
+          dev_t dev = NODEV;
+          dev = makedev(mtab.mnt_major, mtab.mnt_minor);
+          if (dev == dir_stat64.st_dev)
+        {
+          strcpy (mountpoint, mtab.mnt_mountp);
+          found_mount_point = TRUE;
+          break;
+        }
+        }
+      (void) fclose(fp);
+    }
+    }
+    if (found_mount_point)
+      return g_strdup(mountpoint);
+
+    return NULL;
+}
+
+
+char *ts_get_snapshot_dir (char *dir)
+{
+  char *zfs_dir = get_zfs_mountpoint (dir);
+  if (zfs_dir)
+    {
+      char *snapshot_dir = g_strdup_printf ("%s/.zfs/snapshot", zfs_dir);
+      g_free (zfs_dir);
+      return snapshot_dir;
+    }
+  else
+    return NULL;
+}
+
+
+
+static void ts_get_snapshots_for_dir (GSimpleAsyncResult *res,
+                      GObject            *object,
+                      GCancellable       *cancellable)
+{
+  char *mountpoint = NULL;
+  char real_dir[PATH_MAX+1];
+  SearchDataSet *sds;
+  GList* snap_result = NULL;
+  GFile *file = G_FILE (object);
+  char *dir = g_file_get_path (file);
+
+  mountpoint = get_zfs_mountpoint (dir);
+
+
+  if (!mountpoint)
+    {
+      g_simple_async_result_set_op_res_gpointer (res, snap_result, (GDestroyNotify) NULL);
+      g_free (dir);
+      return;
+    }
+
+  ts_realpath(dir, real_dir);
+
+  sds = ts_get_data_from_mountpoint (real_dir, mountpoint, cancellable);
+
+  g_free (mountpoint);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      /* printf ("ts_get_snapshots_for_dir %s cancelled\n", dir); */
+      if (sds)
+    {
+      ts_free_search_dataset (sds);
+      sds = NULL;
+    }
+    }
+
+  if (sds)
+    {
+      GList *tmp;
+      for (tmp=sds->datasets;tmp;tmp=tmp->next)
+    {
+      ZfsDataSet *zds = (ZfsDataSet*) tmp->data;
+      if (zds->snapshots)
+        {
+          snap_result = g_list_concat (snap_result, zds->snapshots);
+          zds->snapshots = NULL;
+        }
+    }
+      ts_free_search_dataset (sds);
+    }
+
+  if (snap_result)
+    {
+      snap_result = g_list_sort (snap_result, (GCompareFunc)snap_sort_by_age);
+      /* print_snap_list (dir, snap_result);  */
+    }
+
+  g_free (dir);
+  g_simple_async_result_set_op_res_gpointer (res, snap_result, (GDestroyNotify) NULL);
+}
+
+
+GList *ts_get_snapshots_for_dir_async (GFile *file,
+                       GAsyncReadyCallback result_ready,
+                       GCancellable *cancel,
+                       gpointer  user_data)
+{
+   GSimpleAsyncResult *res;
+
+   res = g_simple_async_result_new (G_OBJECT (file), result_ready, user_data, (gpointer) ts_get_snapshots_for_dir);
+   g_simple_async_result_run_in_thread (res, ts_get_snapshots_for_dir, G_PRIORITY_DEFAULT, cancel);
+   return NULL;
+}
+
+
+void ts_free_snapshots (GList *snaps)
+{
+  if (snaps)
+    {
+      GList *tmp;
+      for (tmp=snaps;tmp;tmp=tmp->next)
+    ts_free_zfs_dataset ((ZfsDataSet*) tmp->data);
+      g_list_free (snaps);
+    }
+}
+
+gboolean ts_is_in_remote_backup (char *str)
+{
+    if (str != NULL)
+    {
+      if (g_strrstr (str, ZFS_BACKUP_DIR))
+    return TRUE;
+    }
+  return FALSE;
+}
+
+
+gboolean ts_is_in_snapshot (char * str)
+{
+  if (str != NULL)
+    {
+      if (g_strrstr (str, ZFS_SNAPSHOT_DIR))
+    return TRUE;
+      if (g_strrstr (str, ZFS_BACKUP_DIR))
+    return TRUE;
+    }
+  return FALSE;
+}
+
+char* ts_remove_snapshot_dir (char *str)
+{
+  if (ts_is_in_snapshot (str))
+    {
+      char *snap_root;
+      char *zfs, *iter, point;
+      int count = 0;
+
+      /*remove .zfs/snapshot/blah/ */
+      zfs = g_strrstr (str, ZFS_SNAPSHOT_DIR);
+      iter = zfs;
+
+      if (iter)
+    {
+      iter += sizeof (ZFS_SNAPSHOT_DIR);
+      while (*iter != '/' && *iter != '\0')
+        iter++;
+
+      if (*iter == '/')
+        iter++;
+
+      point = *zfs;
+      *zfs = '\0';
+      snap_root = g_strdup_printf ("%s%s", str, iter);
+
+      *zfs = point;
+      return snap_root;
+    }
+    }
+  return NULL;
+}
+
+
+static gboolean restore_col_enabled = FALSE;
+
+gboolean
+ts_is_restore_column_enabled ()
+{
+  return restore_col_enabled;
+}
+
+void ts_is_restore_column_enabled_init ();
+
+static void
+visible_columns_changed (gpointer callback_data)
+{
+  ts_is_restore_column_enabled_init ();
+}
+
+
+void ts_is_restore_column_enabled_init ()
+{
+  char **visible_columns;
+  static gboolean init = FALSE;
+  int i = 0;
+
+  if (!init)
+  {
+      g_signal_connect_swapped ( caja_list_view_preferences,
+                                 g_strconcat ("changed::", CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS, NULL),
+                                 G_CALLBACK ( visible_columns_changed ),
+                                 NULL);
+      init = TRUE;
+  }
+
+  restore_col_enabled = FALSE;
+
+  visible_columns = g_settings_get_strv (caja_list_view_preferences,
+                                  CAJA_PREFERENCES_LIST_VIEW_DEFAULT_VISIBLE_COLUMNS);
+
+  while (visible_columns[i])
+    {
+      if (strcmp (visible_columns [i], "restore_info") == 0)
+    {
+      restore_col_enabled = TRUE;
+      break;
+    }
+      i++;
+    }
+  g_strfreev (visible_columns);
+}
+
+
+static GList * get_dir_entries (char *dir_path)
+{
+  const char *entry_name;
+  GDir *dir;
+  GList *dir_entries = NULL;
+  dir = g_dir_open (dir_path, 0, NULL);
+
+  while ((entry_name = g_dir_read_name (dir)) != NULL)
+    dir_entries = g_list_prepend (dir_entries, g_strdup (entry_name));
+
+  g_dir_close (dir);
+
+  return dir_entries;
+}
+
+static void free_dir_entries (GList *entries)
+{
+  g_list_foreach (entries, (GFunc)g_free, NULL);
+  g_list_free (entries);
+}
+
+static gboolean are_entries_identical (GList *old, GList *new)
+{
+  if (g_list_length (old) != g_list_length (new))
+    return FALSE;
+
+  for (old; old; old = old->next)
+    {
+      gboolean found = FALSE;
+      for (new; new; new = new->next)
+    {
+      if (strcmp (old->data, new->data) == 0)
+        {
+          found = TRUE;
+          break;
+        }
+    }
+      if (!found)
+    return FALSE;
+    }
+  return TRUE;
+}
+
+void monitor_zfs_snap_directory_cancel (ZfsSnapDirMonitor *monitor_data)
+{
+  if (monitor_data)
+    {
+      /* printf ("in monitor_zfs_snap_directory_cancel %s\n", monitor_data->path); */
+      g_source_remove (monitor_data->timeout_id);
+      free_dir_entries (monitor_data->entries);
+      g_free (monitor_data->path);
+      g_free (monitor_data);
+    }
+}
+
+static gboolean
+monitor_snap_dir (ZfsSnapDirMonitor *monitor_data)
+{
+  GList *new_entries;
+
+  if (!g_file_test (monitor_data->path, G_FILE_TEST_IS_DIR))
+    {
+      monitor_zfs_snap_directory_cancel (monitor_data);
+      return TRUE;
+    }
+
+  new_entries = get_dir_entries (monitor_data->path);
+
+  if (are_entries_identical (monitor_data->entries, new_entries))
+    {
+      free_dir_entries (new_entries);
+    }
+  else
+    {
+      free_dir_entries (monitor_data->entries);
+      monitor_data->entries = new_entries;
+      monitor_data->change_callback (monitor_data, monitor_data->user_data);
+    }
+
+  if (monitor_data->backup_path)
+    {
+      if (!g_file_test (monitor_data->backup_path, G_FILE_TEST_IS_DIR))
+    {
+      monitor_zfs_snap_directory_cancel (monitor_data);
+      return TRUE;
+    }
+
+      new_entries = get_dir_entries (monitor_data->backup_path);
+
+      if (are_entries_identical (monitor_data->backup_entries, new_entries))
+    {
+      free_dir_entries (new_entries);
+    }
+      else
+    {
+      free_dir_entries (monitor_data->backup_entries);
+      monitor_data->backup_entries = new_entries;
+      monitor_data->change_callback (monitor_data, monitor_data->user_data);
+    }
+    }
+  return TRUE;
+}
+
+
+ZfsSnapDirMonitor *monitor_zfs_snap_directory (char *path,
+                           char *backup_path,
+                           ZfsDirChangeCallback change_callback,
+                           gpointer data)
+{
+  ZfsSnapDirMonitor *monitor_data = g_new0 (ZfsSnapDirMonitor, 1);
+
+  /* printf ("start monitoring %s\n", path); */
+
+  monitor_data->path = g_strdup (path);
+  monitor_data->entries = get_dir_entries (path);
+  if (backup_path)
+    {
+      monitor_data->backup_path = g_strdup (backup_path);
+      monitor_data->backup_entries = get_dir_entries (backup_path);
+    }
+  monitor_data->change_callback = change_callback;
+  monitor_data->user_data = data;
+
+  monitor_data->timeout_id = g_timeout_add_seconds (5, (GSourceFunc)monitor_snap_dir, monitor_data);
+  return monitor_data;
+}
+
+char *
+ts_get_not_zfs_snapshot_dir (GFile *file)
+{
+  char tmp_path[PATH_MAX + 1];
+  gboolean found = FALSE;
+  gboolean end_path = FALSE;
+  GFile *d = g_file_get_parent(file);
+  GFile *tmp;
+  char *full_path = g_file_get_path (file);
+  char *stripped_path = g_file_get_path (d);
+  struct stat64 dir_stat64;
+
+  if (!full_path)
+     return NULL;
+
+  if (stat64 (full_path, &dir_stat64) == 0)
+    { /* check is fs is zfs if so don't try to check for nfs mounted .zfs dir*/
+      if (strcmp (dir_stat64.st_fstype, "zfs") == 0)
+        end_path = TRUE;
+    }
+
+  while (!found && !end_path)
+    {
+      g_snprintf (tmp_path, sizeof(tmp_path), "%s/.zfs/snapshot", stripped_path);
+      if (g_file_test (tmp_path, G_FILE_TEST_IS_DIR))
+        {
+          GList *entries = get_dir_entries (tmp_path);
+          if (entries != NULL)
+            {
+              char *after_snap_path = full_path + strlen (stripped_path);
+
+              for (entries; entries; entries = entries->next)
+                {
+                  char test_path[PATH_MAX +1];
+                  g_sprintf (test_path, "%s/%s/%s", tmp_path,
+                             entries->data,
+                             after_snap_path);
+                  if (g_file_test (test_path, G_FILE_TEST_EXISTS))
+                    {
+                      found = TRUE;
+                      break;
+                    }
+                }
+              free_dir_entries (entries);
+            }
+        }
+      tmp = d;
+      d = g_file_get_parent (tmp);
+      g_object_unref (tmp);
+      g_free (stripped_path);
+      stripped_path=NULL;
+      if (d == NULL)
+        {
+          end_path = TRUE;
+        }
+      else
+        {
+          stripped_path = g_file_get_path (d);
+        }
+    }
+
+  g_free (full_path);
+
+  if (stripped_path)
+    g_free (stripped_path);
+
+  if (found)
+    return g_strdup (tmp_path);
+  else
+    return NULL;
+
+}
+
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12
New file
@@ -0,0 +1,78 @@
--- caja-1.28.0/libcaja-private/caja-zfs.h.orig    2024-02-26 08:42:41.079103041 +0100
+++ caja-1.28.0/libcaja-private/caja-zfs.h    2024-02-26 08:42:41.079046173 +0100
@@ -0,0 +1,75 @@
+#ifndef CAJA_ZFS_H
+#define CAJA_ZFS_H
+
+#include <glib.h>
+#include <libzfs.h>
+#include <gio/gio.h>
+
+typedef struct
+{
+  zfs_type_t    type;
+  zfs_prop_t    prop;
+  char           *searched_path;
+  char         *mountpoint;
+  GList        *datasets;
+  GCancellable *cancel;
+  gboolean    match_found;
+  gboolean    searched_path_match_mp;
+
+} SearchDataSet;
+
+typedef struct
+{
+  char          *name;
+  char          *mountpoint;
+  char          *mtime_str;
+  time_t       mtime;
+  float           used_space;
+  char          *used_space_str;
+  zfs_type_t       type;
+  GList          *snapshots;
+  SearchDataSet      *search_dataset;
+} ZfsDataSet;
+
+
+GList *ts_get_snapshots_for_dir_async    (GFile *file,
+                     GAsyncReadyCallback result_ready,
+                     GCancellable *cancel,
+                     gpointer  user_data);
+void ts_free_snapshots            (GList *snaps);
+void ts_free_zfs_dataset        (ZfsDataSet* zds);
+
+gboolean ts_is_in_snapshot        (char * str);
+gboolean ts_is_in_remote_backup        (char *str);
+char* ts_remove_snapshot_dir        (char *str);
+char *ts_get_snapshot_dir        (char *dir);
+char *ts_get_zfs_filesystem        (char *dir);
+char * ts_get_not_zfs_snapshot_dir      (GFile *file);
+gboolean ts_is_restore_column_enabled    ();
+void ts_is_restore_column_enabled_init    ();
+void print_snap_list            (char *dir, GList *snap_list);
+char* ts_realpath            (char * dir, char *resolved_name);
+
+char *
+caja_date_as_string            (time_t time_raw, gboolean use_smallest);
+
+typedef void (*ZfsDirChangeCallback)    (gpointer monitor_data,
+                     gpointer user_data);
+
+typedef struct
+{
+  char    *        path;
+  GList            *entries;
+  char    *        backup_path;
+  GList            *backup_entries;
+  guint            timeout_id;
+  ZfsDirChangeCallback    change_callback;
+  gpointer        user_data;
+} ZfsSnapDirMonitor;
+
+void monitor_zfs_snap_directory_cancel (ZfsSnapDirMonitor *monitor_data);
+ZfsSnapDirMonitor *monitor_zfs_snap_directory (char            *path,
+                           char            *backup_path,
+                           ZfsDirChangeCallback change_callback,
+                           gpointer            data);
+#endif /* CAJA_ZFS_H */
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13
New file
@@ -0,0 +1,23 @@
--- caja-1.28.0/libcaja-private/Makefile.am.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/Makefile.am    2024-02-26 08:42:41.079261409 +0100
@@ -37,7 +37,10 @@
     $(top_builddir)/eel/libeel-2.la \
     $(top_builddir)/libcaja-extension/libcaja-extension.la \
     $(CORE_LIBS) \
-    -lnotify
+    $(ZFS_LIBS) \
+    $(SCF_LIBS)     \
+    $(NVPAIR_LIBS) \
+    -lnotify \
     $(NULL)
 libcaja_private_la_SOURCES = \
@@ -184,6 +187,8 @@
     caja-window-slot-info.h \
     caja-undostack-manager.c \
     caja-undostack-manager.h \
+    caja-zfs.c \
+    caja-zfs.h \
     $(NULL)
 nodist_libcaja_private_la_SOURCES =\
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14
New file
@@ -0,0 +1,14 @@
--- caja-1.28.0/libcaja-private/org.mate.caja.gschema.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/org.mate.caja.gschema.xml    2024-02-26 08:42:41.079490993 +0100
@@ -82,6 +82,11 @@
       <summary>Switch tabs with [ctrl] + [tab]</summary>
       <description>If true, it enables the ability to switch tabs using [ctrl + tab] and [ctrl + shift + tab].</description>
     </key>
+    <key name="enable-time-slider" type="b">
+      <default>true</default>
+      <summary>Enables the visualization of the ZFS snaphots timeline.</summary>
+      <description>If set to true, the visualization of the ZFS snapshots timeline is enabled.</description>
+    </key>
     <key name="exit-with-last-window" type="b">
       <default>false</default>
       <summary>Caja will exit when last window destroyed.</summary>
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2
New file
@@ -0,0 +1,57 @@
--- caja-1.28.0/libcaja-private/caja-directory-async.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory-async.c    2024-02-26 08:46:37.475055194 +0100
@@ -767,6 +767,11 @@
         REQUEST_SET_TYPE (request, REQUEST_FILESYSTEM_INFO);
     }
+    if (file_attributes & CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO)
+    {
+        REQUEST_SET_TYPE (request, REQUEST_RESTORE_INFO);
+    }
+
     return request;
 }
@@ -5161,6 +5166,19 @@
     }
 }
+void
+caja_directory_cancel_restore_info (CajaDirectory *directory)
+{
+    if (CAJA_IS_DIRECTORY (directory))
+    {
+        if (directory->details->restore_cancel)
+        {
+            g_cancellable_cancel (directory->details->restore_cancel);
+            directory->details->restore_cancel = NULL;
+        }
+    }
+}
+
 static void
 cancel_loading_attributes (CajaDirectory *directory,
                            CajaFileAttributes file_attributes)
@@ -5213,6 +5231,11 @@
         mount_cancel (directory);
     }
+    if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO))
+    {
+        caja_directory_cancel_restore_info (directory);
+    }
+
     caja_directory_async_state_changed (directory);
 }
@@ -5263,6 +5286,10 @@
     {
         cancel_mount_for_file (directory, file);
     }
+    if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO))
+    {
+        caja_directory_cancel_restore_info (directory);
+    }
     caja_directory_async_state_changed (directory);
 }
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3
New file
@@ -0,0 +1,21 @@
--- caja-1.28.0/libcaja-private/caja-directory-private.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory-private.h    2024-02-26 08:42:41.075455631 +0100
@@ -64,6 +64,7 @@
     REQUEST_THUMBNAIL,
     REQUEST_MOUNT,
     REQUEST_FILESYSTEM_INFO,
+    REQUEST_RESTORE_INFO,
     REQUEST_TYPE_LAST
 } RequestType;
@@ -144,6 +145,10 @@
     guint64 free_space; /* (guint)-1 for unknown */
     time_t free_space_read; /* The time free_space was updated, or 0 for never */
+
+    GCancellable *restore_cancel;
+    /* zfs snapshot info */
+    GList *zfs_snapshots;
 };
 CajaDirectory *caja_directory_get_existing                    (GFile                     *location);
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4
New file
@@ -0,0 +1,282 @@
--- caja-1.28.0/libcaja-private/caja-directory.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory.c    2024-02-26 08:42:41.075943421 +0100
@@ -40,6 +40,7 @@
 #include "caja-metadata.h"
 #include "caja-desktop-directory.h"
 #include "caja-vfs-directory.h"
+#include "caja-zfs.h"
 enum
 {
@@ -120,6 +121,8 @@
     directory->details->low_priority_queue = caja_file_queue_new ();
     directory->details->extension_queue = caja_file_queue_new ();
     directory->details->free_space = (guint64)-1;
+    directory->details->zfs_snapshots = NULL;
+    directory->details->restore_cancel = NULL;
 }
 CajaDirectory *
@@ -191,6 +194,16 @@
     g_assert (directory->details->file_list == NULL);
     g_hash_table_destroy (directory->details->file_hash);
+    if (directory->details->zfs_snapshots)
+    {
+        ts_free_snapshots (directory->details->zfs_snapshots);
+    }
+
+    if (directory->details->restore_cancel)
+    {
+      g_cancellable_cancel (directory->details->restore_cancel);
+    }
+
     caja_file_queue_destroy (directory->details->high_priority_queue);
     caja_file_queue_destroy (directory->details->low_priority_queue);
     caja_file_queue_destroy (directory->details->extension_queue);
@@ -219,6 +232,21 @@
     caja_file_list_free (files);
 }
+static gboolean
+time_slider_enabled = TRUE;
+
+gboolean
+caja_is_time_slider_enabled ()
+{
+    return time_slider_enabled;
+}
+
+static void time_slider_pref_changed_callback (gpointer callback_data)
+{
+    time_slider_enabled = g_settings_get_boolean (caja_preferences,
+                                               CAJA_PREFERENCES_ENABLE_TIME_SLIDER);
+}
+
 static void
 collect_all_directories (gpointer key, gpointer value, gpointer callback_data)
 {
@@ -454,6 +482,7 @@
 {
     CajaDirectory *directory;
     char *uri;
+    char *path;
     uri = g_file_get_uri (location);
@@ -472,6 +501,8 @@
     else
     {
         directory = CAJA_DIRECTORY (g_object_new (CAJA_TYPE_VFS_DIRECTORY, NULL));
+        path = g_file_get_path (location);
+        g_free (path);
     }
     set_directory_location (directory, location);
@@ -495,6 +526,206 @@
            g_file_is_native (directory->details->location);
 }
+typedef struct
+{
+    CajaDirectory    *dir;
+    GCancellable    *cancel;
+    TsReadyCallback  callback;
+    gpointer         callback_user_data;
+} QuerySnapshotsAsyncData;
+
+
+static void
+snapshot_list_ready_callback (GObject *source_object,
+        GAsyncResult *res,
+        gpointer user_data)
+{
+    GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+    QuerySnapshotsAsyncData *data = (QuerySnapshotsAsyncData*) user_data;
+
+    if (!g_cancellable_is_cancelled (data->cancel))
+    {
+        data->dir->details->zfs_snapshots = g_simple_async_result_get_op_res_gpointer (simple);
+    }
+
+    data->callback (data->dir, data->cancel, data->callback_user_data);
+}
+
+void
+caja_directory_get_snapshots_async (CajaDirectory *directory,
+        TsReadyCallback ready_callback,
+        GCancellable *cancel,
+        gpointer      callback_user_data)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->location == NULL)
+        return;
+
+    if (directory->details->zfs_snapshots)
+    {
+        ts_free_snapshots (directory->details->zfs_snapshots);
+        directory->details->zfs_snapshots = NULL;
+    }
+
+    if (caja_is_time_slider_enabled ())
+    {
+        QuerySnapshotsAsyncData *data;
+        data = g_new0 (QuerySnapshotsAsyncData,1);
+        data->dir = directory;
+        data->cancel = cancel;
+        data->callback = ready_callback;
+        data->callback_user_data = callback_user_data;
+
+        ts_get_snapshots_for_dir_async (directory->details->location,
+                snapshot_list_ready_callback,
+                cancel,
+                data);
+    }
+}
+
+gboolean
+caja_directory_has_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->zfs_snapshots)
+        return TRUE;
+
+    return FALSE;
+}
+
+int
+caja_directory_get_num_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    if (directory->details->zfs_snapshots)
+    {
+        int i = 0;
+        GList *tmp;
+        for (tmp = directory->details->zfs_snapshots;tmp;tmp = tmp->next)
+            i++;
+        return i;
+    }
+    return 0;
+}
+
+gboolean
+caja_directory_is_in_snapshot (CajaDirectory *directory)
+{
+    char *directory_uri;
+    gboolean result = FALSE;
+
+    g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), FALSE);
+
+    directory_uri = caja_directory_get_uri (directory);
+
+    result = ts_is_in_snapshot (directory_uri);
+
+    g_free (directory_uri);
+
+    return result;
+}
+
+GList *
+caja_directory_get_snapshots (CajaDirectory *directory)
+{
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    return directory->details->zfs_snapshots;
+}
+
+void
+caja_directory_remove_snapshot (CajaDirectory *directory,
+                                ZfsDataSet *snap)
+{
+    if (directory->details->zfs_snapshots)
+    {
+        directory->details->zfs_snapshots = g_list_remove (directory->details->zfs_snapshots, snap);
+        ts_free_zfs_dataset (snap);
+    }
+}
+
+/* return true if snapdir dir path is a dir or subdir of refdir */
+gboolean
+caja_directory_is_a_snapshot_dir_of (CajaDirectory *snapdir,
+                                     CajaDirectory *refdir)
+{
+
+    gboolean result = FALSE;
+
+    if (caja_directory_is_in_snapshot (snapdir))
+    {
+        char snapdir_root_real_path [PATH_MAX+1];
+        char refdir_real_path [PATH_MAX+1];
+        CajaDirectory *snapdir_root = caja_directory_get_snap_root (snapdir);
+        GFile *snapdir_root_file = caja_directory_get_location (snapdir_root);
+        GFile *refdir_file = caja_directory_get_location (refdir);
+        char* snapdir_root_path = g_file_get_path (snapdir_root_file);
+        char* refdir_path = g_file_get_path (refdir_file);
+
+        if (ts_realpath (snapdir_root_path, snapdir_root_real_path) &&
+                ts_realpath (refdir_path, refdir_real_path))
+        {
+            if (g_strrstr (snapdir_root_real_path,refdir_real_path))
+                result = TRUE;
+        }
+
+        g_free (snapdir_root_path);
+        g_free (refdir_path);
+        g_object_unref (snapdir_root_file);
+        g_object_unref (refdir_file);
+        g_object_unref (snapdir_root);
+    }
+
+    return result;
+}
+
+CajaDirectory *
+caja_directory_get_snap_root (CajaDirectory      *directory)
+{
+    char *directory_uri, *snap_root;
+    char *zfs, *iter;
+    int count = 0;
+    CajaDirectory *new_dir;
+
+    g_assert (CAJA_IS_DIRECTORY (directory));
+
+    directory_uri = caja_directory_get_uri (directory);
+
+
+    if (!caja_directory_is_in_snapshot (directory))
+    {
+        g_free (directory_uri);
+        return directory;
+    }
+
+    /*remove .zfs/snapshot/blah/ */
+    zfs = g_strrstr (directory_uri, ".zfs/snapshot/");
+    iter = zfs;
+
+    if (iter)
+    {
+        iter += sizeof (".zfs/snapshot/");
+        while (*iter != '/' && *iter != '\0')
+            iter++;
+
+        if (*iter == '/')
+            iter++;
+
+        *zfs = '\0';
+        snap_root = g_strdup_printf ("%s%s", directory_uri, iter);
+
+        *zfs = 'a';
+        g_free (directory_uri);
+        new_dir = caja_directory_get_by_uri (snap_root);
+        g_free (snap_root);
+        return new_dir;
+    }
+    return directory;
+}
+
 gboolean
 caja_directory_is_in_trash (CajaDirectory *directory)
 {
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5
New file
@@ -0,0 +1,44 @@
--- caja-1.28.0/libcaja-private/caja-directory.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-directory.h    2024-02-26 08:42:41.076213417 +0100
@@ -29,6 +29,7 @@
 #include <gio/gio.h>
 #include "caja-file-attributes.h"
+#include "caja-zfs.h"
 G_BEGIN_DECLS
@@ -215,6 +216,24 @@
 gboolean           caja_directory_is_in_trash              (CajaDirectory         *directory);
+/* ZFS snasphots management. */
+typedef void (*TsReadyCallback) (CajaDirectory *directory, GCancellable *cancellable, gpointer           callback_data);
+
+void               caja_directory_get_snapshots_async      (CajaDirectory         *directory,
+                                                            TsReadyCallback        ready_callback,
+                                                            GCancellable          *cancel,
+                                                            gpointer               callback_user_data);
+gboolean           caja_directory_has_snapshots            (CajaDirectory         *directory);
+gboolean           caja_directory_is_in_snapshot           (CajaDirectory         *directory);
+int                caja_directory_get_num_snapshots        (CajaDirectory         *directory);
+GList *            caja_directory_get_snapshots            (CajaDirectory         *directory);
+void               caja_directory_remove_snapshot          (CajaDirectory         *directory,
+                                                            ZfsDataSet            *snap);
+CajaDirectory *    caja_directory_get_snap_root            (CajaDirectory         *directory);
+gboolean           caja_directory_is_a_snapshot_dir_of     (CajaDirectory         *snapdir,
+                                                            CajaDirectory         *refdir);
+void               caja_directory_cancel_restore_info      (CajaDirectory         *directory);
+
 /* Return false if directory contains anything besides a Caja metafile.
  * Only valid if directory is monitored. Used by the Trash monitor.
  */
@@ -234,6 +253,8 @@
 gboolean           caja_directory_is_editable              (CajaDirectory         *directory);
+gboolean           caja_is_time_slider_enabled             ();
+
 G_END_DECLS
 #endif /* CAJA_DIRECTORY_H */
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6
New file
@@ -0,0 +1,10 @@
--- caja-1.28.0/libcaja-private/caja-file-attributes.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file-attributes.h    2024-02-26 08:42:41.076404101 +0100
@@ -42,6 +42,7 @@
     CAJA_FILE_ATTRIBUTE_THUMBNAIL = 1 << 8,
     CAJA_FILE_ATTRIBUTE_MOUNT = 1 << 9,
     CAJA_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 10,
+    CAJA_FILE_ATTRIBUTE_RESTORE_INFO = 1 << 12,
 } CajaFileAttributes;
 #endif /* CAJA_FILE_ATTRIBUTES_H */
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7
New file
@@ -0,0 +1,30 @@
--- caja-1.28.0/libcaja-private/caja-file-private.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file-private.h    2024-02-26 08:42:41.076641510 +0100
@@ -154,6 +154,13 @@
     /* Mount for mountpoint or the references GMount for a "mountable" */
     GMount *mount;
+    /* Time slider file difference information */
+    char *restore_info;
+
+    /* Snapshot directory for versions */
+    char *snapshot_directory;
+    GCancellable *has_snapshot_cancel;
+
     /* boolean fields: bitfield to save space, since there can be
            many CajaFile objects. */
@@ -201,6 +208,13 @@
     eel_boolean_bit is_thumbnailing               : 1;
+    eel_boolean_bit restore_info_is_up_to_date    : 1;
+    eel_boolean_bit restore_info_in_progress      : 1;
+
+    eel_boolean_bit has_snap_versions_is_up_to_date    : 1;
+    eel_boolean_bit has_snap_versions_in_progress      : 1;
+    eel_boolean_bit has_snap_versions                  : 1;
+
     /* TRUE if the file is open in a spatial window */
     eel_boolean_bit has_open_window               : 1;
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8
New file
@@ -0,0 +1,967 @@
--- caja-1.28.0/libcaja-private/caja-file.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file.c    2024-02-26 08:42:41.077938703 +0100
@@ -71,6 +71,7 @@
 #include "caja-ui-utilities.h"
 #include "caja-vfs-file.h"
 #include "caja-saved-search-file.h"
+#include "caja-zfs.h"
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
@@ -149,7 +150,8 @@
     attribute_where_q,
     attribute_link_target_q,
     attribute_volume_q,
-    attribute_free_space_q;
+    attribute_free_space_q,
+        attribute_restore_info_q;;
 static void     caja_file_info_iface_init                (CajaFileInfoIface *iface);
 static char *   caja_file_get_owner_as_string            (CajaFile          *file,
@@ -159,6 +161,7 @@
                                   GFileInfo             *info);
 static const char * caja_file_peek_display_name (CajaFile *file);
 static const char * caja_file_peek_display_name_collation_key (CajaFile *file);
+static void invalidate_restore_info (CajaFile *file);
 static void file_mount_unmounted (GMount *mount,  gpointer data);
 static void metadata_hash_free (GHashTable *hash);
@@ -497,6 +500,15 @@
     g_clear_pointer (&file->details->filesystem_id, g_ref_string_release);
     file->details->filesystem_id = NULL;
+        g_free (file->details->restore_info);
+        file->details->restore_info = NULL;
+        invalidate_restore_info (file);
+        g_free (file->details->snapshot_directory);
+        file->details->snapshot_directory = NULL;
+        file->details->has_snap_versions_in_progress = FALSE;
+        file->details->has_snap_versions_is_up_to_date = FALSE;
+        file->details->has_snap_versions = FALSE;
+
     clear_metadata (file);
 }
@@ -815,6 +827,11 @@
     g_free (file->details->activation_uri);
     g_free (file->details->compare_by_emblem_cache);
+        g_free (file->details->restore_info);
+        if (file->details->snapshot_directory) {
+                g_free (file->details->snapshot_directory);
+        }
+
     if (file->details->thumbnail) {
         g_object_unref (file->details->thumbnail);
     }
@@ -4835,6 +4852,242 @@
     NULL
 };
+/* Following code is copied from Rhythmbox rb-cut-and-paste-code.c */
+
+/* Legal conversion specifiers, as specified in the C standard. */
+#define C_STANDARD_STRFTIME_CHARACTERS "aAbBcdHIjmMpSUwWxXyYZ"
+#define C_STANDARD_NUMERIC_STRFTIME_CHARACTERS "dHIjmMSUwWyY"
+#define SUS_EXTENDED_STRFTIME_MODIFIERS "EO"
+
+/**
+ * eel_strdup_strftime:
+ *
+ * Cover for standard date-and-time-formatting routine strftime that returns
+ * a newly-allocated string of the correct size. The caller is responsible
+ * for g_free-ing the returned string.
+ *
+ * Besides the buffer management, there are two differences between this
+ * and the library strftime:
+ *
+ *   1) The modifiers "-" and "_" between a "%" and a numeric directive
+ *      are defined as for the GNU version of strftime. "-" means "do not
+ *      pad the field" and "_" means "pad with spaces instead of zeroes".
+ *   2) Non-ANSI extensions to strftime are flagged at runtime with a
+ *      warning, so it's easy to notice use of the extensions without
+ *      testing with multiple versions of the library.
+ *
+ * @format: format string to pass to strftime. See strftime documentation
+ * for details.
+ * @time_pieces: date/time, in struct format.
+ *
+ * Return value: Newly allocated string containing the formatted time.
+ **/
+
+static char *
+eel_strdup_strftime (const char *format, struct tm *time_pieces)
+{
+  g_autoptr(GString) string = NULL;
+  const char *remainder, *percent;
+  char code[4], buffer[512];
+  char *piece, *result;
+  g_autofree gchar *converted = NULL;
+  size_t string_length;
+  gboolean strip_leading_zeros, turn_leading_zeros_to_spaces;
+  char modifier;
+  int i;
+
+  /* Format could be translated, and contain UTF-8 chars,
+   * so convert to locale encoding which strftime uses */
+  converted = g_locale_from_utf8 (format, -1, NULL, NULL, NULL);
+  if (!converted)
+    converted = g_strdup (format);
+
+  string = g_string_new ("");
+  remainder = converted;
+
+  /* Walk from % character to % character. */
+  for (;;) {
+    percent = strchr (remainder, '%');
+    if (percent == NULL) {
+      g_string_append (string, remainder);
+      break;
+    }
+    g_string_append_len (string, remainder,
+                         percent - remainder);
+
+    /* Handle the "%" character. */
+    remainder = percent + 1;
+    switch (*remainder) {
+      case '-':
+        strip_leading_zeros = TRUE;
+        turn_leading_zeros_to_spaces = FALSE;
+        remainder++;
+        break;
+      case '_':
+        strip_leading_zeros = FALSE;
+        turn_leading_zeros_to_spaces = TRUE;
+        remainder++;
+        break;
+      case '%':
+        g_string_append_c (string, '%');
+        remainder++;
+        continue;
+      case '\0':
+        g_warning ("Trailing %% passed to eel_strdup_strftime");
+        g_string_append_c (string, '%');
+        continue;
+      default:
+        strip_leading_zeros = FALSE;
+        turn_leading_zeros_to_spaces = FALSE;
+        break;
+    }
+
+    modifier = 0;
+    if (strchr (SUS_EXTENDED_STRFTIME_MODIFIERS, *remainder) != NULL) {
+      modifier = *remainder;
+      remainder++;
+
+      if (*remainder == 0) {
+        g_warning ("Unfinished %%%c modifier passed to eel_strdup_strftime", modifier);
+        break;
+      }
+    }
+
+    if (strchr (C_STANDARD_STRFTIME_CHARACTERS, *remainder) == NULL) {
+      g_warning ("eel_strdup_strftime does not support "
+                 "non-standard escape code %%%c",
+                 *remainder);
+    }
+
+    /* Convert code to strftime format. We have a fixed
+     * limit here that each code can expand to a maximum
+     * of 512 bytes, which is probably OK. There's no
+     * limit on the total size of the result string.
+     */
+    i = 0;
+    code[i++] = '%';
+    if (modifier != 0) {
+#ifdef HAVE_STRFTIME_EXTENSION
+      code[i++] = modifier;
+#endif
+    }
+    code[i++] = *remainder;
+    code[i++] = '\0';
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+    /* Format string under control of caller, since this is a wrapper for strftime. */
+    string_length = strftime (buffer, sizeof (buffer),
+                              code, time_pieces);
+#pragma GCC diagnostic pop
+    if (string_length == 0) {
+      /* We could put a warning here, but there's no
+       * way to tell a successful conversion to
+       * empty string from a failure.
+       */
+      buffer[0] = '\0';
+    }
+
+    /* Strip leading zeros if requested. */
+    piece = buffer;
+    if (strip_leading_zeros || turn_leading_zeros_to_spaces) {
+      if (strchr (C_STANDARD_NUMERIC_STRFTIME_CHARACTERS, *remainder) == NULL) {
+        g_warning ("eel_strdup_strftime does not support "
+                   "modifier for non-numeric escape code %%%c%c",
+                   remainder[-1],
+                   *remainder);
+      }
+      if (*piece == '0') {
+        do {
+          piece++;
+        } while (*piece == '0');
+        if (!g_ascii_isdigit (*piece)) {
+          piece--;
+        }
+      }
+      if (turn_leading_zeros_to_spaces) {
+        memset (buffer, ' ', piece - buffer);
+        piece = buffer;
+      }
+    }
+    remainder++;
+
+    /* Add this piece. */
+    g_string_append (string, piece);
+  }
+
+  /* Convert the string back into utf-8. */
+  result = g_locale_to_utf8 (string->str, -1, NULL, NULL, NULL);
+
+  return result;
+}
+
+char *
+caja_date_as_string (time_t time_raw, gboolean use_smallest)
+{
+    struct tm *ttime;
+    const char **formats;
+    const char *width_template;
+    const char *format;
+    char *date_string;
+    char *result;
+    GDate *today;
+    GDate *date;
+    guint32 date_age;
+    int i;
+
+    ttime = localtime (&time_raw);
+
+    if (!use_smallest) {
+        if (date_format_pref == CAJA_DATE_FORMAT_LOCALE) {
+          return eel_strdup_strftime ("%c", ttime);
+        } else if (date_format_pref == CAJA_DATE_FORMAT_ISO) {
+          return eel_strdup_strftime ("%Y-%m-%d %H:%M:%S",ttime);
+        }
+    }
+
+    date = g_date_new ();
+    g_date_set_time_t (date, time_raw);
+
+    today = g_date_new ();
+    g_date_set_time_t (today, time (NULL));
+
+    /* Overflow results in a large number; fine for our purposes. */
+    date_age = (g_date_get_julian (today) -
+                g_date_get_julian (date));
+
+    g_date_free (date);
+    g_date_free (today);
+
+    /* Format varies depending on how old the date is. This minimizes
+     * the length (and thus clutter & complication) of typical dates
+     * while providing sufficient detail for recent dates to make
+     * them maximally understandable at a glance. Keep all format
+     * strings separate rather than combining bits & pieces for
+     * internationalization's sake.
+     */
+
+    if (date_age == 0) {
+        formats = TODAY_TIME_FORMATS;
+    } else if (date_age == 1) {
+        formats = YESTERDAY_TIME_FORMATS;
+    } else if (date_age < 7) {
+        formats = CURRENT_WEEK_TIME_FORMATS;
+    } else {
+        formats = CURRENT_WEEK_TIME_FORMATS;
+    }
+
+    if (!use_smallest)
+      format = _(formats[1]);
+    else
+      {
+        int i=0;
+        while (formats[i] != NULL)
+          i++;
+        format = _(formats[i-3]);
+      }
+    return eel_strdup_strftime (format, ttime);
+}
+
 static char *
 caja_file_fit_date_as_string (CajaFile *file,
                   CajaDateType date_type,
@@ -6608,6 +6861,9 @@
     if (attribute_q == attribute_free_space_q) {
         return caja_file_get_volume_free_space (file);
     }
+    if (attribute_q == attribute_restore_info_q) {
+                return caja_file_get_restore_info_async (file);
+        }
     extension_attribute = NULL;
@@ -7654,6 +7910,616 @@
 }
+
+gboolean
+caja_file_is_in_snapshot (CajaFile *file)
+{
+  char *file_uri = caja_file_get_uri (file);
+  gboolean result = ts_is_in_snapshot (file_uri);
+  g_free (file_uri);
+  return result;
+}
+
+static gboolean caja_file_in_snap_exist_in_current (CajaFile *file, GCancellable *cancel)
+{
+  /* get path without /.zfs/snapshot/blah/ */
+  /* test is file exist */
+  char *file_uri = caja_file_get_uri (file);
+  char *file_uri_without_snap = NULL;
+  gboolean result = FALSE;
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (file_uri);
+      return FALSE;
+    }
+
+  file_uri_without_snap = ts_remove_snapshot_dir (file_uri);
+
+  if (file_uri_without_snap)
+    {
+      GFile* root_file = g_file_new_for_uri (file_uri_without_snap);
+      char *path = g_file_get_path (root_file);
+
+      if (path)
+    {
+      result =  g_file_test (path, G_FILE_TEST_EXISTS);
+      g_free (path);
+    }
+      g_object_unref (root_file);
+      g_free (file_uri_without_snap);
+
+    }
+
+  g_free (file_uri);
+
+  return result;
+}
+
+
+char * caja_file_in_snapshot_get_info (CajaFile *file, GCancellable *cancel)
+{
+  char *info = NULL;
+  GFile *then_gfile = caja_file_get_location (file);
+  char *then_path = g_file_get_path (then_gfile);
+  g_object_unref (then_gfile);
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (then_gfile);
+      g_free (then_path);
+      return g_strdup ("cancelled");
+    }
+  if (then_path)
+    {
+      struct stat64 now;
+      struct stat64 then;
+      char *now_path = ts_remove_snapshot_dir (then_path);
+
+      if (lstat64 (now_path, &now) == 0)
+    {
+      if (lstat64 (then_path, &then) == 0)
+        {
+
+          if (now.st_mtime != then.st_mtime)
+        {
+          if (now.st_size == then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, same size as latest version"));
+          else if (now.st_size > then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, smaller than latest version"));
+          else if ( now.st_size < then.st_size)
+            /* SUN_BRANDING */
+            info = g_strdup (_("different date, bigger than latest version"));
+        }
+          else
+        /* SUN_BRANDING */
+        info = g_strdup (_("identical to latest version"));
+        }
+      else
+        info = g_strdup_printf ("FIXME no then %s", then_path);
+    }
+      else
+    /* SUN_BRANDING */
+    info = g_strdup (_("not present in latest version"));
+
+      g_free (now_path);
+      g_free (then_path);
+    }
+
+  return info;
+}
+
+static char * restore_string (char *str, GCancellable *cancel)
+{
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      g_free (str);
+      return g_strdup (_("unknown"));
+    }
+  else
+    return str;
+}
+
+gint time_cmp (time_t *a,
+           time_t *b)
+{
+  if (*a == *b)
+    return 0;
+  if (*a > *b)
+    return 1;
+  if (*a < *b)
+    return -1;
+
+}
+
+char *
+caja_file_get_num_snapshot_version (CajaFile *file,
+                                        GCancellable *cancel,
+                                        gboolean stop_at_first)
+{
+  GList *tmp = NULL;
+  GList *tmp2 = NULL;
+  GList *time = NULL;
+  time_t* now_time = NULL;
+  char *result = NULL;
+  int version = 0;
+  CajaFile *parent = NULL;
+  CajaDirectory *dir = NULL;
+  char *snapdir = NULL;
+
+  if (CAJA_IS_FILE (file))
+    {
+      parent = caja_file_get_parent (file);
+      if (parent)
+    {
+      dir = caja_directory_get_for_file (parent);
+      g_object_unref (parent);
+    }
+    }
+  if (dir)
+    {
+      struct stat64 now;
+      struct stat64 then;
+      char snap_name[PATH_MAX+1];
+      char *name = caja_file_get_name (file);
+
+      g_object_ref (dir);
+      tmp = caja_directory_get_snapshots (dir);
+
+      GFile *now_gfile = caja_file_get_location (file);
+      char *now_path = g_file_get_path (now_gfile);
+      g_object_unref (now_gfile);
+
+      if (now_path)
+    {
+      if (lstat64 (now_path, &now) != 0)
+        {
+          g_free (now_path);
+          g_object_unref (dir);
+          return NULL;
+        }
+    }
+
+      g_free (now_path);
+
+      time = NULL;
+
+      /* get list of mtime for all files in snapshots */
+
+      now_time = g_new0 (time_t, 1);
+      *now_time = now.st_mtim.tv_sec;
+      time = g_list_prepend (time, now_time);
+
+
+      for (tmp; tmp; tmp = tmp->next)
+    {
+      g_snprintf (snap_name, sizeof(snap_name), "%s/%s",
+             ((ZfsDataSet *) tmp->data)->mountpoint,
+             name);
+      if (g_cancellable_is_cancelled (cancel))
+        goto cancel;
+      if (lstat64 (snap_name, &then) == 0)
+        {
+          if (g_list_find_custom (time, &then.st_mtim.tv_sec, (GCompareFunc) time_cmp) == NULL)
+        { /*insert in list only is unique */
+          time_t* snap_time = g_new0 (time_t, 1);
+          *snap_time = then.st_mtim.tv_sec;
+          time = g_list_prepend (time, snap_time);
+                  if (stop_at_first)
+                    {
+                      snapdir = g_strdup (((ZfsDataSet *) tmp->data)->mountpoint);
+                      goto cancel;
+                    }
+        }
+        }
+
+    }
+cancel:
+      g_free (name);
+      g_object_unref (dir);
+    }
+
+
+  for (tmp = time; tmp; tmp = tmp->next)
+    {
+      g_free ((time_t*) tmp->data);
+      version++;
+    }
+
+  /* remove current version */
+  version--;
+
+  g_list_free (time);
+
+  if (version == 0)
+    {
+      if (stop_at_first)
+        return NULL;
+      else /*SUN_BRANDING*/
+        return restore_string (g_strdup_printf (_("no other version")), cancel);
+    }
+
+  if (stop_at_first)
+    return snapdir;
+  else
+    return restore_string (g_strdup_printf ("%d %s", version,
+                                            /* SUN_BRANDING */
+                                            version > 1 ? _("other versions") : /* SUN_BRANDING */ _("other version")),
+                           cancel);
+}
+
+static gboolean worker_thread_started = FALSE;
+
+typedef void (*ReadyCallback) (gpointer          data,
+                   GCancellable      *cancellable);
+typedef void (*WorkerFunction) (gpointer          data,
+                   GCancellable      *cancellable);
+typedef struct {
+  gpointer        data;
+  gpointer        return_data;
+  ReadyCallback        ready_callback;
+  WorkerFunction    worker_func;
+  GCancellable        *cancellable;
+} QueryData;
+
+static void
+caja_file_get_restore_info (gpointer data,
+                GCancellable       *cancellable)
+{
+  QueryData *qdata = (QueryData*) data;
+  CajaFile *file = CAJA_FILE (qdata->data);
+  char *result = NULL;
+
+  /*{
+    struct timespec ts;
+    ts.tv_sec = 1;
+    ts.tv_nsec = 0;
+    nanosleep (&ts, NULL);
+  }
+
+    {
+      GFile *f = caja_file_get_location (file);
+      char *path = g_file_get_uri (f);
+      printf ("start restore info for %s", path);
+      g_free (path);
+      g_object_unref (f);
+    }*/
+  if (!g_cancellable_is_cancelled (cancellable))
+    {
+
+      if (caja_file_is_directory (file))
+    {
+      CajaDirectory *dir = caja_directory_get_for_file (file);
+      g_object_ref (dir);
+      if (caja_directory_is_in_snapshot (dir))
+        {
+          if (!caja_file_in_snap_exist_in_current (file, cancellable))
+        /* SUN_BRANDING */
+        result = g_strdup (_("not present in latest version"));
+          else
+        /* SUN_BRANDING */
+        result = g_strdup (_("present in latest version"));
+        }
+      else
+        {
+          int version = caja_directory_get_num_snapshots (dir);
+
+          if (version == 0)
+        /* SUN_BRANDING */
+        result = g_strdup (_("no version"));
+          else
+        result = g_strdup_printf ("%d %s",version,
+                      /* SUN_BRANDING */
+                      version > 1 ? _("versions") : /* SUN_BRANDING */ _("version"));
+        }
+      g_object_unref (dir);
+    }
+      else
+    {
+      if (caja_file_is_in_snapshot (file))
+          result = caja_file_in_snapshot_get_info (file, cancellable);
+      else
+          result = caja_file_get_num_snapshot_version (file, cancellable, FALSE);
+    }
+    }
+
+/*    {
+      printf ("is %s\n", result);
+    }*/
+
+
+  qdata->return_data = restore_string (result, cancellable);
+}
+
+
+static void restore_information_ready_callback (gpointer data,
+                        GCancellable *cancellable)
+{
+  QueryData *qdata = (QueryData*) data;
+  CajaFile *file = (CajaFile*) qdata->data;
+  char *return_data = qdata->return_data;
+
+  if (!CAJA_IS_FILE (file))
+    return;
+
+  file->details->restore_info_in_progress = FALSE;
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      file->details->restore_info = g_strdup (_("unknown"));
+      invalidate_restore_info (file);
+      if (return_data)
+    g_free (return_data);
+    }
+  else
+    {
+      file->details->restore_info_is_up_to_date = TRUE;
+      file->details->restore_info = return_data;
+    }
+
+  caja_file_changed (file);
+  caja_file_unref (file);
+}
+
+
+static gboolean
+complete_in_idle_cb (gpointer data)
+{
+  QueryData *qdata = (QueryData*)data;
+  qdata->ready_callback (data, qdata->cancellable);
+  g_free (qdata);
+  return FALSE;
+}
+
+static void
+worker_queue_finished_callback (GObject *source_object,
+                GAsyncResult *res,
+                gpointer user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GCancellable *cancel = (GCancellable*) user_data;
+
+  worker_thread_started = FALSE;
+
+  if (g_cancellable_is_cancelled (cancel))
+    {
+      return;
+    }
+
+  g_simple_async_result_get_op_res_gpointer (simple);
+
+}
+
+static void
+worker_queue_func (GSimpleAsyncResult *res,
+           GObject            *object,
+           GCancellable       *cancellable)
+{
+  QueryData *data = NULL;
+
+  GTimeVal timeout;
+  GAsyncQueue *queue = (GAsyncQueue*) g_simple_async_result_get_op_res_gpointer (res);
+  g_async_queue_ref (queue);
+
+  g_get_current_time (&timeout);
+  g_time_val_add (&timeout, 3000000);
+
+  data = g_async_queue_timed_pop (queue, &timeout);
+
+  while (data)
+    {
+      GSource *source;
+
+      /* only call the worker fct if not cancel
+       * but execute ready function anyway */
+      if (!g_cancellable_is_cancelled (data->cancellable))
+    data->worker_func (data, data->cancellable);
+
+      /*call ready callback in main loop/thread */
+      source = g_idle_source_new ();
+      g_source_set_priority (source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (source, complete_in_idle_cb, data, NULL);
+      g_source_attach (source, NULL);
+      g_source_unref (source);
+
+      /* pop next one */
+      g_get_current_time (&timeout);
+      g_time_val_add (&timeout, 3000000);
+      data = g_async_queue_timed_pop (queue, &timeout);
+    }
+
+  g_async_queue_unref (queue);
+}
+
+char * caja_file_get_restore_info_async (CajaFile *file)
+{
+  if (!caja_is_time_slider_enabled ())
+    return NULL;
+
+  if (!ts_is_restore_column_enabled ())
+    return NULL;
+
+  if (file->details->restore_info_is_up_to_date)
+    {
+      /*if ( file->details->restore_info == NULL)
+    return g_strdup ("null cached info");*/
+      return g_strdup (file->details->restore_info);
+    }
+
+  if (file->details->restore_info_in_progress)
+    return g_strdup ("...");
+  else
+    {
+      static GAsyncQueue *queue = NULL;
+      QueryData *data  = NULL;
+
+      if (!file->details->directory)
+    return g_strdup ("no directory element\n");
+
+      if (!caja_directory_has_snapshots (file->details->directory) && !caja_file_is_in_snapshot (file))
+    return g_strdup ("doesn't have snap nor is in snap\n");
+
+      if (!file->details->directory->details->restore_cancel)
+    {
+      file->details->directory->details->restore_cancel = g_cancellable_new ();
+    }
+      else
+    {
+      if (g_cancellable_is_cancelled (file->details->directory->details->restore_cancel))
+        return NULL;
+    }
+
+      g_free (file->details->restore_info);
+      file->details->restore_info = NULL;
+      file->details->restore_info_in_progress = TRUE;
+
+      if (!queue)
+    queue = g_async_queue_new ();
+
+      data = g_new0 (QueryData, 1);
+      data->data = file;
+      caja_file_ref (file);
+      data->cancellable = file->details->directory->details->restore_cancel;
+      data->ready_callback = restore_information_ready_callback;
+      data->worker_func = caja_file_get_restore_info;
+
+      g_async_queue_push (queue, data);
+
+      if (!worker_thread_started)
+    {
+      GSimpleAsyncResult *res;
+      worker_thread_started = TRUE;
+
+      res = g_simple_async_result_new (G_OBJECT (file),
+                       worker_queue_finished_callback,
+                       NULL,
+                       (gpointer) worker_queue_func);
+
+      g_simple_async_result_set_op_res_gpointer (res, queue, NULL);
+      g_simple_async_result_run_in_thread (res,
+                           worker_queue_func,
+                           G_PRIORITY_DEFAULT,
+                           data->cancellable);
+    }
+
+      return g_strdup ("...");
+    }
+}
+
+HasSnapshotResult
+caja_file_has_snapshot_version (CajaFile *file)
+{
+    if (file->details->has_snap_versions_is_up_to_date)
+        return (file->details->has_snap_versions);
+    return UNKNOWN_STATE;
+}
+
+typedef struct {
+  CajaFile              *file;
+  GCancellable              *cancel;
+  FileHasSnapshotCallback    callback;
+  gpointer             callback_user_data;
+  char                      *snap_dir;
+} HasSnapshotAsyncData;
+
+typedef void (*HasSnapReadyCallback) (CajaDirectory *file,
+                                      GCancellable    *cancel,
+                                      gpointer           callback_data);
+
+
+static void has_snapshot_ready_callback (GObject *source_object,
+                                         GAsyncResult *res,
+                                         gpointer user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  HasSnapshotAsyncData *data = (HasSnapshotAsyncData*) user_data;
+
+  if (g_cancellable_is_cancelled (data->cancel))
+    {
+      data->file->details->has_snap_versions_in_progress = FALSE;
+      data->file->details->has_snap_versions_is_up_to_date = FALSE;
+      if (data->file->details->snapshot_directory)
+        g_free (data->file->details->snapshot_directory);
+
+      data->file->details->has_snapshot_cancel = NULL;
+    }
+  else
+    {
+      data->file->details->has_snap_versions_in_progress = FALSE;
+      data->file->details->has_snap_versions_is_up_to_date = TRUE;
+      if (data->file->details->snapshot_directory)
+        g_free (data->file->details->snapshot_directory);
+      data->file->details->snapshot_directory = g_simple_async_result_get_op_res_gpointer (simple);
+      if (data->file->details->snapshot_directory)
+        data->file->details->has_snap_versions = TRUE;
+      else
+        data->file->details->has_snap_versions = FALSE;
+    }
+  data->callback (data->callback_user_data);
+}
+char *
+caja_file_get_snapshot_dir (CajaFile *file)
+{
+  return file->details->snapshot_directory;
+}
+void caja_file_real_get_snapshot_version (GSimpleAsyncResult *res,
+                                  GObject            *object,
+                              GCancellable       *cancellable)
+{
+  CajaFile *file = CAJA_FILE (object);
+  char *snap_info = caja_file_get_num_snapshot_version (file, cancellable, TRUE);
+
+  if (!snap_info) /* scan for .zfs directory*/
+    snap_info = ts_get_not_zfs_snapshot_dir (caja_file_get_location (file));
+/*
+  {
+    struct timespec ts;
+    ts.tv_sec = 4;
+    ts.tv_nsec = 0;
+    nanosleep (&ts, NULL);
+  }
+*/
+  if (snap_info)
+    g_simple_async_result_set_op_res_gpointer (res, snap_info, (GDestroyNotify) NULL);
+  else
+    g_simple_async_result_set_op_res_gpointer (res, NULL, (GDestroyNotify) NULL);
+}
+
+void caja_file_get_snapshot_version (CajaFile *file,
+                                         FileHasSnapshotCallback callback,
+                                         GCancellable *cancel,
+                                         gpointer user_data)
+{
+    HasSnapshotAsyncData *data;
+    GSimpleAsyncResult *res;
+
+    if (file->details->has_snap_versions_in_progress)
+    {
+        g_cancellable_cancel(file->details->has_snapshot_cancel);
+        file->details->has_snapshot_cancel = NULL;
+        file->details->has_snap_versions_in_progress = FALSE;
+    }
+
+    file->details->has_snapshot_cancel = cancel;
+    file->details->has_snap_versions_in_progress = TRUE;
+    file->details->has_snap_versions_is_up_to_date  = FALSE;
+
+    data = g_new0 (HasSnapshotAsyncData, 1);
+    data->file = file;
+    data->cancel = cancel;
+    data->callback = callback;
+    data->callback_user_data = user_data;
+
+    res = g_simple_async_result_new (G_OBJECT (file),
+                                     has_snapshot_ready_callback,
+                                     data,
+                                    (gpointer) caja_file_real_get_snapshot_version);
+    g_simple_async_result_run_in_thread (res, caja_file_real_get_snapshot_version,
+                                         G_PRIORITY_DEFAULT, cancel);
+}
+
 void
 caja_file_mark_gone (CajaFile *file)
 {
@@ -7920,6 +8786,12 @@
     file->details->mount_is_up_to_date = FALSE;
 }
+static void
+invalidate_restore_info (CajaFile *file)
+{
+        file->details->restore_info_is_up_to_date = FALSE;
+}
+
 void
 caja_file_invalidate_extension_info_internal (CajaFile *file)
 {
@@ -7974,6 +8846,9 @@
     if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) {
         invalidate_thumbnail (file);
     }
+        if (REQUEST_WANTS_TYPE (request, REQUEST_RESTORE_INFO)) {
+                invalidate_restore_info (file);
+        }
     if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) {
         invalidate_mount (file);
     }
@@ -8054,7 +8929,8 @@
         CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT |
         CAJA_FILE_ATTRIBUTE_EXTENSION_INFO |
         CAJA_FILE_ATTRIBUTE_THUMBNAIL |
-        CAJA_FILE_ATTRIBUTE_MOUNT;
+        CAJA_FILE_ATTRIBUTE_MOUNT |
+                CAJA_FILE_ATTRIBUTE_RESTORE_INFO ;
 }
 void
@@ -8621,6 +9497,7 @@
     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_restore_info_q = g_quark_from_static_string ("restore_info");
     G_OBJECT_CLASS (class)->finalize = finalize;
     G_OBJECT_CLASS (class)->constructor = caja_file_constructor;
components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9
New file
@@ -0,0 +1,38 @@
--- caja-1.28.0/libcaja-private/caja-file.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/libcaja-private/caja-file.h    2024-02-26 08:42:41.078215891 +0100
@@ -194,6 +194,7 @@
         const char                     *mime_type);
 gboolean                caja_file_is_launchable                     (CajaFile                   *file);
 gboolean                caja_file_is_symbolic_link                  (CajaFile                   *file);
+gboolean                caja_file_is_in_snapshot                    (CajaFile                   *file);
 gboolean                caja_file_is_mountpoint                     (CajaFile                   *file);
 GMount *                caja_file_get_mount                         (CajaFile                   *file);
 char *                  caja_file_get_volume_free_space             (CajaFile                   *file);
@@ -250,6 +251,27 @@
 CajaFile *          caja_file_get_trash_original_file           (CajaFile                   *file);
+/* Time slider */
+char *                  caja_file_get_num_snapshot_version          (CajaFile                   *file,
+                                                                     GCancellable               *cancel,
+                                                                     gboolean                    stop_at_first);
+char *                  caja_file_get_restore_info_async            (CajaFile                   *file);
+
+typedef enum {
+    NO,
+    YES,
+    UNKNOWN_STATE
+} HasSnapshotResult;
+
+HasSnapshotResult       caja_file_has_snapshot_version              (CajaFile                   *file);
+char *                  caja_file_get_snapshot_dir                  (CajaFile                   *file);
+typedef void (*FileHasSnapshotCallback)    (gpointer user_data);
+
+void                    caja_file_get_snapshot_version              (CajaFile                   *file,
+                                                                     FileHasSnapshotCallback     callback,
+                                                                     GCancellable               *cancel,
+                                                                     gpointer                    user_data);
+
 /* Permissions. */
 gboolean                caja_file_can_get_permissions               (CajaFile                   *file);
 gboolean                caja_file_can_set_permissions               (CajaFile                   *file);
components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1
New file
@@ -0,0 +1,58 @@
--- caja-1.28.0/src/caja-window-menus.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/caja-window-menus.c    2024-02-26 08:42:41.082015281 +0100
@@ -57,6 +57,8 @@
 #include "caja-window-private.h"
 #include "caja-desktop-window.h"
 #include "caja-search-bar.h"
+#include "caja-navigation-window.h"
+#include "caja-zfs-bar.h"
 #define MENU_PATH_EXTENSION_ACTIONS                     "/MenuBar/File/Extension Actions"
 #define POPUP_PATH_EXTENSION_ACTIONS                     "/background/Before Zoom Items/Extension Actions"
@@ -370,6 +372,33 @@
 }
 static void
+action_restore_callback (GtkToggleAction *action,
+                         gpointer user_data)
+{
+    CajaWindowSlot *slot;
+    GFile *directory;
+    CajaDirectory *n_dir;
+    GtkWidget *bar = CAJA_NAVIGATION_WINDOW (user_data)->zfs_bar;
+
+    slot = caja_window_get_active_slot (CAJA_WINDOW (user_data));
+    directory = caja_window_slot_get_location (slot);
+    n_dir = caja_directory_get (directory);
+
+    if (gtk_toggle_action_get_active (action))
+    {
+        caja_zfs_bar_setup (CAJA_ZFS_BAR (bar), n_dir, slot, action);
+        gtk_widget_show (bar);
+    }
+    else
+    {
+        caja_zfs_bar_hide (CAJA_ZFS_BAR (bar));
+        caja_window_reload (CAJA_WINDOW (user_data), FALSE);
+    }
+    g_object_unref (n_dir);
+    g_object_unref (directory);
+}
+
+static void
 action_zoom_in_callback (GtkAction *action,
                          gpointer user_data)
 {
@@ -962,6 +991,12 @@
 static const GtkToggleActionEntry main_toggle_entries[] =
 {
+    /* name, stock id */         { "Restore", NULL,
+    /* label, accelerator */       N_("R_estore"), "<control>E",
+    /* tooltip */                  N_("Browse the current location snapshot history"),
+        G_CALLBACK (action_restore_callback),
+        FALSE
+    },
     /* name, icon name */        { "Show Hidden Files", NULL,
         /* label, accelerator */       N_("Show _Hidden Files"), "<control>H",
         /* tooltip */                  N_("Toggle the display of hidden files in the current window"),
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1
New file
@@ -0,0 +1,30 @@
--- caja-1.28.0/src/file-manager/caja-directory-view-ui.xml.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/caja-directory-view-ui.xml    2024-02-26 08:42:41.083540685 +0100
@@ -71,6 +71,9 @@
             <menuitem name="Duplicate" action="Duplicate"/>
             <menuitem name="Create Link" action="Create Link"/>
             <menuitem name="Rename" action="Rename"/>
+            <menuitem name="Restore to" action="Restore to"/>
+            <menuitem name="Snapshot now" action="Snap Now"/>
+            <menuitem name="Scanning...." action="View Snap"/>
             <menu action="CopyToMenu">
                 <menuitem name="Copy to next pane" action="Copy to next pane"/>
                 <menuitem name="Copy to Home" action="Copy to Home"/>
@@ -168,6 +171,9 @@
     <placeholder name="File Actions">
         <menuitem name="Create Link" action="Create Link"/>
         <menuitem name="Rename" action="Rename"/>
+        <menuitem name="Restore to" action="Restore to"/>
+        <menuitem name="Snapshot now" action="Snap Now"/>
+        <menuitem name="Scanning...." action="View Snap"/>
         <menu action="CopyToMenu">
             <menuitem name="Copy to next pane" action="Copy to next pane"/>
             <menuitem name="Copy to Home" action="Copy to Home"/>
@@ -218,6 +224,7 @@
     </placeholder>
     <separator name="Location After Clipboard Separator"/>
     <placeholder name="Dangerous File Actions">
+        <menuitem name="Restore" action="Restore"/>
         <menuitem name="Trash" action="LocationTrash"/>
         <menuitem name="Delete" action="LocationDelete"/>
         <menuitem name="Restore From Trash" action="LocationRestoreFromTrash"/>
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2
New file
@@ -0,0 +1,12 @@
--- caja-1.28.0/src/file-manager/fm-actions.h.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/fm-actions.h    2024-02-26 08:42:41.083717777 +0100
@@ -58,6 +58,9 @@
 #define FM_ACTION_NEW_LAUNCHER "New Launcher"
 #define FM_ACTION_NEW_LAUNCHER_DESKTOP "New Launcher Desktop"
 #define FM_ACTION_RENAME "Rename"
+#define FM_ACTION_RESTORE_TO "Restore to"
+#define FM_ACTION_HAS_SNAPSHOT "View Snap"
+#define FM_ACTION_SNAP_NOW "Snap Now"
 #define FM_ACTION_DUPLICATE "Duplicate"
 #define FM_ACTION_CREATE_LINK "Create Link"
 #define FM_ACTION_SELECT_ALL "Select All"
components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3
New file
@@ -0,0 +1,271 @@
--- caja-1.28.0/src/file-manager/fm-directory-view.c.orig    2024-02-20 01:30:36.000000000 +0100
+++ caja-1.28.0/src/file-manager/fm-directory-view.c    2024-02-26 08:42:41.084993364 +0100
@@ -1002,6 +1002,77 @@
     return FALSE;
 }
+ static void
+action_snap_now (GtkAction *action,
+         gpointer callback_data)
+{
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+  GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+  char *path = g_file_get_path (file);
+  char *fs = ts_get_zfs_filesystem (path);
+  char *cmd = g_strdup_printf ("/usr/lib/time-slider-snapshot '%s' '%s'", path, fs);
+  mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (GTK_WIDGET (callback_data)),
+                    cmd, NULL);
+
+  g_free (cmd);
+  g_free (fs);
+  g_free (path);
+  g_object_unref (file);
+}
+
+static void
+action_restore_to_desktop_callback (GtkAction *action,
+                    gpointer callback_data)
+{
+  GList *locations = NULL;
+  GList *node;
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  char *desktop_directory = caja_get_desktop_directory_uri();
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+
+  if (selection == NULL)
+    return;
+
+  if (desktop_directory == NULL)
+    return;
+
+
+  for (node = selection; node != NULL; node = node->next)
+    {
+      locations = g_list_prepend (locations,
+                  caja_file_get_uri ((CajaFile *) node->data));
+    }
+
+  fm_directory_view_move_copy_items (locations, NULL, desktop_directory,
+                     GDK_ACTION_COPY, 0, 0, view);
+
+  caja_file_list_free (selection);
+}
+
+static void
+action_show_snapshot_versions_callback (GtkAction *action,
+                                        gpointer callback_data)
+{
+  FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data);
+  GList *selection = fm_directory_view_get_selection_for_file_transfer (view);
+  GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+  char *dir = caja_file_get_snapshot_dir (CAJA_FILE (selection->data));
+  char *file_path = g_file_get_path (file);
+  char real_file_path [PATH_MAX + 1];
+  if (ts_realpath (file_path, real_file_path))
+    {
+      char *cmd = g_strdup_printf ("/usr/lib/time-slider-version '%s' '%s'", dir,
+                                   real_file_path);
+      mate_gdk_spawn_command_line_on_screen (gtk_widget_get_screen (GTK_WIDGET (callback_data)),
+                                        cmd, NULL);
+      g_free (cmd);
+    }
+
+  g_free (file_path);
+  g_object_unref (file);
+}
+
 static void
 action_trash_callback (GtkAction *action,
                gpointer callback_data)
@@ -1686,20 +1757,20 @@
         scripts_directory_uri = g_filename_to_uri(scripts_directory_path, NULL, NULL);
         scripts_directory_uri_length = strlen(scripts_directory_uri);
-        /* Support for GNOME Nautilus scripts
+        /* Support for GNOME Caja scripts
          */
-        char* nautilus_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "nautilus-scripts", NULL);
+        char* caja_scripts_path = g_build_filename(g_get_home_dir(), ".gnome2", "caja-scripts", NULL);
-        if (g_file_test(nautilus_scripts_path, G_FILE_TEST_IS_DIR) == TRUE)
+        if (g_file_test(caja_scripts_path, G_FILE_TEST_IS_DIR) == TRUE)
         {
-            char* nautilus_syslink = g_build_filename(g_get_user_config_dir(), "caja", "scripts", "nautilus", NULL);
+            char* caja_syslink = g_build_filename(g_get_user_config_dir(), "caja", "scripts", "caja", NULL);
             /* If link already exists, or also any other kind of file/dir with same name, ignore it */
-            if (g_file_test(nautilus_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE &&
-                g_file_test(nautilus_syslink, G_FILE_TEST_EXISTS) == FALSE &&
-                g_file_test(nautilus_syslink, G_FILE_TEST_IS_DIR) == FALSE)
+            if (g_file_test(caja_syslink, G_FILE_TEST_IS_SYMLINK) == FALSE &&
+                g_file_test(caja_syslink, G_FILE_TEST_EXISTS) == FALSE &&
+                g_file_test(caja_syslink, G_FILE_TEST_IS_DIR) == FALSE)
             {
                 /* Check if we need to create a link */
-                GDir* dir = g_dir_open(nautilus_scripts_path, 0, NULL);
+                GDir* dir = g_dir_open(caja_scripts_path, 0, NULL);
                 if (dir)
                 {
@@ -1713,20 +1784,20 @@
                     if (count > 0)
                     {
-                        /* Create link to nautilus folder */
-                        int res = symlink (nautilus_scripts_path, nautilus_syslink);
+                        /* Create link to caja folder */
+                        int res = symlink (caja_scripts_path, caja_syslink);
                         if (res != 0)
-                            g_warning ("Can't create symlink to nautilus scripts folder");
+                            g_warning ("Can't create symlink to caja scripts folder");
                     }
                     g_dir_close(dir);
                 }
             }
-            g_free(nautilus_syslink);
+            g_free(caja_syslink);
         }
-        g_free(nautilus_scripts_path);
+        g_free(caja_scripts_path);
     }
     g_free(scripts_directory_path);
@@ -7473,6 +7544,18 @@
   /* label, accelerator */       "RenameSelectAll", "<shift>F2",
   /* tooltip */                  NULL,
                                  G_CALLBACK (action_rename_select_all_callback) },
+  /* name, icon name */         { "Restore to", NULL,
+  /* label, accelerator */       N_("Restore to Desktop"), NULL,
+  /* tooltip */                  N_("Move each selected item to the Desktop"),
+                                 G_CALLBACK (action_restore_to_desktop_callback) },
+  /* name, stock id */         { "View Snap", NULL,
+  /* label, accelerator */       N_("View versions"), NULL,
+  /* tooltip */                  N_("View the versions of this file available in ZFS snapshots"),
+                                 G_CALLBACK (action_show_snapshot_versions_callback) },
+  /* name, stock id */         { "Snap Now", NULL,
+  /* label, accelerator */       N_("Snapshot now"), NULL,
+  /* tooltip */                  N_("Take a ZFS snapshot of this directory now"),
+                                 G_CALLBACK (action_snap_now) },
   /* name, icon name */        { "Trash", NULL,
   /* label, accelerator */       N_("Mo_ve to Trash"), NULL,
   /* tooltip */                  N_("Move each selected item to the Trash"),
@@ -8834,6 +8917,40 @@
     return FALSE;
 }
+typedef struct {
+  CajaFile              *file;
+  GCancellable              *cancel;
+  GtkAction                 *action;
+} HasSnapshotData;
+
+static void
+has_snapshot_ready_callback (gpointer user_data)
+{
+  GValue name = {0,};
+  HasSnapshotData *data = (HasSnapshotData*) user_data;
+  HasSnapshotResult result = caja_file_has_snapshot_version (data->file);
+
+  switch (result)
+    {
+    case UNKNOWN_STATE:
+    case NO:
+      gtk_action_set_sensitive (data->action, FALSE);
+      g_value_init (&name, G_TYPE_STRING);
+      /* SUN_BRANDING */
+      g_value_set_static_string (&name, _("No versions"));
+      g_object_set_property (G_OBJECT (data->action), "label", &name);
+      break;
+    case YES:
+      gtk_action_set_sensitive (data->action, TRUE);
+      g_value_init (&name, G_TYPE_STRING);
+      /* SUN_BRANDING */
+      g_value_set_static_string (&name, _("Explore versions"));
+      g_object_set_property (G_OBJECT (data->action), "label", &name);
+      break;
+    }
+  g_free (data);
+ }
+
 static void
 real_update_menus (FMDirectoryView *view)
 {
@@ -9211,6 +9328,75 @@
     gtk_action_set_sensitive (action, can_copy_files);
     G_GNUC_END_IGNORE_DEPRECATIONS;
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_RESTORE_TO);
+        gtk_action_set_visible (action, can_copy_files &&
+                                  caja_directory_is_in_snapshot (view->details->model));
+
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_SNAP_NOW);
+
+        if (selection_count == 1 && caja_file_is_directory (CAJA_FILE (selection->data)))
+            {
+              GFile *file = caja_file_get_location (CAJA_FILE (selection->data));
+              char *path = g_file_get_path (file);
+              char *fs = ts_get_zfs_filesystem (path);
+              if (fs)
+                {
+                  gtk_action_set_visible (action, TRUE);
+                  g_free (fs);
+                }
+              g_free (path);
+              g_object_unref (file);
+            }
+        else
+          gtk_action_set_visible (action, FALSE);
+
+        action = gtk_action_group_get_action (view->details->dir_action_group,
+                                              FM_ACTION_HAS_SNAPSHOT);
+   if (selection_count == 1)
+     {
+       GValue name = { 0, };
+       int result = caja_file_has_snapshot_version (CAJA_FILE (selection->data));
+
+       switch (result)
+         {
+         case NO:
+           gtk_action_set_visible (action, FALSE);
+           break;
+         case YES:
+           gtk_action_set_visible (action, TRUE);
+           gtk_action_set_sensitive (action, TRUE);
+           g_value_init (&name,G_TYPE_STRING);
+           /* SUN_BRANDING */
+           g_value_set_static_string (&name, _("Explore versions"));
+           g_object_set_property (G_OBJECT (action), "label", &name);
+           break;
+         case UNKNOWN_STATE:
+           gtk_action_set_visible (action, TRUE);
+           gtk_action_set_sensitive (action, FALSE);
+           g_value_init (&name,G_TYPE_STRING);
+           /* SUN_BRANDING */
+           g_value_set_static_string (&name, _("Scanning for versions"));
+           g_object_set_property (G_OBJECT (action), "label", &name);
+           break;
+         }
+       if (result == UNKNOWN_STATE)
+         {
+           HasSnapshotData *data = g_new0 (HasSnapshotData, 1);
+           data->action = action;
+           data->file = CAJA_FILE (selection->data);
+           data->cancel = g_cancellable_new ();
+           caja_file_get_snapshot_version (CAJA_FILE (selection->data),
+                                               has_snapshot_ready_callback,
+                                               data->cancel,
+                                               data);
+         }
+     }
+   else
+     gtk_action_set_visible (action, FALSE);
+
+
     real_update_paste_menu (view, selection, selection_count);
     disable_command_line = g_settings_get_boolean (mate_lockdown_preferences, CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE);
components/desktop/mate/caja/patches/Archiv/15-timescale.patch1
New file
@@ -0,0 +1,1289 @@
--- caja-1.28.0/src/timescale.c.orig    2024-02-26 08:42:41.085817612 +0100
+++ caja-1.28.0/src/timescale.c    2024-02-26 08:42:41.085766910 +0100
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+ *
+ */
+
+#include "config.h"
+#include "timescale.h"
+#include <math.h>
+#include <stdlib.h>
+#include <libcaja-private/caja-zfs.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#define BAR_W_MAX 20
+#define BAR_SPACE 2
+
+
+typedef struct
+{
+    char          *name;
+    char          *mountpoint;
+    char          *mtime_str;
+    char          *mtime_short_str;
+    time_t       mtime;
+    float           used_space;
+    char          *used_space_str;
+    SnapType       type;
+    char          *type_str;
+} Snap;
+
+
+struct TimeScalePrivate
+{
+    GList* all_snaps;
+    GList* snaps;
+    int*    bar_x_end;
+    int   current_pos;
+    int   num_snaps;
+    char  *num_rev_string;
+    int    current_period;
+    GList *today;
+    GList *yesterday;
+    GList *this_week;
+    GList *last_week;
+    GList *this_month;
+    GList *last_month;
+    gboolean scrollbar_set;
+    gboolean key_pressed;
+    GtkWidget *darea;
+    GtkWidget *period;
+    GtkWidget *info;
+    GtkWidget *scrolled;
+    GtkWidget *label_tip;
+};
+
+enum {
+    VALUE_CHANGED,
+    LAST_SIGNAL
+};
+
+enum {
+    ALL,
+    TODAY,
+    YESTERDAY,
+    THIS_WEEK,
+    LAST_WEEK,
+    THIS_MONTH,
+    LAST_MONTH
+};
+
+enum {
+    COLUMN_INDEX,
+    COLUMN_STRING
+};
+
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (TimeScale, timescale, GTK_TYPE_HBOX)
+
+#define TIMESCALE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_TIMESCALE, TimeScalePrivate))
+
+static gboolean
+timescale_expose (GtkWidget *widget,
+            GdkEventExpose *event, TimeScale *ts);
+
+static gboolean
+query_tooltip (GtkWidget  *widget,
+        gint        x,
+        gint        y,
+        gboolean    keyboard_tip,
+        GtkTooltip *tooltip,
+        gpointer    data);
+static int
+key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale *ts);
+static void
+button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale *ts);
+
+static void
+print_snaps (GList *list)
+{
+    GList* tmp = list;
+    int i = 0;
+
+    while (tmp)
+    {
+        Snap *snap = (Snap*) tmp->data;
+        printf ("=-= %d =-=\nname: %s\nmountpoint: %s\nmtime: %s\nused_space: %s\n",i,
+                snap->name, snap->mountpoint, snap->mtime_str, snap->used_space_str);
+        i++;
+        tmp = tmp->next;
+    }
+}
+
+static char *
+get_num_snap_string (GList *snap_list)
+{
+    goffset total = 0;
+    int i = 0;
+    GList *tmp = snap_list;
+    char *num_rev;
+
+    char *size_str = NULL;
+
+    for (tmp; tmp; tmp = tmp->next)
+    {
+        Snap *snap = ((Snap*) tmp->data);
+        total += snap->used_space;
+        i++;
+    }
+
+    total *= 1024;
+
+    size_str = g_format_size (total);
+
+    /* SUN_BRANDING */
+    num_rev = g_strdup_printf (_("%d %s\n%s"),
+            i - 1 /* Account for "Now" snapshot */,
+            /* SUN_BRANDING */
+            ngettext ("snapshot", "snapshots", i),
+            size_str);
+    g_free (size_str);
+
+    return num_rev;
+}
+
+
+static char *
+get_date (GDate *date)
+{
+    return g_strdup_printf ("%d/%d/%d", g_date_get_day (date),
+            g_date_get_month (date),
+            g_date_get_year (date));
+}
+
+static GList *
+trim_list_by_date (GList *list, int type)
+{
+    GDate then;
+    GDate now;
+    GDate range_min;
+    GDate range_max;
+    GDateWeekday weekday;
+
+    time_t time_now;
+    int diff  = 0;
+    time_now = time (NULL);
+    g_date_set_time_t (&now, time_now);
+    g_date_set_time_t (&range_min, time_now);
+    GList *return_list = NULL;
+    int days_diff = 0;
+    gboolean range = FALSE;
+
+    switch (type)
+    {
+        case TODAY:
+            days_diff = 0;
+            break;
+        case YESTERDAY:
+            days_diff = 1;
+            break;
+        case THIS_WEEK:
+            weekday = g_date_get_weekday(&now);
+            days_diff = weekday - G_DATE_MONDAY;
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_subtract_days (&range_min, days_diff);
+            range = TRUE;
+            break;
+        case LAST_WEEK:
+            g_date_subtract_days (&range_min, 7);
+            weekday = g_date_get_weekday(&range_min);
+            days_diff = weekday - G_DATE_MONDAY;
+            g_date_subtract_days (&range_min, days_diff);
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_add_days (&range_max, 6);
+            range = TRUE;
+            break;
+        case THIS_MONTH:
+            g_date_set_dmy (&range_min, 1, g_date_get_month (&now),
+                    g_date_get_year (&now));
+            g_date_set_time_t (&range_max, time_now);
+            range = TRUE;
+            break;
+        case LAST_MONTH:
+            g_date_subtract_months (&range_min, 1);
+            g_date_set_dmy (&range_min, 1,
+                    g_date_get_month (&range_min),
+                    g_date_get_year (&range_min));
+            memcpy (&range_max, &range_min, sizeof (GDate));
+            g_date_add_days (&range_max, g_date_get_days_in_month (g_date_get_month (&range_min), g_date_get_year (&range_min)) - 1);
+            range = TRUE;
+            break;
+    }
+
+    while (list)
+    {
+        Snap* snap = (Snap*) list->data;
+
+        if (snap->mtime != 0)
+        {
+            g_date_set_time_t (&then, snap->mtime);
+
+            if (!range)
+            {
+                if (g_date_get_julian (&now) - g_date_get_julian (&then) == days_diff)
+                    return_list = g_list_append (return_list, snap);
+            }
+            else
+            {
+                if (g_date_compare (&then, &range_min) >= 0 && g_date_compare (&then, &range_max) <= 0)
+                    return_list = g_list_append (return_list, snap);
+            }
+        }
+        list = list->next;
+    }
+    return return_list;
+}
+
+static void
+free_periods (TimeScale *ts)
+{
+    if (ts->priv->today)
+    {
+        g_list_free (ts->priv->today);
+        ts->priv->today = NULL;
+    }
+    if (ts->priv->yesterday)
+    {
+        g_list_free (ts->priv->yesterday);
+        ts->priv->yesterday = NULL;
+    }
+    if (ts->priv->this_week)
+    {
+        g_list_free (ts->priv->this_week);
+        ts->priv->this_week = NULL;
+    }
+    if (ts->priv->last_week)
+    {
+        g_list_free (ts->priv->last_week);
+        ts->priv->last_week = NULL;
+    }
+    if (ts->priv->this_month)
+    {
+        g_list_free (ts->priv->this_month);
+        ts->priv->this_month = NULL;
+    }
+    if (ts->priv->last_month)
+    {
+        g_list_free (ts->priv->last_month);
+        ts->priv->last_month = NULL;
+    }
+}
+
+static GtkListStore *
+create_periods (TimeScale *ts)
+{
+    GtkTreeIter iter;
+    GtkListStore* periods = gtk_list_store_new (3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_POINTER);
+
+    free_periods (ts);
+
+    gtk_list_store_append (periods, &iter);
+    gtk_list_store_set (periods, &iter, 0, ALL, 1, _("All"), 2, ts->priv->all_snaps, -1);
+
+    ts->priv->today = trim_list_by_date (ts->priv->all_snaps, TODAY);
+
+    if (ts->priv->today)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, TODAY, 1, _("Today"), 2, ts->priv->today, -1);
+    }
+
+    ts->priv->yesterday = trim_list_by_date (ts->priv->all_snaps, YESTERDAY);
+    if (ts->priv->yesterday)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, YESTERDAY, 1, _("Yesterday"), 2, ts->priv->yesterday, -1);
+    }
+
+    ts->priv->this_week = trim_list_by_date (ts->priv->all_snaps, THIS_WEEK);
+    if (ts->priv->this_week)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, THIS_WEEK, 1, _("This Week"), 2, ts->priv->this_week, -1);
+    }
+
+    ts->priv->last_week = trim_list_by_date (ts->priv->all_snaps, LAST_WEEK);
+    if (ts->priv->last_week)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, LAST_WEEK, 1, _("Last Week"), 2, ts->priv->last_week, -1);
+    }
+
+    ts->priv->this_month = trim_list_by_date (ts->priv->all_snaps, THIS_MONTH);
+    if (ts->priv->this_month)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, THIS_MONTH, 1, _("This Month"), 2, ts->priv->this_month, -1);
+    }
+
+    ts->priv->last_month = trim_list_by_date (ts->priv->all_snaps, LAST_MONTH);
+    if (ts->priv->last_month)
+    {
+        gtk_list_store_append (periods, &iter);
+        gtk_list_store_set (periods, &iter, 0, LAST_MONTH, 1, _("Last Month"), 2, ts->priv->last_month, -1);
+    }
+
+    return periods;
+}
+
+static void
+period_changed (GtkComboBox *combo,
+        TimeScale *ts)
+{
+    gint type;
+    GtkTreePath *path;
+    GtkTreeModel *model;
+    GtkTreeIter  iter;
+    GList *period;
+
+    if (!gtk_combo_box_get_active_iter (combo, &iter))
+        return;
+
+    model = gtk_combo_box_get_model (combo);
+
+    gtk_tree_model_get (model, &iter, 0, &type, 2, &period, -1);
+
+    if (ts->priv->current_period == type)
+        return;
+
+    ts->priv->current_period = type;
+
+    ts->priv->snaps = period;
+
+    ts->priv->num_snaps = g_list_length (ts->priv->snaps);
+    if (ts->priv->bar_x_end)
+        g_free (ts->priv->bar_x_end);
+
+    ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps));
+
+    ts->priv->current_pos = -1;
+
+    if (ts->priv->num_rev_string)
+        g_free (ts->priv->num_rev_string);
+
+    ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps);
+    gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string);
+
+    gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX ) * ts->priv->num_snaps) + PADDING * 2, 60);
+
+    gtk_widget_queue_draw (ts->priv->darea);
+}
+
+static void
+timescale_init (TimeScale *ts)
+{
+    GtkWidget *vbox;
+    GtkCellRenderer *renderer;
+
+    ts->priv = TIMESCALE_GET_PRIVATE (ts);
+    ts->priv->snaps = NULL;
+    ts->priv->all_snaps = NULL;
+    ts->priv->num_snaps = 0;
+    ts->priv->bar_x_end = NULL;
+    ts->priv->current_pos = 0;
+    ts->priv->num_rev_string = NULL;
+    ts->priv->current_period = ALL;
+    ts->priv->today = NULL;
+    ts->priv->yesterday = NULL;
+    ts->priv->this_week = NULL;
+    ts->priv->last_week = NULL;
+    ts->priv->this_month = NULL;
+    ts->priv->last_month = NULL;
+    ts->priv->key_pressed = FALSE;
+
+    gtk_box_set_homogeneous (GTK_BOX (ts), FALSE);
+
+    /* setup drawing area */
+
+    ts->priv->darea = gtk_drawing_area_new ();
+
+    g_signal_connect(ts->priv->darea, "draw",
+            G_CALLBACK(timescale_expose), ts);
+
+    g_signal_connect (ts->priv->darea, "query-tooltip",
+            G_CALLBACK (query_tooltip), ts);
+
+    g_signal_connect(ts->priv->darea, "key-press-event",
+            G_CALLBACK(key_pressed), ts);
+    g_signal_connect(ts->priv->darea, "button_press_event",
+            G_CALLBACK(button_pressed), ts);
+    gtk_widget_set_can_focus(GTK_WIDGET (ts->priv->darea), TRUE);
+    gtk_widget_add_events (GTK_WIDGET (ts->priv->darea), GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK);
+    g_object_set (G_OBJECT (ts->priv->darea), "has-tooltip", TRUE, NULL);
+    gtk_widget_set_size_request (GTK_WIDGET (ts->priv->darea), -1, 60);
+
+    ts->priv->scrolled = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ts->priv->scrolled),
+            GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
+    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (ts->priv->scrolled),
+            ts->priv->darea);
+    gtk_viewport_set_shadow_type (GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (ts->priv->scrolled))), GTK_SHADOW_NONE);
+    gtk_widget_show (ts->priv->scrolled);
+
+    ts->priv->label_tip = gtk_label_new ("Hello");
+    gtk_widget_set_name (ts->priv->label_tip, "gtk-tooltip");
+    g_object_ref_sink (ts->priv->label_tip);
+    gtk_label_set_justify (GTK_LABEL (ts->priv->label_tip), GTK_JUSTIFY_CENTER);
+
+    /* setup period combo and snap info */
+
+    vbox = gtk_vbox_new (FALSE, 5);
+    ts->priv->period = gtk_combo_box_new ();
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (ts->priv->period),
+            renderer,
+            TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (ts->priv->period), renderer,
+            "text", 1,
+            NULL);
+    g_signal_connect (ts->priv->period, "changed", G_CALLBACK (period_changed), ts);
+
+    ts->priv->info = gtk_label_new ("info\ninfo");
+    gtk_label_set_justify (GTK_LABEL (ts->priv->info), GTK_JUSTIFY_CENTER);
+    gtk_box_pack_start (GTK_BOX (vbox), ts->priv->period, FALSE, FALSE, 5);
+    gtk_box_pack_end (GTK_BOX (vbox), ts->priv->info, FALSE, FALSE, 5);
+
+    /* setup container */
+
+    gtk_box_pack_start (GTK_BOX (ts), vbox, FALSE, FALSE, 0);
+    gtk_box_pack_start (GTK_BOX (ts), ts->priv->scrolled, TRUE, TRUE, 0);
+
+    gtk_widget_set_can_focus(GTK_WIDGET (ts), TRUE);
+    gtk_widget_show_all (GTK_WIDGET (ts));
+}
+
+static float
+get_max_snap_size (GList *snaps)
+{
+    Snap *snap;
+    float max_size = 0;
+    while (snaps)
+    {
+        GList *next = snaps->next;
+        snap = (Snap*) snaps->data;
+        if (snap->used_space > max_size)
+            max_size = snap->used_space;
+        snaps = next;
+    }
+    return max_size;
+}
+
+static Snap*
+get_now_snap (TimeScale* ts)
+{
+    Snap *last_snap;
+    last_snap = g_new0 (Snap, 1);
+    last_snap->name = g_strdup (_("Now"));
+    last_snap->mountpoint = g_strdup (_("None"));
+    last_snap->mtime_str = g_strdup (_("Now"));
+    last_snap->mtime_short_str = g_strdup (_("Now"));
+    last_snap->used_space_str = g_strdup (_("-"));
+    last_snap->used_space = 0.0;
+    last_snap->type = NOW;
+    last_snap->type_str = g_strdup (_("Current Directory"));
+    return last_snap;
+}
+
+static char *
+get_type_str (SnapType type)
+{
+    switch (type)
+    {
+        case LOCAL_AUTOMATIC:
+            return g_strdup (_("Automatic Snapshot"));
+            break;
+        case LOCAL_MANUAL:
+            return g_strdup (_("Manual Snapshot"));
+            break;
+        case REMOTE_AUTOMATIC:
+            return g_strdup (_("Automatic Remote Backup"));
+            break;
+        case REMOTE_MANUAL:
+            return g_strdup (_("Manual Remote Backup"));
+            break;
+        default:
+            break;
+    }
+    return NULL;
+}
+
+static SnapType
+get_type (ZfsDataSet* snap)
+{
+    if (snap->type)
+    {
+        if (strstr (snap->name, "zfs-auto-snap"))
+            return LOCAL_AUTOMATIC;
+        else
+            return LOCAL_MANUAL;
+    }
+    else
+    {
+        if (strstr (snap->name, "zfs-auto-snap"))
+            return REMOTE_AUTOMATIC;
+        else
+            return REMOTE_MANUAL;
+    }
+}
+
+static GList *
+copy_zfs_list (GList *list)
+{
+    ZfsDataSet *snap;
+    GList *new_list = NULL;
+    Snap *ts_shot;
+    GList *tmp_list = list;
+    while (tmp_list)
+    {
+        snap = (ZfsDataSet*) tmp_list->data;
+        ts_shot = g_new0 (Snap, 1);
+        ts_shot->name = g_strdup (snap->name);
+        ts_shot->mountpoint = g_strdup (snap->mountpoint);
+        ts_shot->mtime_str = g_strdup (snap->mtime_str);
+        ts_shot->mtime_short_str = caja_date_as_string (snap->mtime, TRUE);
+        ts_shot->used_space_str = g_strdup (snap->used_space_str);
+        ts_shot->used_space = snap->used_space;
+        ts_shot->mtime = snap->mtime;
+        ts_shot->type = get_type (snap);
+        ts_shot->type_str = get_type_str (ts_shot->type);
+        new_list = g_list_append (new_list, ts_shot);
+        tmp_list = tmp_list->next;
+    }
+    return new_list;
+}
+
+static void
+free_snap (Snap *snap)
+{
+    if (snap->name)
+        g_free (snap->name);
+    if (snap->mountpoint)
+        g_free (snap->mountpoint);
+    if (snap->mtime_str)
+        g_free (snap->mtime_str);
+    if (snap->mtime_short_str)
+        g_free (snap->mtime_short_str);
+    if (snap->used_space_str)
+        g_free (snap->used_space_str);
+    if (snap->type_str)
+        g_free (snap->type_str);
+
+    g_free (snap);
+}
+
+static void
+free_snap_list (GList *list)
+{
+    GList *tmp_list = list;
+    while (tmp_list)
+    {
+        free_snap ((Snap*) tmp_list->data);
+        tmp_list = tmp_list->next;
+    }
+}
+
+
+void
+timescale_set_snapshots (TimeScale* ts, GList *list, int init_position)
+{
+    if (ts->priv->bar_x_end)
+        g_free (ts->priv->bar_x_end);
+
+    if (ts->priv->num_rev_string)
+        g_free (ts->priv->num_rev_string);
+
+    if (ts->priv->all_snaps)
+        free_snap_list (ts->priv->all_snaps);
+
+    ts->priv->all_snaps = copy_zfs_list (list);
+    ts->priv->all_snaps = g_list_append (ts->priv->all_snaps, get_now_snap(ts));
+    ts->priv->snaps = ts->priv->all_snaps;
+    ts->priv->num_snaps = g_list_length (ts->priv->snaps);
+
+    ts->priv->bar_x_end = g_new (int, g_list_length (ts->priv->snaps));
+    ts->priv->current_pos = ts->priv->num_snaps - 1;
+
+    if (init_position >= 0 && init_position <= ts->priv->num_snaps - 1)
+        ts->priv->current_pos = init_position;
+
+    ts->priv->num_rev_string = get_num_snap_string (ts->priv->snaps);
+
+    gtk_label_set_label (GTK_LABEL (ts->priv->info), ts->priv->num_rev_string);
+    ts->priv->current_period = ALL;
+
+    gtk_combo_box_set_model (GTK_COMBO_BOX (ts->priv->period), GTK_TREE_MODEL (create_periods (ts)));
+    gtk_combo_box_set_active (GTK_COMBO_BOX (ts->priv->period), ALL);
+
+    gtk_widget_set_size_request (ts->priv->darea, ((BAR_SPACE + BAR_W_MAX) * ts->priv->num_snaps) + PADDING * 2, 60);
+    ts->priv->scrollbar_set = FALSE;
+}
+
+GtkWidget*
+timescale_new ()
+{
+    return g_object_new (TYPE_TIMESCALE, NULL);
+}
+
+static void
+draw_type (cairo_t *cr, int x_c, int y_c, int w, SnapType type)
+{
+    int x = x_c - w/2;
+    int y = y_c - w/2;
+
+
+
+    switch (type)
+    {
+        case LOCAL_MANUAL:
+            cairo_move_to (cr,x,y_c);
+            cairo_line_to (cr,x+w/2,y_c-w/2);
+            cairo_line_to (cr,x+w,y_c);
+            /*y = y_c - w/4;
+              cairo_rectangle (cr, x, y, w, w/2);*/
+            break;
+        case LOCAL_AUTOMATIC:
+            cairo_arc (cr, x_c, y_c, w/2, M_PI, M_PI*2);
+            break;
+        case REMOTE_MANUAL:
+            /* cairo_rectangle (cr, x, y, w, w); */
+            cairo_move_to (cr,x,y_c);
+            cairo_line_to (cr,x+w/2,y_c+w/2);
+            cairo_line_to (cr,x+w,y_c);
+            cairo_line_to (cr,x+w/2,y_c-w/2);
+            break;
+        case REMOTE_AUTOMATIC:
+            cairo_arc (cr, x_c, y_c, w/2, 0.0, M_PI*2);
+            break;
+        default:
+            break;
+    }
+}
+
+static void
+draw_rounded_bar (cairo_t *cr, int x, int y, double w, double h, int r)
+{
+    /* bottom y instead of top */
+    y -= h;
+
+    if (h == 0 || h < (w / 2) + r)
+    {
+        y += h;
+        cairo_arc (cr, x+(w/2), y+.5, w/2, M_PI, M_PI*2);
+        cairo_line_to (cr,x,y+.5);
+        return;
+    }
+
+    /*    A ****BQ
+          H      C
+          *      *
+          G      D
+          F **** E */
+
+
+    cairo_arc (cr, x+(w/2), y+(w/2), w/2, M_PI, M_PI*2); /* arc from H to C */
+    cairo_line_to (cr,x+w,y+h-r);             /* Move to D */
+    cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h);   /* Curve to E */
+    cairo_line_to(cr, x+r,y+h);                 /* Line to F */
+    cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r);         /* Curve to G */
+    cairo_line_to(cr, x,y+(w/2));                 /* Line to H */
+
+}
+
+
+
+static void
+draw_rounded_rec (cairo_t *cr, int x, int y, double w, double h, int r)
+{
+    /* bottom y instead of top */
+    y -= h;
+
+    if (h == 0)
+    {
+        cairo_set_line_width (cr, 1);
+        cairo_move_to (cr,x,y - .5);
+        cairo_line_to (cr, x + w, y -.5);
+        return;
+    }
+
+    if (h < r * 2)
+        r = h / 2;
+
+    /*    A****BQ
+          H      C
+          *      *
+          G      D
+          F****E */
+
+    cairo_move_to (cr,x+r,y);                 /* Move to A */
+    cairo_line_to (cr,x+w-r,y);                      /* Straight line to B */
+    cairo_curve_to (cr,x+w,y,x+w,y,x+w,y+r);         /* Curve to C, Control points are both at Q */
+    cairo_line_to (cr,x+w,y+h-r);             /* Move to D */
+    cairo_curve_to(cr, x+w,y+h,x+w,y+h,x+w-r,y+h);   /* Curve to E */
+    cairo_line_to(cr, x+r,y+h);                 /* Line to F */
+    cairo_curve_to(cr, x,y+h,x,y+h,x,y+h-r);         /* Curve to G */
+    cairo_line_to(cr, x,y+r);                 /* Line to H */
+    cairo_curve_to(cr, x,y,x,y,x+r,y);             /*  Curve to A */
+}
+
+    static void
+set_cr_color (GtkWidget *widget, cairo_t* cr, SnapType type, double alpha)
+{
+    GtkStyle * s;
+
+    switch (type)
+    {
+        case LOCAL_MANUAL:
+        case REMOTE_MANUAL:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->dark[GTK_STATE_SELECTED]);
+            break;
+        case LOCAL_AUTOMATIC:
+        case REMOTE_AUTOMATIC:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->light[GTK_STATE_SELECTED]);
+            break;
+        case NOW:
+            s = gtk_widget_get_style(widget);
+            gdk_cairo_set_source_color (cr, &s->black);
+            break;
+        default:
+            break;
+    }
+}
+
+int get_snap_index_from_coord (TimeScale* ts, gdouble x, gdouble y)
+{
+    int i;
+
+    for (i = 0; i < ts->priv->num_snaps; i++)
+    {
+        if (x < ts->priv->bar_x_end[i])
+            return i;
+    }
+    return -1;
+}
+
+static gboolean
+timescale_expose (GtkWidget *widget,
+        GdkEventExpose *event, TimeScale* ts)
+{
+    PangoContext *context;
+    PangoFontMetrics *metrics;
+    PangoRectangle logical_rect;
+    gint ascent, descent;
+    cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
+    int i = 0;
+    double x;
+    int y;
+    int selected_x = -1;
+    int remaining_timeline_space;
+    int remaining_x_start;
+    int remaining_x_end;
+    int bar_space = BAR_SPACE;
+    int bar_w = BAR_W_MAX;
+    int line_padding = 3;
+    float bar_max_h_inc;
+    float bar_max_h;
+    int last_bar_x;
+    int line_height = 0;
+    int timeline_line_height = 0;
+    float max_size = 0;
+    GList *view_snaps = NULL;
+    int num_view_snaps = 0;
+    PangoLayout *layout = pango_cairo_create_layout (cr);
+    Snap *tmp_snap = NULL;
+    gboolean space_left = TRUE;
+    PangoFontDescription *timeline_font = NULL;
+
+    if (gtk_widget_has_focus (widget)) {
+        gtk_paint_focus (gtk_widget_get_style (widget), cr, gtk_widget_get_state (widget),
+                widget, NULL,
+                0, 0,
+                gtk_widget_get_allocated_width(widget)-1, gtk_widget_get_allocated_height(widget)-1);
+    }
+
+    if (!ts->priv->snaps)
+        goto end;
+
+    /* smaller font for timeline */
+
+    timeline_font = pango_font_description_copy_static ((gtk_widget_get_style (widget))->font_desc);
+    pango_font_description_set_size (timeline_font, pango_font_description_get_size (timeline_font) - (PANGO_SCALE*2));
+
+    /* determine space needed for 2 lines of text + padding with
+     * current widget->style->font_desc
+     * and widget->style->font_desc - 1*/
+
+    context = gtk_widget_get_pango_context (widget);
+    metrics = pango_context_get_metrics (context, (gtk_widget_get_style (widget))->font_desc,
+            pango_context_get_language (context));
+    ascent = pango_font_metrics_get_ascent (metrics);
+    descent = pango_font_metrics_get_descent (metrics);
+    pango_font_metrics_unref (metrics);
+
+    line_height = PANGO_PIXELS (ascent + descent);
+
+    metrics = pango_context_get_metrics (context, timeline_font,
+            pango_context_get_language (context));
+    ascent = pango_font_metrics_get_ascent (metrics);
+    descent = pango_font_metrics_get_descent (metrics);
+    pango_font_metrics_unref (metrics);
+
+    timeline_line_height = PANGO_PIXELS (ascent + descent);
+
+
+    x = PADDING;
+    bar_max_h = gtk_widget_get_allocated_height(widget) - ((line_height + timeline_line_height) + PADDING * 4);
+    y =  gtk_widget_get_allocated_height(widget) - (line_height + (PADDING * 2));
+
+    num_view_snaps = ts->priv->num_snaps;
+    view_snaps = ts->priv->snaps;
+
+    max_size = log ((float)get_max_snap_size (view_snaps));
+
+    bar_max_h_inc =  max_size / bar_max_h;
+
+    if (!ts->priv->scrollbar_set)
+    {
+        GtkAdjustment *adj;
+        adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled));
+        gtk_adjustment_set_value (adj, gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size (adj));
+        gtk_adjustment_set_step_increment(adj, BAR_W_MAX + BAR_SPACE);
+        gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        ts->priv->scrollbar_set = TRUE;
+    }
+
+    /* draw the bars */
+
+    cairo_set_line_width (cr, 1);
+
+    for (i = 0; i < num_view_snaps; i++)
+    {
+        tmp_snap = g_list_nth_data (view_snaps, i);
+        int rounded_radius = ROUNDED_RADIUS;
+        int height = 0;
+        gboolean draw_bar = TRUE;
+        double alpha = 1.0;
+
+        if (tmp_snap->used_space != 0)
+            height = (int) log (tmp_snap->used_space) / bar_max_h_inc;
+
+        /*printf ("drawing %d height %d size %f log of size %f\n", i,
+          height,
+          tmp_snap->used_space, log (tmp_snap->used_space));*/
+
+        if (height == 0 & tmp_snap->used_space != 0)
+            height = 1;
+
+        if (tmp_snap->type == REMOTE_AUTOMATIC ||
+                tmp_snap->type == REMOTE_MANUAL)
+            height = bar_max_h - bar_max_h_inc; /* placeholder until we get rsync size */
+
+        if (height < ROUNDED_RADIUS)
+            height = 0;
+
+        /* printf ("height %d name %s size %s\n", height, tmp_snap->name, tmp_snap->used_space_str);  */
+
+        if (i == ts->priv->current_pos)
+        {
+
+            cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1);
+            draw_rounded_rec (cr, x-1, y+2, bar_w+2, bar_max_h+8, ROUNDED_RADIUS);
+
+            cairo_fill (cr);
+            cairo_stroke(cr);
+
+            alpha = 0.5;
+            selected_x = x-1 + ((bar_w+2) / 2);
+
+            if (tmp_snap->type != NOW)
+            {
+                char *selected_time_size = g_strdup_printf ("%s - %s",
+                        tmp_snap->mtime_str,
+                        tmp_snap->used_space_str);
+                gdk_cairo_set_source_color (cr,  gtk_widget_get_style(widget)->text);
+                pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc);
+                pango_layout_set_text (layout, selected_time_size, -1);
+                g_free (selected_time_size);
+            }
+            else
+                pango_layout_set_text (layout, tmp_snap->mtime_str, -1);
+        }
+
+        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.8);
+
+        ts->priv->bar_x_end[i] = x + bar_w;
+
+        if (draw_bar)
+        {
+            int tmp_y = y;
+            float tmp_w = bar_w;
+            set_cr_color (widget, cr, tmp_snap->type, 1.0);
+            if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == LOCAL_MANUAL)
+            {
+                tmp_y -= 1;
+                tmp_w -= 1;
+            }
+
+            if (tmp_snap->type == LOCAL_AUTOMATIC || tmp_snap->type == REMOTE_AUTOMATIC)
+                draw_rounded_bar (cr, x, tmp_y, tmp_w, height, rounded_radius);
+            else if (tmp_snap->type == LOCAL_MANUAL || tmp_snap->type == REMOTE_MANUAL)
+                draw_rounded_rec (cr, x, tmp_y, tmp_w, height, rounded_radius);
+            else /* Now Shape */
+                draw_type (cr, (x + ((bar_w+bar_space)/2)) - .5, y - (bar_max_h/2), bar_w - 4, REMOTE_MANUAL);
+
+            if (tmp_snap->type == REMOTE_MANUAL || tmp_snap->type == REMOTE_AUTOMATIC || tmp_snap->type == NOW)
+                cairo_fill (cr);
+        }
+
+        cairo_stroke(cr);
+
+        x += bar_w + bar_space;
+    }
+
+    last_bar_x = x;
+
+    /* ensure selected bar is visible on key press when scrollbar is enabled */
+
+    if (ts->priv->current_pos != -1 && ts->priv->key_pressed)
+    {
+
+        GtkAdjustment *adj;
+        adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled));
+        ts->priv->key_pressed = FALSE;
+
+        if (gtk_adjustment_get_value (adj) > selected_x)
+        {
+            gtk_adjustment_set_value (adj, selected_x - BAR_W_MAX);
+            gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        }
+        if (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj) < selected_x)
+        {
+            gtk_adjustment_set_value (adj, (selected_x + BAR_W_MAX) - gtk_adjustment_get_page_size (adj));
+            gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (ts->priv->scrolled), adj);
+        }
+    }
+
+    pango_layout_set_font_description (layout, gtk_widget_get_style(widget)->font_desc);
+
+    /* try to center the selected text */
+
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (ts->priv->current_pos != -1 && selected_x != -1)
+    {
+
+        int right_space = last_bar_x - selected_x;
+
+        if (logical_rect.width /2 > selected_x)
+            /* no space on the left, left align */
+            selected_x = PADDING;
+        else if (logical_rect.width /2 > right_space)
+            /* no space on the right, right align */
+            selected_x = last_bar_x - logical_rect.width;
+        else
+            selected_x -= logical_rect.width /2;
+
+        if (selected_x < 0)
+            selected_x = PADDING;
+
+        /* draw background */
+        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1);
+        draw_rounded_rec (cr, selected_x-2, line_height + 3 , logical_rect.width + 4, line_height+1, ROUNDED_RADIUS);
+        cairo_fill (cr);
+        cairo_stroke(cr);
+
+        /* then selected text */
+        gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text);
+        cairo_move_to(cr, selected_x, PADDING);
+        pango_cairo_show_layout (cr, layout);
+    }
+
+    /* timeline */
+
+    /* what to do
+     * try to draw oldest
+     * then "now" first
+     * then in between dates
+     * draw additional is possible starting with newest first */
+
+    x = PADDING;
+
+
+    /* Can the oldest time fit ? */
+    tmp_snap = g_list_nth_data (view_snaps, 0);
+    pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+    pango_layout_set_font_description (layout, timeline_font);
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (logical_rect.width >  gtk_widget_get_allocated_width(widget))
+        goto end;
+
+    /* draw anchor line */
+
+    x += bar_w/2 + .5;
+
+    cairo_set_line_width (cr, 1);
+    gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa);
+    cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1));
+    cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_line_to (cr, x+2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_stroke(cr);
+
+    x += 3;
+
+    gdk_cairo_set_source_color (cr, gtk_widget_get_style(widget)->text_aa);
+    cairo_move_to(cr, x,  gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+    pango_cairo_show_layout (cr, layout);
+
+    remaining_timeline_space = last_bar_x - (x + logical_rect.width);
+
+    remaining_x_start = x + logical_rect.width;
+
+    /* try to draw last item */
+
+    tmp_snap = g_list_nth_data (view_snaps, num_view_snaps-1);
+    pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+    pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+    if (remaining_timeline_space < (logical_rect.width + bar_w))
+        goto end;
+
+    remaining_timeline_space -= logical_rect.width + bar_w;
+
+    x = last_bar_x;
+
+
+    x -= bar_space + bar_w/2 + 0.5;
+
+    cairo_move_to (cr, x,  gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-2));
+    cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_line_to (cr, x-2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+    cairo_stroke(cr);
+
+    cairo_move_to(cr, x - (logical_rect.width + 3.5),
+             gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+    pango_cairo_show_layout (cr, layout);
+
+    remaining_x_end = x - (logical_rect.width + 3.5);
+
+    /* now find the next bar that we can to the timeline and loop */
+
+
+    while (space_left)
+    {
+        int bar = get_snap_index_from_coord (ts, remaining_x_start, 0);
+        /* get the next snap */
+        bar++;
+        if (bar >= num_view_snaps)
+            goto end;
+
+        tmp_snap = g_list_nth_data (view_snaps, bar);
+        pango_layout_set_text (layout, tmp_snap->mtime_short_str, -1);
+
+        pango_layout_get_pixel_extents (layout,NULL, &logical_rect);
+
+        if (remaining_timeline_space < logical_rect.width + 4)
+            goto end;
+
+        /* get middle x coord of current bar */
+
+        x = PADDING + ((bar_w + bar_space) * bar) + bar_w / 2;
+
+        if ( x + 4 + logical_rect.width > remaining_x_end)
+            goto end;
+
+        /* draw anchor and text */
+        x += 0.5;
+
+        cairo_move_to (cr, x, gtk_widget_get_allocated_height(widget) - ((line_height + PADDING*2)-1));
+        cairo_line_to (cr, x,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+        cairo_line_to (cr, x+2.5,  gtk_widget_get_allocated_height(widget) - (line_height/2 + PADDING)-.5);
+        cairo_stroke(cr);
+
+        x += 3;
+
+        cairo_move_to(cr, x,
+                 gtk_widget_get_allocated_height(widget) - (line_height + PADDING));
+        pango_cairo_show_layout (cr, layout);
+
+        remaining_x_start = x + logical_rect.width;
+
+        remaining_timeline_space = remaining_x_end - remaining_x_start;
+
+        if (remaining_timeline_space <= 0)
+            space_left = FALSE;
+    }
+end:
+    if (timeline_font)
+        pango_font_description_free(timeline_font);
+    cairo_destroy(cr);
+
+    return FALSE;
+}
+
+static int
+key_pressed (GtkWidget *widget, GdkEventKey *event, TimeScale* ts)
+{
+    switch (event->keyval)
+    {
+        case GDK_KEY_KP_Left:
+        case GDK_KEY_KP_Up:
+        case GDK_KEY_Left:
+        case GDK_KEY_Up:
+
+            if (ts->priv->current_pos >= 1)
+            {
+                ts->priv->current_pos -= 1;
+                gtk_widget_queue_draw (widget);
+                ts->priv->key_pressed = TRUE;
+                g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+                /* printf ("key_pressed back %d\n", ts->priv->current_pos); */
+            }
+            return TRUE;
+        case GDK_KEY_KP_Right:
+        case GDK_KEY_KP_Down:
+        case GDK_KEY_Right:
+        case GDK_KEY_Down:
+            if (ts->priv->current_pos <= ts->priv->num_snaps - 2)
+            {
+                ts->priv->current_pos += 1;
+                gtk_widget_queue_draw (widget);
+                ts->priv->key_pressed = TRUE;
+                g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+                /* printf ("key_pressed forward %d\n", ts->priv->current_pos); */
+            }
+            return TRUE;
+
+        default:
+            return FALSE;
+    }
+}
+
+int timescale_get_position (TimeScale* ts)
+{
+    /* translate into all_snaps position */
+    gconstpointer data;
+    data = g_list_nth_data (ts->priv->snaps, ts->priv->current_pos);
+    return g_list_index (ts->priv->all_snaps, data);
+}
+
+static int match_func (ZfsDataSet *set, char *dir)
+{
+    return strcmp (set->mountpoint, dir);
+}
+
+void
+timescale_set_position (TimeScale* ts, char *mountpoint)
+{
+    gboolean redraw = FALSE;
+    int num_snaps = g_list_length (ts->priv->all_snaps);
+    if (mountpoint)
+    {
+        int pos;
+        GList* match = NULL;
+        match = g_list_find_custom (ts->priv->all_snaps, mountpoint, (GCompareFunc)match_func);
+        pos = g_list_index (ts->priv->all_snaps, match->data);
+        if (pos != -1 && ts->priv->current_pos != pos)
+        {
+            redraw = TRUE;
+            ts->priv->current_pos = pos;
+        }
+    }
+    else
+    { /* Now special case */
+        if (ts->priv->current_pos != num_snaps - 1)
+        {
+            redraw = TRUE;
+            ts->priv->current_pos = num_snaps - 1;
+        }
+    }
+
+    if (redraw)
+        gtk_widget_queue_draw (GTK_WIDGET (ts));
+}
+
+static void
+button_pressed (GtkWidget *widget, GdkEventButton *event, TimeScale* ts)
+{
+    int new_pos;
+
+    gtk_widget_grab_focus (widget);
+    new_pos = get_snap_index_from_coord (ts, event->x, event->y);
+
+    if (new_pos == -1)
+        return;
+
+    if (new_pos != ts->priv->current_pos)
+    {
+        ts->priv->current_pos = new_pos;
+        /* printf ("button_pressed (%g,%g) selected %d\n", event->x, event->y, ts->priv->current_pos); */
+        g_signal_emit (ts, signals[VALUE_CHANGED], 0);
+        gtk_widget_queue_draw (widget);
+    }
+}
+
+static gboolean
+query_tooltip (GtkWidget  *widget,
+        gint        x,
+        gint        y,
+        gboolean    keyboard_tip,
+        GtkTooltip *tooltip,
+        gpointer    data)
+{
+    static gboolean set = FALSE;
+    static int old_pos = -1;
+    TimeScale* ts = TIMESCALE (data);
+    int new_pos = get_snap_index_from_coord (ts, x, y);
+    /* printf ("in query_tooltip new_pos %d total %d num_snap %d\n", new_pos, g_list_length (ts->priv->snaps), ts->priv->num_snaps); */
+    Snap *tmp_snap = NULL;
+    char *tip = NULL;
+
+    if (new_pos == -1)
+        return FALSE;
+
+    if (new_pos != old_pos)
+    {
+        old_pos = new_pos;
+        return FALSE;
+    }
+
+    if (new_pos >= ts->priv->num_snaps)
+        return FALSE;
+
+    tmp_snap = g_list_nth_data (ts->priv->snaps, new_pos);
+
+    tip = g_strdup_printf ("%s\nCreated: %s\nSize: %s\nName: %s", tmp_snap->type_str, tmp_snap->mtime_str, tmp_snap->used_space_str, tmp_snap->name);
+    gtk_label_set_text (GTK_LABEL(ts->priv->label_tip), tip);
+    gtk_tooltip_set_custom (tooltip, ts->priv->label_tip);
+    g_free (tip);
+    return TRUE;
+}
+
+static void
+timescale_class_init (TimeScaleClass *class)
+{
+    GtkWidgetClass *widget_class;
+    GtkScaleClass *scale_class;
+    GObjectClass *object_class;
+
+    object_class = G_OBJECT_CLASS (class);
+    g_type_class_add_private (class, sizeof (TimeScalePrivate));
+
+    widget_class = GTK_WIDGET_CLASS (class);
+
+    signals[VALUE_CHANGED] =
+        g_signal_new ("value-changed",
+                G_TYPE_FROM_CLASS (object_class),
+                G_SIGNAL_RUN_LAST,
+                G_STRUCT_OFFSET (TimeScaleClass, value_changed),
+                NULL, NULL,
+                g_cclosure_marshal_VOID__VOID,
+                G_TYPE_NONE, 0);
+}
+
components/desktop/mate/caja/patches/Archiv/15-timescale.patch2
New file
@@ -0,0 +1,61 @@
--- caja-1.28.0/src/timescale.h.orig    2024-02-26 08:42:41.085975314 +0100
+++ caja-1.28.0/src/timescale.h    2024-02-26 08:42:41.085928246 +0100
@@ -0,0 +1,58 @@
+#ifndef __TIMESCALE_H__
+#define __TIMESCALE_H__
+
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+#define TYPE_TIMESCALE            (timescale_get_type ())
+#define TIMESCALE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TIMESCALE, TimeScale))
+#define TIMESCALE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TIMESCALE, TimeScaleClass))
+#define IS_TIMESCALE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TIMESCALE))
+#define IS_TIMESCALE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TIMESCALE))
+#define TIMESCALE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_TIMESCALE, TimeScaleClass))
+
+
+typedef struct _TimeScale       TimeScale;
+typedef struct _TimeScaleClass  TimeScaleClass;
+typedef struct TimeScalePrivate TimeScalePrivate;
+
+struct _TimeScale
+{
+  GtkHBox hbox;
+  TimeScalePrivate *priv;
+};
+
+struct _TimeScaleClass
+{
+  GtkHBoxClass parent_class;
+  void (* value_changed)    (TimeScale *timescale);
+
+};
+
+
+GType      timescale_get_type       (void) G_GNUC_CONST;
+GtkWidget* timescale_new            ();
+void       timescale_set_snapshots (TimeScale* ts, GList *list, int init_position);
+int       timescale_get_position   (TimeScale* ts);
+void       timescale_set_position   (TimeScale* ts, char *mountpoint);
+
+
+#define ROUNDED_RADIUS 6
+#define PADDING 3
+
+typedef enum {
+  LOCAL_MANUAL,
+  LOCAL_AUTOMATIC,
+  REMOTE_MANUAL,
+  REMOTE_AUTOMATIC,
+  NOW
+} SnapType;
+
+
+G_END_DECLS
+
+#endif /* __TIMESCALE_H__ */