/* $Id$ Copyright (C) 2003-2010 tooar Portions copyright (C) 1999 Michael Clark This file is part of emelFM2. emelFM2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses. */ /** @file plugins/e2p_du.c @brief plugin for determining the disk usage of selected items */ #include "emelfm2.h" //#include #include #include #include #include #include "e2_filelist.h" #include "e2_plugins.h" // binary factor #define BFAC 1024.0 #define BFAC2 1048576.0 #define BFAC3 1073741824.0 typedef struct _E2_Du { guint64 total; guint64 files; guint64 dirs; gboolean hidden; } E2_Du; /** @brief update du counters This is a callback for the treewalk function Error message expects BGL to be open/off @param localpath absolute path of item reported by the walker, localised string @param statptr pointer to struct stat with data about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to data struct with total, files, dirs counters @return E2TW_CONTINUE always */ static E2_TwResult _e2p_du_twcb (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_Du *user_data) { guint64 thissize; const gchar *ptr; ptr = strrchr (VPSTR (localpath), G_DIR_SEPARATOR); if (ptr == NULL) ptr = VPCSTR(localpath); else ptr++; //skip the / if (ITEM_ISHIDDEN (ptr)) user_data->hidden = TRUE; switch (status) { case E2TW_F: //not directory or link case E2TW_SL: //symbolic link case E2TW_SLN: //symbolic link naming non-existing file user_data->files++; if (statptr->st_dev > 0) //CHECKME { thissize = statptr->st_blocks * statptr->st_blksize; if (thissize > statptr->st_size) thissize = statptr->st_size; user_data->total += thissize; } break; case E2TW_DM: //directory, not opened due to different file system (reported upstream) case E2TW_DL: //directory, not opened due to tree-depth limit (reported upstream) case E2TW_DNR: //unreadable directory (for which, error is reported upstream) case E2TW_DRR: //directory now readable case E2TW_D: //directory user_data->dirs++; if (statptr->st_dev > 0) //CHECKME { thissize = statptr->st_blocks * statptr->st_blksize; if (thissize > statptr->st_size) thissize = statptr->st_size; user_data->total += thissize; } break; // case E2TW_DP: //dir finished // case E2TW_NS: //un-stattable item (for which, error is reported upstream) default: break; } return E2TW_CONTINUE; } /** @brief thread function to iterate over active pane file list to get sizes @param localpattern An allocated, localised, string, filename-pattern (possibly-wildcarded) to be matched before sizing a selected item or within a selected dir, or NULL to check all items @return NULL */ static gpointer _e2p_du_all (gchar *localpattern) { //FIXME with E2_ASYNC, curr_view etc is not necessarily relevant TABLOG (curr_tab); //pointer to tab which was current when the thread was initiated FileInfo *info; guint64 total, files, dirs; gboolean hashidden; GString *text; gchar *curr_local, *local_path; #ifdef E2_VFS VPATH ddata; #endif //all counters initialized to 0 E2_Du *cbdata = ALLOCATE0 (E2_Du); CHECKALLOCATEDWARN (cbdata, return NULL;) e2_filelist_disable_one_refresh (PANEACTIVE); #ifdef E2_VFS ddata.spacedata = curr_view->spacedata; #endif curr_local = D_FILENAME_TO_LOCALE (curr_view->dir);//always dup, to avoid dirchange race if (localpattern == NULL) { GList *base, *tmp; base = e2_fileview_get_selected_local (curr_view); for (tmp = base; tmp != NULL; tmp = tmp->next) { info = tmp->data; // printd (DEBUG, "fn: %s", info->filename); local_path = e2_utils_strcat (curr_local, info->filename); #ifdef E2_VFS ddata.path = local_path; #endif //if (! #ifdef E2_VFS e2_fs_tw (&ddata, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #else e2_fs_tw (local_path, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #endif //) //{ //FIXME handle error //} g_free (local_path); } g_list_free (base); } else //localpattern != NULL { GtkTreeModel *mdl; GtkTreeIter iter; if (GTK_IS_TREE_MODEL_FILTER (curr_view->model)) mdl = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (curr_view->model)); else mdl = curr_view->model; if (gtk_tree_model_get_iter_first (mdl, &iter)) { do { gtk_tree_model_get (mdl, &iter, FINFO, &info, -1); if (strcmp (info->filename, "..") == 0) //the user may have chosen to have this item in the list continue; gint result = fnmatch (localpattern, info->filename, FNM_PATHNAME | FNM_PERIOD); if (result == FNM_NOMATCH) continue; else if (result != 0) { // gdk_threads_enter (); //TODO display error // gdk_threads_leave (); continue; } local_path = e2_utils_strcat (curr_local, info->filename); #ifdef E2_VFS ddata.path = local_path; e2_fs_tw (&ddata, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #else e2_fs_tw (local_path, _e2p_du_twcb, cbdata, -1, E2TW_PHYS E2_ERR_NONE()); #endif g_free (local_path); } while (gtk_tree_model_iter_next (mdl, &iter)); } } e2_filelist_enable_one_refresh (PANEACTIVE); g_free (curr_local); total = cbdata->total; files = cbdata->files; dirs = cbdata->dirs; hashidden = cbdata->hidden; DEALLOCATE (E2_Du, cbdata); static gchar big[3] = { '1', ',', '\0' }; gchar *comma = nl_langinfo (THOUSEP); if (comma != NULL && *comma != '\0') big[1] = *comma; text = g_string_new(_("total size: ")); gint fwidth; if (total < BFAC) { gchar *b = _("bytes"); if (total < 1000) g_string_append_printf (text, "%"PRIu64" %s", total, b); else { total -= 1000; g_string_append_printf (text, "%s%03"PRIu64" %s", big, total, b); } } else if (total < BFAC2) { gchar *kb = _("kilobytes"); fwidth = (total < 10 * BFAC) ? 3 : 2; if ((total/BFAC) < 1000) g_string_append_printf (text, "%.*f %s", fwidth, (total / BFAC), kb); else { total -= (1000 * BFAC); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC), kb); } } else if (total < BFAC3) { gchar *mb = _("Megabytes"); fwidth = (total < 10 * BFAC2) ? 3 : 1; if ((total/BFAC2) < 1000) g_string_append_printf (text, "%.*f %s", fwidth, (total / BFAC2), mb); else { total -= (1000 * BFAC2); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC2), mb); } } else { gchar *gb = _("gigabytes"); fwidth = (total < 10 * BFAC3) ? 3 : 1; if ((total/BFAC3) < 1000) g_string_append_printf (text, "%.*f %s", fwidth, (total / BFAC3), gb); else { total -= (1000 * BFAC3); g_string_append_printf (text, "%s%04.1f %s", big, (total / BFAC3), gb); } } gchar *filetext = (files == 1) ? _("file") : _("files"); gchar *dirtext = (dirs == 1) ? _("directory") : _("directories"); g_string_append_printf (text, "\n%s %"PRIu64" %s %s %"PRIu64" %s", _("in"), files, filetext, _("and"), dirs, dirtext); if (localpattern != NULL) { g_string_append_printf (text, _(" named or in directories named '%s'\n"), localpattern); } else { if (hashidden && (files > 0 || dirs > 0)) g_string_append_printf (text, " %s\n", _("(one or more are hidden)")); else text = g_string_append_c (text, '\n'); } gdk_threads_enter (); e2_output_print_same (text->str); gdk_threads_leave (); E2_OutputTabRuntime *tab = (_e2t1_ == curr_tab) ? &app.tab : _e2t1_; gdk_threads_enter (); e2_output_print_end (tab, FALSE); gdk_threads_leave (); g_string_free (text, TRUE); if (localpattern != NULL) g_free (localpattern); return NULL; } /** @brief du plugin action This creates a thread to iterate over active pane selected items to recursively determine their disk usage Action-data may be a (UTF-8) string representing the name or wildcarded-pattern of items to be included in the scan. In that case, non-matching directories are not counted per se, but any matching contents are counted. @param from the button, menu item etc which was activated @param art action runtime data @return TRUE */ static gboolean _e2p_du (gpointer from, E2_ActionRuntime *art) { gchar *pattern; //file name-pattern to match if (art->data != NULL) { pattern = e2_utils_unquote_string ((const gchar *) art->data); //cleared in thread-func if (pattern != NULL) { if (G_LIKELY (strcmp (pattern, "*") != 0)) { gchar *freeme = pattern; #if 0 //TODO make sense of macros e.g. multiple names, or a directory path //interpret any macros pattern = e2_utils_expand_macros (freeme, NULL); g_free (freeme); if (pattern == NULL) { e2_output_print_error (_("Failed to expand macros"), FALSE); //current tab return FALSE; } else if (pattern == GINT_TO_POINTER(1)) { //the user cancelled in a prompt macro return FALSE; } //replace ~ and $variable[s] freeme = pattern; pattern = e2_utils_replace_vars (freeme, TRUE); g_free (freeme); freeme = pattern; #endif pattern = D_FILENAME_TO_LOCALE (pattern); g_free (freeme); } else { g_free (pattern); pattern = NULL; } } } else pattern = NULL; #ifdef USE_GLIB2_32 g_thread_new ("", (GThreadFunc)_e2p_du_all, pattern); #else g_thread_create ((GThreadFunc)_e2p_du_all, pattern, FALSE, NULL); #endif return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "du" aname = _("du"); p->signature = ANAME VERSION; p->menu_name = _("_Disk usage"); p->description = _("Calculate the disk usage of selected item(s)"); p->icon = "plugin_"ANAME E2ICONTB; //icon file pathname if (p->action == NULL) { //don't free name string here E2_Action plugact = {g_strconcat (_A(6),".",aname,NULL),_e2p_du,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL}; p->action = e2_plugins_action_register (&plugact); if G_LIKELY((p->action != NULL)) return TRUE; g_free (plugact.name); } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(6),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); return ret; }