From e3f6f2c3f9a81740833756aeb6ba285e1c6e014e Mon Sep 17 00:00:00 2001
From: Andreas Wacknitz <A.Wacknitz@gmx.de>
Date: Mon, 26 Feb 2024 21:14:23 +0100
Subject: [PATCH] caja: update to 1.28.0

---
 components/desktop/mate/caja/Makefile                                                    |    7 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14                   |   14 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13                   |   23 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12                   |   78 
 components/desktop/mate/caja/patches/Archiv/15-timescale.patch1                          | 1289 ++++++
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11                   | 1242 ++++++
 components/desktop/mate/caja/patches/Archiv/15-timescale.patch2                          |   61 
 components/desktop/mate/caja/patches/Archiv/02-time-slider-part2.patch.org               |    0 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10                   |   12 
 components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch |   20 
 components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1             |   10 
 components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch          |  183 
 components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2             |   31 
 components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3             |  142 
 components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch                     |   22 
 components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1                  |   58 
 components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch                    |   54 
 components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch                      |   10 
 components/desktop/mate/caja/patches/15-timeslider-timescale.patch                       | 1350 ++++++
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1                    |   17 
 components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2                       |   12 
 components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3                       |  271 +
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3                    |   21 
 components/desktop/mate/caja/manifests/sample-manifest.p5m                               |    3 
 components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2                   |   16 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2                    |   57 
 components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1                       |   30 
 components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1                   |   10 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5                    |   44 
 components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch        |  184 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4                    |  282 +
 components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch                 | 2835 ++++++++++++++
 components/desktop/mate/caja/patches/12-timeslider-caja-window.patch                     |   49 
 components/desktop/mate/caja/patches/Archiv/01-time-slider.patch.org                     |    0 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7                    |   30 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6                    |   10 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9                    |   38 
 components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8                    |  967 ++++
 components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch                    |  913 ++++
 components/desktop/mate/caja/caja.p5m                                                    |    3 
 components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch                    |   10 
 components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2                       |  854 ++++
 components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1                       |   59 
 components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch                |   26 
 components/desktop/mate/caja/patches/13-timeslider-file-manager.patch                    |  313 +
 components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch               |   20 
 components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch               |   58 
 47 files changed, 11,728 insertions(+), 10 deletions(-)

diff --git a/components/desktop/mate/caja/Makefile b/components/desktop/mate/caja/Makefile
index 6f087ea..c1590dc 100644
--- a/components/desktop/mate/caja/Makefile
+++ b/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
 
