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