Blob Blame History Raw
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include <config.h>

#include <gtk/gtk.h>
#include <glibtop/mountlist.h>
#include <glibtop/fsusage.h>
#include <glib/gi18n.h>

#include "disks.h"
#include "application.h"
#include "util.h"
#include "settings-keys.h"
#include "legacy/treeview.h"

enum DiskColumns
{
    /* string columns* */
    DISK_DEVICE,
    DISK_DIR,
    DISK_TYPE,
    DISK_TOTAL,
    DISK_FREE,
    DISK_AVAIL,
    /* USED has to be the last column */
    DISK_USED,
    // then unvisible columns
    /* PixBuf column */
    DISK_ICON,
    /* numeric columns */
    DISK_USED_PERCENTAGE,
    DISK_N_COLUMNS
};

static void
cb_sort_changed (GtkTreeSortable *model, gpointer data)
{
    GsmApplication *app = (GsmApplication *) data;

    gsm_tree_view_save_state (GSM_TREE_VIEW (app->disk_list));
}

static void
fsusage_stats(const glibtop_fsusage *buf,
              guint64 *bused, guint64 *bfree, guint64 *bavail, guint64 *btotal,
              gint *percentage)
{
    guint64 total = buf->blocks * buf->block_size;

    if (!total) {
        /* not a real device */
        *btotal = *bfree = *bavail = *bused = 0ULL;
        *percentage = 0;
    } else {
        int percent;
        *btotal = total;
        *bfree = buf->bfree * buf->block_size;
        *bavail = buf->bavail * buf->block_size;
        *bused = *btotal - *bfree;
        /* percent = 100.0f * *bused / *btotal; */
        percent = 100 * *bused / (*bused + *bavail);
        *percentage = CLAMP(percent, 0, 100);
    }
}

static const char* get_icon_for_path(const char* path)
{
    GVolumeMonitor *monitor;
    GList *mounts;
    uint i;
    GMount *mount;
    GIcon *icon;
    const char* name = "";

    monitor = g_volume_monitor_get ();
    mounts = g_volume_monitor_get_mounts (monitor);

    for (i = 0; i < g_list_length (mounts); i++) {
        mount = G_MOUNT (g_list_nth_data(mounts, i));
        if (strcmp(g_mount_get_name(mount), path))
            continue;

        icon = g_mount_get_icon (mount);

        if (!icon)
            continue;
        name = g_icon_to_string (icon);
        g_object_unref (icon);
    }

    g_list_free_full (mounts, g_object_unref);
    return name;

}

static GdkPixbuf*
get_icon_for_device(const char *mountpoint)
{
    const char* icon_name = get_icon_for_path(mountpoint);
    if (!strcmp(icon_name, ""))
        // FIXME: defaults to a safe value
        icon_name = "drive-harddisk"; // get_icon_for_path("/");
    return gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), icon_name, 24, GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
}


static gboolean
find_disk_in_model(GtkTreeModel *model, const char *mountpoint,
                   GtkTreeIter *result)
{
    GtkTreeIter iter;
    gboolean found = FALSE;

    if (gtk_tree_model_get_iter_first(model, &iter)) {
        do {
            char *dir;

            gtk_tree_model_get(model, &iter,
                               DISK_DIR, &dir,
                               -1);

            if (dir && !strcmp(dir, mountpoint)) {
                *result = iter;
                found = TRUE;
            }

            g_free(dir);

        } while (!found && gtk_tree_model_iter_next(model, &iter));
    }

    return found;
}



static void
remove_old_disks(GtkTreeModel *model, const glibtop_mountentry *entries, guint n)
{
    GtkTreeIter iter;

    if (!gtk_tree_model_get_iter_first(model, &iter))
        return;

    while (true) {
        char *dir;
        guint i;
        gboolean found = FALSE;

        gtk_tree_model_get(model, &iter,
                           DISK_DIR, &dir,
                           -1);

        for (i = 0; i != n; ++i) {
            if (!strcmp(dir, entries[i].mountdir)) {
                found = TRUE;
                break;
            }
        }

        g_free(dir);

        if (!found) {
            if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter))
                break;
            else
                continue;
        }

        if (!gtk_tree_model_iter_next(model, &iter))
            break;
    }
}