diff --git a/components/desktop/mate/caja/caja.p5m b/components/desktop/mate/caja/caja.p5m
index 8af9244..7a32d37 100644
--- a/components/desktop/mate/caja/caja.p5m
+++ b/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
diff --git a/components/desktop/mate/caja/manifests/sample-manifest.p5m b/components/desktop/mate/caja/manifests/sample-manifest.p5m
index 088b8c8..0805d04 100644
--- a/components/desktop/mate/caja/manifests/sample-manifest.p5m
+++ b/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
diff --git a/components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch b/components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch
new file mode 100644
index 0000000..cb00e4c
--- /dev/null
+++ b/components/desktop/mate/caja/patches/01-timeslider-configure.ac.patch
@@ -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)
diff --git a/components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch b/components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch
new file mode 100644
index 0000000..50f5353
--- /dev/null
+++ b/components/desktop/mate/caja/patches/02-timeslider-caja-zfs-bar.patch
@@ -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);
++}
++
diff --git a/components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch b/components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch
new file mode 100644
index 0000000..2c5fa29
--- /dev/null
+++ b/components/desktop/mate/caja/patches/03-timeslider-caja-window-slot.patch
@@ -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);
diff --git a/components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch b/components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch
new file mode 100644
index 0000000..784f124
--- /dev/null
+++ b/components/desktop/mate/caja/patches/04-timeslider-caja-navigation-window.patch
@@ -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)
+ {
diff --git a/components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch b/components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch
new file mode 100644
index 0000000..6c9bd80
--- /dev/null
+++ b/components/desktop/mate/caja/patches/05-timeslider-icons-Makefile.am.patch
@@ -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)
diff --git a/components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch b/components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch
new file mode 100644
index 0000000..60b390c
--- /dev/null
+++ b/components/desktop/mate/caja/patches/06-timeslider-libcaja-private.patch
@@ -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>
diff --git a/components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch b/components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch
new file mode 100644
index 0000000..7a1be0f
--- /dev/null
+++ b/components/desktop/mate/caja/patches/07-timeslider-caja-actions.patch
@@ -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"
diff --git a/components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch b/components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch
new file mode 100644
index 0000000..c676b6d
--- /dev/null
+++ b/components/desktop/mate/caja/patches/08-timeslider-caja-file-management-properties.patch
@@ -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);
diff --git a/components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch b/components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch
new file mode 100644
index 0000000..29d1066
--- /dev/null
+++ b/components/desktop/mate/caja/patches/09-timeslider-caja-shell.patch
@@ -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/>
diff --git a/components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch b/components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch
new file mode 100644
index 0000000..f5ef421
--- /dev/null
+++ b/components/desktop/mate/caja/patches/10-timeslider-caja-window-manage-views.patch
@@ -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);
+ }
diff --git a/components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch b/components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch
new file mode 100644
index 0000000..5d7a617
--- /dev/null
+++ b/components/desktop/mate/caja/patches/11-timeslider-caja-window-menus.patch
@@ -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"),
diff --git a/components/desktop/mate/caja/patches/12-timeslider-caja-window.patch b/components/desktop/mate/caja/patches/12-timeslider-caja-window.patch
new file mode 100644
index 0000000..3e9738e
--- /dev/null
+++ b/components/desktop/mate/caja/patches/12-timeslider-caja-window.patch
@@ -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;
+         }
+ 
diff --git a/components/desktop/mate/caja/patches/13-timeslider-file-manager.patch b/components/desktop/mate/caja/patches/13-timeslider-file-manager.patch
new file mode 100644
index 0000000..2c1bfcf
--- /dev/null
+++ b/components/desktop/mate/caja/patches/13-timeslider-file-manager.patch
@@ -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);
diff --git a/components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch b/components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch
new file mode 100644
index 0000000..d2b42df
--- /dev/null
+++ b/components/desktop/mate/caja/patches/14-timeslider-Makefile.am.patch
@@ -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 = \
diff --git a/components/desktop/mate/caja/patches/15-timeslider-timescale.patch b/components/desktop/mate/caja/patches/15-timeslider-timescale.patch
new file mode 100644
index 0000000..6377a90
--- /dev/null
+++ b/components/desktop/mate/caja/patches/15-timeslider-timescale.patch
@@ -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__ */
diff --git a/components/desktop/mate/caja/patches/01-time-slider.patch b/components/desktop/mate/caja/patches/Archiv/01-time-slider.patch.org
similarity index 100%
rename from components/desktop/mate/caja/patches/01-time-slider.patch
rename to components/desktop/mate/caja/patches/Archiv/01-time-slider.patch.org
diff --git a/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1 b/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1
new file mode 100644
index 0000000..751a943
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch1
@@ -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 */
diff --git a/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2 b/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2
new file mode 100644
index 0000000..5011849
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/02-caja-zfs-bar.patch2
@@ -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);
++}
++
diff --git a/components/desktop/mate/caja/patches/02-time-slider-part2.patch b/components/desktop/mate/caja/patches/Archiv/02-time-slider-part2.patch.org
similarity index 100%
rename from components/desktop/mate/caja/patches/02-time-slider-part2.patch
rename to components/desktop/mate/caja/patches/Archiv/02-time-slider-part2.patch.org
diff --git a/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1 b/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1
new file mode 100644
index 0000000..8d1ba8f
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch1
@@ -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;
+ };
diff --git a/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2 b/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2
new file mode 100644
index 0000000..a18810a
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/03-caja-window-slot.patch2
@@ -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);
diff --git a/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1 b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1
new file mode 100644
index 0000000..952b40d
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch1
@@ -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"/>
diff --git a/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2 b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2
new file mode 100644
index 0000000..4d0e7d1
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch2
@@ -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,
diff --git a/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3 b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3
new file mode 100644
index 0000000..4dd057d
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/04-caja-navigation-window.patch3
@@ -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)
+ {
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1
new file mode 100644
index 0000000..5992866
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch1
@@ -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;
+ }
+ 
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10
new file mode 100644
index 0000000..17d5286
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch10
@@ -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"
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11
new file mode 100644
index 0000000..45e7d9f
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch11
@@ -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;
++
++}
++
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12
new file mode 100644
index 0000000..bdb183e
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch12
@@ -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 */
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13
new file mode 100644
index 0000000..03a0e3e
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch13
@@ -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 =\
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14
new file mode 100644
index 0000000..7d93473
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch14
@@ -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>
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2
new file mode 100644
index 0000000..e23468b
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch2
@@ -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);
+ }
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3
new file mode 100644
index 0000000..fd921ec
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch3
@@ -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);
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4
new file mode 100644
index 0000000..ff9fda0
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch4
@@ -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)
+ {
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5
new file mode 100644
index 0000000..d0fdf7c
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch5
@@ -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 */
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6
new file mode 100644
index 0000000..e009f1e
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch6
@@ -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 */
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7
new file mode 100644
index 0000000..e15d45f
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch7
@@ -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;
+ 
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8
new file mode 100644
index 0000000..346c463
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch8
@@ -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;
diff --git a/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9 b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9
new file mode 100644
index 0000000..5b81e61
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/06-libcaja-private.patch9
@@ -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);
diff --git a/components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1 b/components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1
new file mode 100644
index 0000000..c1d4b7f
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/11-caja-window-menus.patch1
@@ -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"),
diff --git a/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1 b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1
new file mode 100644
index 0000000..3efc02c
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch1
@@ -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"/>
diff --git a/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2 b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2
new file mode 100644
index 0000000..6585d87
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch2
@@ -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"
diff --git a/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3 b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3
new file mode 100644
index 0000000..fcad934
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/13-file-manager.patch3
@@ -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);
diff --git a/components/desktop/mate/caja/patches/Archiv/15-timescale.patch1 b/components/desktop/mate/caja/patches/Archiv/15-timescale.patch1
new file mode 100644
index 0000000..1e7559b
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/15-timescale.patch1
@@ -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);
++}
++
diff --git a/components/desktop/mate/caja/patches/Archiv/15-timescale.patch2 b/components/desktop/mate/caja/patches/Archiv/15-timescale.patch2
new file mode 100644
index 0000000..5834235
--- /dev/null
+++ b/components/desktop/mate/caja/patches/Archiv/15-timescale.patch2
@@ -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__ */

--
Gitblit v1.9.3