42 files added
3 files modified
2 files renamed
| | |
| | | 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 |
| | | |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
| | |
| | | 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 |
New file |
| | |
| | | --- 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) |
New file |
| | |
| | | --- 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); |
| | | +} |
| | | + |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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) |
| | | { |
New file |
| | |
| | | --- 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) |
New file |
| | |
| | | --- 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> |
New file |
| | |
| | | --- 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" |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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/> |
New file |
| | |
| | | --- 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); |
| | | } |
New file |
| | |
| | | --- 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"), |
New file |
| | |
| | | --- 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; |
| | | } |
| | | |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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 = \ |
New file |
| | |
| | | --- 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__ */ |
New file |
| | |
| | | --- 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 */ |
New file |
| | |
| | | --- 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); |
| | | +} |
| | | + |
New file |
| | |
| | | --- 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; |
| | | }; |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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"/> |
New file |
| | |
| | | --- 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, |
New file |
| | |
| | | --- 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) |
| | | { |
New file |
| | |
| | | --- 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; |
| | | } |
| | | |
New 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" |
New file |
| | |
| | | --- 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; |
| | | + |
| | | +} |
| | | + |
New file |
| | |
| | | --- 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 */ |
New file |
| | |
| | | --- 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 =\ |
New file |
| | |
| | | --- 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> |
New file |
| | |
| | | --- 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); |
| | | } |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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) |
| | | { |
New file |
| | |
| | | --- 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 */ |
New file |
| | |
| | | --- 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 */ |
New file |
| | |
| | | --- 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; |
| | | |
New file |
| | |
| | | --- 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; |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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"), |
New file |
| | |
| | | --- 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"/> |
New file |
| | |
| | | --- 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" |
New file |
| | |
| | | --- 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); |
New file |
| | |
| | | --- 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); |
| | | +} |
| | | + |
New file |
| | |
| | | --- 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__ */ |