static void
add_disk(GtkListStore *list, const glibtop_mountentry *entry, bool show_all_fs)
{
    GdkPixbuf* pixbuf;
    GtkTreeIter iter;
    glibtop_fsusage usage;
    guint64 bused, bfree, bavail, btotal;
    gint percentage;

    glibtop_get_fsusage(&usage, entry->mountdir);

    if (not show_all_fs and usage.blocks == 0) {
        if (find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
            gtk_list_store_remove(list, &iter);
        return;
    }

    fsusage_stats(&usage, &bused, &bfree, &bavail, &btotal, &percentage);
    pixbuf = get_icon_for_device(entry->mountdir);

    /* if we can find a row with the same mountpoint, we get it but we
       still need to update all the fields.
       This makes selection persistent.
    */
    if (!find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
        gtk_list_store_append(list, &iter);

    gtk_list_store_set(list, &iter,
                       DISK_ICON, pixbuf,
                       DISK_DEVICE, entry->devname,
                       DISK_DIR, entry->mountdir,
                       DISK_TYPE, entry->type,
                       DISK_USED_PERCENTAGE, percentage,
                       DISK_TOTAL, btotal,
                       DISK_FREE, bfree,
                       DISK_AVAIL, bavail,
                       DISK_USED, bused,
                       -1);
}

static void
mount_changed (GVolumeMonitor *monitor, GMount *mount, GsmApplication *app)
{
    disks_update(app);
}

static gboolean
cb_timeout (gpointer data)
{
    GsmApplication *app = (GsmApplication *) data;
    disks_update (app);

    return G_SOURCE_CONTINUE;
}

void
disks_update(GsmApplication *app)
{
    GtkListStore *list;
    glibtop_mountentry * entries;
    glibtop_mountlist mountlist;
    guint i;
    gboolean show_all_fs;

    list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->disk_list)));
    show_all_fs = app->settings->get_boolean (GSM_SETTING_SHOW_ALL_FS);
    entries = glibtop_get_mountlist (&mountlist, show_all_fs);

    remove_old_disks(GTK_TREE_MODEL(list), entries, mountlist.number);

    for (i = 0; i < mountlist.number; i++)
        add_disk(list, &entries[i], show_all_fs);

    g_free(entries);
}

static void
init_volume_monitor (GsmApplication *app)
{
    GVolumeMonitor *monitor = g_volume_monitor_get ();

    g_signal_connect (monitor, "mount-added", G_CALLBACK (mount_changed), app);
    g_signal_connect (monitor, "mount-changed", G_CALLBACK (mount_changed), app);
    g_signal_connect (monitor, "mount-removed", G_CALLBACK (mount_changed), app);
}

void
disks_freeze (GsmApplication *app)
{
  if (app->disk_timeout) {
      g_source_remove (app->disk_timeout);
      app->disk_timeout = 0;
  }
}

void
disks_thaw (GsmApplication *app)
{
  if (app->disk_timeout)
      return;

  app->disk_timeout = g_timeout_add (app->config.disks_update_interval,
                                     cb_timeout,
                                     app);
}

void
disks_reset_timeout (GsmApplication *app)
{
    disks_freeze (app);
    disks_thaw (app);
}

static void
cb_disk_columns_changed(GtkTreeView *treeview, gpointer data)
{
    gsm_tree_view_save_state (GSM_TREE_VIEW (treeview));
}


static void open_dir(GtkTreeView       *tree_view,
                     GtkTreePath       *path,
                     GtkTreeViewColumn *column,
                     gpointer          user_data)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    char *dir, *url;

    model = gtk_tree_view_get_model(tree_view);

    if (!gtk_tree_model_get_iter(model, &iter, path)) {
        char *p;
        p = gtk_tree_path_to_string(path);
        g_warning("Cannot get iter for path '%s'\n", p);
        g_free(p);
        return;
    }

    gtk_tree_model_get(model, &iter, DISK_DIR, &dir, -1);

    url = g_strdup_printf("file://%s", dir);

    GError* error = 0;
    if (!g_app_info_launch_default_for_uri(url, NULL, &error)) {
        g_warning("Cannot open '%s' : %s\n", url, error->message);
        g_error_free(error);
    }

    g_free(url);
    g_free(dir);
}

static void
cb_disk_list_destroying (GtkWidget *self, gpointer data)
{
    g_signal_handlers_disconnect_by_func(self, (gpointer) cb_disk_columns_changed, data);
    
    g_signal_handlers_disconnect_by_func (gtk_tree_view_get_model (GTK_TREE_VIEW(self)),
                                          (gpointer) cb_sort_changed,
                                          data);
}


void
create_disk_view(GsmApplication *app, GtkBuilder *builder)
{
    GtkScrolledWindow *scrolled;
    GsmTreeView *disk_tree;
    GtkListStore *model;
    GtkTreeViewColumn *col;
    GtkCellRenderer *cell;
    guint i;

    init_volume_monitor (app);
    const gchar * const titles[] = {
        N_("Device"),
        N_("Directory"),
        N_("Type"),
        N_("Total"),
        N_("Free"),
        N_("Available"),
        N_("Used")
    };

    scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "disks_scrolled"));

    model = gtk_list_store_new(DISK_N_COLUMNS,      /* n columns */
                               G_TYPE_STRING,       /* DISK_DEVICE */
                               G_TYPE_STRING,       /* DISK_DIR */
                               G_TYPE_STRING,       /* DISK_TYPE */
                               G_TYPE_UINT64,       /* DISK_TOTAL */
                               G_TYPE_UINT64,       /* DISK_FREE */
                               G_TYPE_UINT64,       /* DISK_AVAIL */
                               G_TYPE_UINT64,       /* DISK_USED */
                               GDK_TYPE_PIXBUF,     /* DISK_ICON */
                               G_TYPE_INT           /* DISK_USED_PERCENTAGE */
        );
    disk_tree = gsm_tree_view_new (g_settings_get_child (app->settings->gobj(), GSM_SETTINGS_CHILD_DISKS), TRUE);
    gtk_tree_view_set_model (GTK_TREE_VIEW (disk_tree), GTK_TREE_MODEL (model));

    g_signal_connect(G_OBJECT(disk_tree), "row-activated", G_CALLBACK(open_dir), NULL);
    app->disk_list = disk_tree;
    gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET (disk_tree));
    g_object_unref(G_OBJECT(model));

    /* icon + device */

    col = gtk_tree_view_column_new();
    cell = gtk_cell_renderer_pixbuf_new();
    
    gtk_tree_view_column_pack_start(col, cell, FALSE);
    gtk_tree_view_column_set_min_width(col, 30);
    gtk_tree_view_column_set_attributes(col, cell, "pixbuf", DISK_ICON,
                                        NULL);

    cell = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(col, cell, FALSE);
    gtk_tree_view_column_set_attributes(col, cell, "text", DISK_DEVICE,
                                        NULL);
    gtk_tree_view_column_set_title(col, _(titles[DISK_DEVICE]));
    gtk_tree_view_column_set_sort_column_id(col, DISK_DEVICE);
    gtk_tree_view_column_set_reorderable(col, TRUE);
    gtk_tree_view_column_set_resizable(col, TRUE);
    gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
    gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);


    /* sizes - used */

    for (i = DISK_DIR; i <= DISK_AVAIL; i++) {
        cell = gtk_cell_renderer_text_new();
        col = gtk_tree_view_column_new();
        gtk_tree_view_column_pack_start(col, cell, TRUE);
        gtk_tree_view_column_set_title(col, _(titles[i]));
        gtk_tree_view_column_set_resizable(col, TRUE);
        gtk_tree_view_column_set_sort_column_id(col, i);
        gtk_tree_view_column_set_reorderable(col, TRUE);
        gtk_tree_view_column_set_min_width(col, i == DISK_TYPE ? 40 : 72);
        gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
        gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
        switch (i) {
            case DISK_TOTAL:
            case DISK_FREE:
            case DISK_AVAIL:
                g_object_set(cell, "xalign", 1.0f, NULL);
                gtk_tree_view_column_set_cell_data_func(col, cell,
                                                        &procman::size_si_cell_data_func,
                                                        GUINT_TO_POINTER(i),
                                                        NULL);
                break;

            default:
                gtk_tree_view_column_set_attributes(col, cell,
                                                    "text", i,
                                                    NULL);
                break;
        }
    }

    /* used + percentage */

    col = gtk_tree_view_column_new();
    cell = gtk_cell_renderer_text_new();
    g_object_set(cell, "xalign", 1.0f, NULL);
    gtk_tree_view_column_set_min_width(col, 72);
    gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
    gtk_tree_view_column_pack_start(col, cell, FALSE);
    gtk_tree_view_column_set_cell_data_func(col, cell,
                                            &procman::size_si_cell_data_func,
                                            GUINT_TO_POINTER(DISK_USED),
                                            NULL);
    gtk_tree_view_column_set_title(col, _(titles[DISK_USED]));

    cell = gtk_cell_renderer_progress_new();
    gtk_cell_renderer_set_padding(cell, 4.0f, 4.0f);
    gtk_tree_view_column_pack_start(col, cell, TRUE);
    gtk_tree_view_column_set_attributes(col, cell, "value",
                                        DISK_USED_PERCENTAGE, NULL);
    gtk_tree_view_column_set_resizable(col, TRUE);
    gtk_tree_view_column_set_sort_column_id(col, DISK_USED);
    gtk_tree_view_column_set_reorderable(col, TRUE);
    gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);

    /* numeric sort */

    gsm_tree_view_load_state (GSM_TREE_VIEW (disk_tree));
    g_signal_connect (G_OBJECT(disk_tree), "destroy",
                      G_CALLBACK(cb_disk_list_destroying),
                      app);

    g_signal_connect (G_OBJECT(disk_tree), "columns-changed",
                      G_CALLBACK(cb_disk_columns_changed), app);
                      
    g_signal_connect (G_OBJECT (model), "sort-column-changed",
                      G_CALLBACK (cb_sort_changed), app);

    app->settings->signal_changed(GSM_SETTING_SHOW_ALL_FS).connect ([app](const Glib::ustring&) { disks_update (app); disks_reset_timeout (app); });

    gtk_widget_show (GTK_WIDGET (disk_tree));
}