/* $Id$ * * Copyright (C) 2006-2007 tooar * * 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 2, 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, contact the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* TODO retain tooltips through toolbar recreation (filters too) when to use|change|add history row data (add if ID different), and how many ID determinaton for vfs data quark based on proto+host+path ? connection title ? clear p/w for new history record with insensitive p/w entry en/decrypt cached passwords en/decrypt alt dialog passwords activation from alt dialog N/A due to cell editing ? activation from URI entered into a dirline when changing archive or changing fs type, re-pack current archive if 'dirty' enable nested opening of archives enable opening of archive on remote site change dir uses prepended real root, for archives displayed dirline omits prepended real root, for archives & remote dirline completion not to operate for remote dirs list, if not cache, browsed archive-dirs for each pane list, if not cache, browsed remote-dirs for each pane browse completion for archives at least default CWD for vfs places is a temp dir, usable for path prefix, extraction etc bookmarking vfs places ?? (as opposed to history) mirror task status line shows root revise action name for unpack plugin in filetypes default strings _("unpack_with_plugin") RPM unpack, re-pack processes 7z unpack, re-pack processes p/w archive access */ #include "emelfm2.h" #ifdef E2_VFS #include #include #include #include #include #include #include #include "e2_vfs.h" #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" /*typedef struct { gchar *utf_prefix; //absolute path of source archive, utf8-ized view->prefix gint pack_pid; //id of process which is re-packing an archive guint pack_timer_id; //id of timer which checks for repack completed FileView *view; //data for pane where the unpack is displayed } E2_Unpackdata; */ typedef struct { gchar *path; //the requested archive path, relative to root (utf8 string) GList *list; //list of E2_VFSArchNode's for each node GNode *parent; //store for matched node GList *entries; //list to store added FileInfo's gpointer parser; //pointer to gboolean (*func) (gchar *, FileInfo *) //which parses data-line into a FileInfo } E2_NodeFind; static gchar *_e2_vfs_get_path_for_tar (gchar *itemline); static gboolean _e2_vfs_make_node_data_for_tar (gchar *itemline, FileInfo *info); //FIXME make these arrays dynamic according to available libraries //these are in same order as E2_VFSProtocol enum in header //ensure RPROTOCOUNT in header is defined in header in accord with no of strings here gchar *remote_protocols[RPROTOCOUNT] = { "ftp", "ftps", "http", "https", "ssh", "fish", "fsp" }; //ensure APROTOCOUNT in header is defined in header in accord with no of strings here gchar *archive_protocols[APROTOCOUNT] = { "tar.bz2", "tar.gz", "tar", "zip", "7z", "arj", "rar", "rpm", // "deb" }; const gchar *listers[APROTOCOUNT] = { "tar -tjvf \"%s\"", //E2_VPROTO_TBZ2 "tar -tzvf \"%s\"", //E2_VPROTO_TGZ, "tar -tvf \"%s\"", //E2_VPROTO_TAR, "", //E2_VPROTO_ZIP, "", //E2_VPROTO_7Z, "", //E2_VPROTO_ARJ, "", //E2_VPROTO_RAR, "" //E2_VPROTO_RPM, //E2_VPROTO_DEB, //unsupported }; gpointer pathfuncs[APROTOCOUNT] = { _e2_vfs_get_path_for_tar, _e2_vfs_get_path_for_tar, _e2_vfs_get_path_for_tar, NULL, NULL, NULL, NULL, NULL }; gpointer parsefuncs[APROTOCOUNT] = { _e2_vfs_make_node_data_for_tar, _e2_vfs_make_node_data_for_tar, _e2_vfs_make_node_data_for_tar, NULL, NULL, NULL, NULL, NULL }; //static gboolean _e2_vfs_unpackQ (E2_ActionTaskData *qed); /** * @brief create a string-representation of a row in @a model * All converted data are assumed to be string or bool or [u]int * Column data are separated by '|' chars. Any leading '>' or contained '|' is * escaped. * c.f. e2_option_tree_row_write_to_string () which handles bool values differently * @param model the treemodel to process * @param iter treeiter with data for the row to be processed * @param columns this many columns in each row are converted, 0 or -1 to use them all * @param level tree path-depth of the processed row * * @return the constructed string **/ gchar *e2_tree_row_to_string (GtkTreeModel *model, GtkTreeIter *iter, gint columns, gint level) { gchar *str_data, *fmt; GString *treerow = g_string_sized_new (128); gint j; gint int_data; GType type; for (j = 0; j < level; j++) treerow = g_string_append_c (treerow, '\t'); if (columns < 1) columns = gtk_tree_model_get_n_columns (model); for (j = 0; j < columns; j++) { type = gtk_tree_model_get_column_type (model, j); if (type == G_TYPE_STRING) gtk_tree_model_get (model, iter, j, &str_data, -1); else if (type == G_TYPE_BOOLEAN || type == G_TYPE_INT || type == G_TYPE_UINT) { gtk_tree_model_get (model, iter, j, &int_data, -1); fmt = (type == G_TYPE_UINT) ? "%u" : "%d"; str_data = g_strdup_printf (fmt, int_data); } else str_data = NULL; if (str_data != NULL) { if (j == 0 && str_data[0] == '>') treerow = g_string_append (treerow, "\\"); if ((str_data[0] != '\0') && ((strchr (str_data, '|')) != NULL)) //if always ascii |, don't need g_utf8_strchr() { gchar **split = g_strsplit (str_data, "|", -1); g_free (str_data); str_data = g_strjoinv ("\\|", split); g_strfreev (split); } treerow = g_string_append (treerow, str_data); g_free (str_data); } treerow = g_string_append_c (treerow, '|'); } //get rid of superfluous trailing '|' treerow = g_string_truncate (treerow, treerow->len - 1); str_data = g_string_free (treerow, FALSE); return str_data; } /** * @brief make temporary directory * * @param mode octal permissions of new dir e.g. 0777 or 0644 * * @return newly-allocated utf8 string = path of new dir (no trailer), * or NULL if a problem occurs */ gchar *e2_fs_mktempdir (gint mode) { gchar *utfdir, *localdir, *unique; const gchar *maintmp = g_get_tmp_dir (); if (g_str_has_prefix (maintmp, g_get_home_dir ())) utfdir = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s", maintmp, BINNAME); else { //in shared space, add a clue as to which user, in case the dir is //left there afterwards gint myuid = getuid (); utfdir = g_strdup_printf ("%s"G_DIR_SEPARATOR_S"%s-%d", maintmp, BINNAME, myuid); } localdir = F_FILENAME_TO_LOCALE (utfdir); unique = e2_utils_get_tempname (localdir); F_FREE (localdir); g_free (utfdir); utfdir = D_FILENAME_FROM_LOCALE (unique); E2_ERR_DECLARE //make it if (e2_fs_recurse_mkdir (unique, mode E2_ERR_PTR())) { #ifdef E2_VFSTMP //FIXME use vfs error in message #endif gchar *msg = g_strdup_printf ("Cannot create a working directory '%s'", utfdir); e2_output_print_error (msg, TRUE); g_free (utfdir); E2_ERR_CLEAR return NULL; } return utfdir; } /** * @brief convert @a path into an absolute path string, representing the directory shown in @a view, and in a form according to @a type * If @a path is not absolute, the corresponding "active" dir is prepended. Then, * according to the @a type, other data may be prepended * @param path path string (utf8) to be processed * @param type enumerator of the type of string(s) to prepend to @a path * @param view pointer to view data struct. May be NULL if @a type requires data from curr_view. * * @return newly-allocated utf8 string **/ gchar *e2_utils_get_absolute_dirpath (gchar *path, E2_FSDirType type, FileView *view) { gchar *p1; if (type >= E2_DIRCUR_WORK || view == NULL) view = curr_view; if (g_path_is_absolute (path)) p1 = g_strdup (path); else p1 = g_strconcat (view->dir, path, NULL); if (type != E2_DIR_WORK && type != E2_DIRCUR_WORK) { gchar *p2 = p1; //FIXME p1 = g_strdup (view->dir); g_free (p2); } return p1; } /** * @brief get "working" directory for @a view, in a form according to @a type * The working directory is @a view ->dir, possibly, according to @a type, with * other data prepended * * @param type enumerator of the type of string to prepend to view->dir * @param view pointer to view data struct. May be NULL if @a type requires data from curr_view. * * @return newly-allocated utf8 string **/ gchar *e2_utils_get_work_dirpath (E2_FSDirType type, FileView *view) { gchar *p1; if (type >= E2_DIRCUR_WORK || view == NULL) view = curr_view; if (type == E2_DIR_WORK || type == E2_DIRCUR_WORK) p1 = g_strdup (view->dir); else { //FIXME p1 = g_strdup (view->dir); } return p1; } /** * @brief compare active and inactive panes' path strings, expressed in a form according to @a type * If the panes do not show both local, or both remote, or both archive, the * returned value will be FALSE. * @param type enumerator of the type of string, if any, to prepend to each view->dir * * @return TRUE if the paths are the same **/ gboolean e2_utils_compare_dirpaths (E2_FSDirType type) { gboolean retval, freep; gchar *p1, *p2; p1 = app.pane1.path; p2 = app.pane2.path; if (0) //FIXME { p1 = g_strdup (""); p2 = g_strdup (""); freep = TRUE; } else freep = FALSE; retval = g_str_equal (p1, p2); if (freep) { g_free (p1); g_free (p2); } return retval; } /** * @brief separate a string-representation of a tree row into pieces stored in parts * * Any leading '>' or contained '|' is unescaped * * @param line the string to be processed * @param columns this many columns in each row are converted * @param parts array of size @a columns to hold the results * * @return parts filled with newly-allocated strings, maybe "" **/ void e2_utils_rowstr_split (gchar *line, gint columns, gchar *parts[]) { //"unescape" the first character if need be if (line[0] == '\\' && line[1] == '>') line++; //strsplit() isn't a great approach because it makes //unescaping complicated, but ... gchar **split = g_strsplit (line, "|", -1); //tree column counter gint j; //split counter, increased when stepping through **split gint i = 0; //unescape helper for concatting, later on GList *freecats = NULL; //transform splitted values to void pointers and //add default value "" in case of a missing value/field for (j = 0; j < columns; j++) { //not enough separators in the line = bad cache data if (split[i] == NULL) { //fill rest with "" for (; j < columns; j++) parts [j] = g_strdup (""); break; } if ( *(split[i]) != '\0') { gchar *value = split[i]; //unescape | and concat values while (split[i + 1] != NULL) { gint len = strlen (value); if ((len > 0) && (value[len - 1] == '\\')) { value[len - 1] = '\0'; value = g_strconcat (value, "|", split[i + 1], NULL); //save pointer to free it after //knowing how long this really gets //(there could be several escaped | in one line) freecats = g_list_append (freecats, value); i++; } else break; } parts[j] = g_strdup (value); } else //empty string found found, use default value parts[j] = g_strdup (""); i++; } //cleanup g_strfreev (split); GList *member; for (member = freecats; member != NULL; member = member->next) g_free (member->data); g_list_free (freecats); } /** * @brief update vfs toggle button and tooltip to reflect vfs root * Button state is set TRUE for local fs, FALSE for vfs * View data are not set here, as the button may be toggled before the view is * actually updated * @param fs enumerator of type of fs * @param tiptext new tooltip for FALSE toggle button, irrelevant for TRUE * @param view data struct for the view being processed * * @return */ void e2_toolbar_update_vfs_toggle_button (E2_FSType fs, gchar *tiptext, FileView *view) { E2_ToggleType num = (view == &app.pane1_view) ? E2_TOGGLE_PANE1VIRTUAL: E2_TOGGLE_PANE2VIRTUAL; gchar *hashkey = g_strconcat (toggles_array[num], ".", toggles_array[num], NULL); E2_ToggleData *data = g_hash_table_lookup (toggles_hash, hashkey); g_free (hashkey); if (data != NULL) { //TRUE is the 'local' fs state gboolean local = (fs == FS_LOCAL); e2_toolbar_toggle_button_set_state (toggles_array [num], local); if (!local) { //update current tooltip GList *tmp; for (tmp = data->boxes; tmp != NULL; tmp = tmp->next) { E2_ToggleBox *ex = tmp->data; g_free (ex->tips->tip_text); //CHECKME ok ? ex->tips->tip_text = g_strdup (tiptext); } } } } //******************* ARCHIVE-SPECIFIC ************************* /** * @brief get path from line of data for an item in a tarball * Format of lines from tarball listing is like: drwxrwxrwx maker/maker 0 2006-01-14 05:48:13 tango-scalable/ -rw-rw-r-- maker/maker 36748 2006-01-12 15:43:12 tango-scalable/key#1.svg * @param itemline the string to be parsed * @return newly-allocated absolute path string, in same encoding as in archive, no trailer */ static gchar *_e2_vfs_get_path_for_tar (gchar *itemline) { //skip to start of path string (assumes no gaps in elements) gchar *path = itemline; guint j; for (j = 0; j < 5; j++) { path = e2_utils_find_whitespace (path); path = e2_utils_pass_whitespace (path); } path = (g_path_is_absolute (path)) ? g_strdup (path) : e2_utils_strcat (G_DIR_SEPARATOR_S, path); j = strlen (path) - 1; if (j > 0) //not processing root dir { gchar *s = path + j; if (*s == G_DIR_SEPARATOR) *s = '\0'; } return path; } /** * @brief parse line of data for an item in a tarball * Format of lines from tarball listing is like: drwxrwxrwx maker/maker 0 2006-01-14 05:48:13 tango-scalable/ -rw-rw-r-- maker/maker 36748 2006-01-12 15:43:12 tango-scalable/key#1.svg * @param itemline the string to be parsed * @param info pointer to FileInfo to hold parsed data for item * @return TRUE on successful completion */ static gboolean _e2_vfs_make_node_data_for_tar (gchar *itemline, FileInfo *info) { struct passwd *pw_buf; struct group *grp_buf; struct tm tm; uid_t user_id; gid_t group_id; gchar *s, *p, *b; const gchar *t; //parse perms in *(line+1) by exploiting happy coincidence between //ascii-string-form and bits-form of permissions representation gint i = 0; p = itemline + 1; b = s = e2_utils_find_whitespace (p); while (s > p) { s--; if (*s != '-') info->statbuf.st_mode |= 1<statbuf.st_mode |= S_IFDIR; break; case 'l': info->statbuf.st_mode |= S_IFLNK; break; /* 'special' files should not be in an archive case 'b': info->statbuf.st_mode |= S_IFBLK; break; case 'c': info->statbuf.st_mode |= S_IFCHR; break; case 'f': info->statbuf.st_mode |= S_IFIFO; break; case 's': info->statbuf.st_mode |= S_IFSOCK; break; */ default: // case '-': info->statbuf.st_mode |= S_IFREG; break; } //parse owner/group p = e2_utils_pass_whitespace (b+1); //owner name b = strchr (p, G_DIR_SEPARATOR); *b = '\0'; b++; //group name s = e2_utils_find_whitespace (b+1); *s = '\0'; if ((pw_buf = getpwnam (p)) != NULL) user_id = pw_buf->pw_uid; else user_id = (uid_t)atoi (p); //defaults to 0 if ((grp_buf = getgrnam (b)) != NULL) group_id = grp_buf->gr_gid; else group_id = (gid_t)atoi (b); info->statbuf.st_uid = user_id; info->statbuf.st_gid = group_id; //parse size (dirs will be 0) p = e2_utils_pass_whitespace (s+1); s = e2_utils_find_whitespace (p+1); *s = '\0'; info->statbuf.st_size = strtoull (p, &b, 10); if (b != s) return FALSE; //parse mdate //other times stay at 0 until sniffed or extracted, if ever p = e2_utils_pass_whitespace (s+1); s = e2_utils_find_whitespace (p+1); s = e2_utils_find_whitespace (s+1); *s = '\0'; //clear result structure memset (&tm, '\0', sizeof (tm)); t = strptime (p, "%Y-%m-%d%n%T", &tm); if (t != NULL && *t == '\0') { tm.tm_isdst = -1; //don't correct for daylight saving time_t dt = mktime (&tm); if (dt != (time_t) -1) info->statbuf.st_mtime = dt; } //parse rel-path/name //same encoding as in archive p = e2_utils_pass_whitespace (s+1); b = g_path_get_basename (p); g_strlcpy (info->filename, b, sizeof (info->filename)); g_free (b); return TRUE; } //*********************************************************** /** * @brief create empty liststore for vfs places history data * * @return **/ static void _e2_vfs_create_history_store (void) { app.vfs_data = gtk_list_store_new (10, G_TYPE_STRING, //0 protocol (string) G_TYPE_STRING, //1 hostname G_TYPE_STRING, //2 path G_TYPE_STRING, //3 user G_TYPE_STRING, //4 password (plaintext) G_TYPE_STRING, //5 port (string) G_TYPE_INT, //6 protonum (integer) G_TYPE_UINT, //7 ID for matching past entries G_TYPE_POINTER, //8 dirline history, glist of strings G_TYPE_POINTER //9 other data struct specific to archive or site //CHECKME pane id ? ); } /** * @brief save @a data in vfs history store * @a parts holds protocol, host, path, user, password, port respectively * @param iter pointer to data for the store row with data to update, or NULL to append * @param parts array of VFSDISPLAY strings to store * * @return **/ void e2_vfs_store_history_data (GtkTreeIter *iter, const gchar *parts[]) { E2_VFSProtocol protonum = e2_vfs_get_protonum (parts[VFSPROTONAME]); //FIXME create a suitable ID integer guint ID = 0; if (iter == NULL) { GtkTreeIter iter2; //gtk >= 2.10 can handle &iter2 = NULL gtk_list_store_insert_with_values (app.vfs_data, &iter2, -1, VFSPROTONAME, parts[VFSPROTONAME], VFSHOSTNAME, parts[VFSHOSTNAME], VFSPATH, parts[VFSPATH], VFSUSER, parts[VFSUSER], VFSPASSWORD, parts[VFSPASSWORD], VFSPORT, parts[VFSPORT], VFSPROTO, protonum, VFSID, ID, VFSHIST, NULL, //always init with non-existent data ptrs VFSDATA, NULL, -1); } else { gtk_list_store_set (app.vfs_data, iter, VFSPROTONAME, parts[VFSPROTONAME], VFSHOSTNAME, parts[VFSHOSTNAME], VFSPATH, parts[VFSPATH], VFSUSER, parts[VFSUSER], VFSPASSWORD, parts[VFSPASSWORD], VFSPORT, parts[VFSPORT], VFSPROTO, protonum, VFSID, ID, // VFSHIST, NULL, don't change history pointer // VFSDATA, NULL, or the specific data -1); } } /** * @brief determine protocol enum corresponding to @a protoname * * @return the number which matches the string, or E2_VPROTO_INVALID **/ E2_VFSProtocol e2_vfs_get_protonum (const gchar *protoname) { E2_VFSProtocol proto = E2_VPROTO_INVALID; gint i; for (i = 0; i < RPROTOCOUNT; i++) { if (g_str_equal (protoname, remote_protocols[i])) { proto = (E2_VFSProtocol) i; break; } } if (proto == E2_VPROTO_INVALID) { for (i = 0; i < APROTOCOUNT; i++) { if (g_str_equal (protoname, archive_protocols[i])) { proto = (E2_VFSProtocol) i + E2_VPROTO_MINARCHIVE; break; } } } return proto; } /** * @brief determine enumerator for changing dir to the fs type in @a view * * @param view data struct for view * * @return the number **/ E2_FSNewType e2_vfs_get_change_type (FileView *view) { E2_FSNewType fstype; switch (curr_view->dirtype) { case FS_LOCAL: case FS_FUSE: fstype = FS_OPENLOCAL; break; case FS_REMOTE: fstype = FS_OLDREMOTE; break; case FS_ARCHIVE: fstype = FS_OLDARCHIVE; //case FS_ARCHIVE | FS_REMOTE: //remote archive //FIXME break; } return fstype; } /** * @brief determine protocol enum corresponding to @a filename * This performs simple ascii checking for file extension * @param path or name of archive to check * * @return the number which matches the name, or E2_VPROTO_INVALID **/ E2_VFSProtocol e2_vfs_get_protonum_for_file (gchar *filename) { E2_VFSProtocol proto; if ((strstr (filename, ".tar.gz") != NULL) ||(strstr (filename, ".tgz") != NULL)) proto = E2_VPROTO_TGZ; else if ((strstr (filename, ".tar.bz2") != NULL) ||(strstr (filename, ".tbz") != NULL) ||(strstr (filename, ".tbz2") != NULL)) proto = E2_VPROTO_TBZ2; else if (strstr (filename, ".tar") != NULL) proto = E2_VPROTO_TAR; else if (strstr(filename, ".rpm") != NULL) proto = E2_VPROTO_RPM; // else if (strstr(filename, ".deb") != NULL) // proto = E2_VPROTO_DEB; else if (strstr (filename, ".zip") != NULL) proto = E2_VPROTO_ZIP; else if (strstr (filename, ".7z") != NULL) proto = E2_VPROTO_7Z; else if (strstr(filename, ".rar") != NULL) proto = E2_VPROTO_RAR; else if (strstr(filename, ".arj") != NULL) proto = E2_VPROTO_ARJ; else proto = E2_VPROTO_INVALID; return proto; } /** * @brief determine vfs button tooltip, reflecting new vfs root * * @param index index of row in vfs data store containing data to use * * @return newly-allocated tip string */ static gchar *_e2_vfs_get_tip (gint index) { gchar *text; GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (app.vfs_data), &iter, NULL, index)) { gchar *protname, *host; E2_VFSProtocol proto; gtk_tree_model_get (GTK_TREE_MODEL (app.vfs_data), &iter, VFSPROTONAME, &protname, VFSHOSTNAME, &host, VFSPATH, &text, VFSPROTO, &proto, -1); if (proto < E2_VPROTO_MINARCHIVE) { //remote site g_free (text); text = g_strconcat (protname, "://", host, NULL); g_free (protname); g_free (host); } } else text = ""; return g_strdup (text); } /** * @brief create and populate vfs places history data from cached @a rowlist * * @param store place to save the created store pointer * @param rowslist list of strings with row data to add to to created store * * @return **/ static void _e2_vfs_sync_history (gpointer *store, GList *rowslist) { gint i; E2_VFSProtocol proto; guint ID; gchar *parts[VFSDISPLAY]; GtkTreeIter iter; GList *member; _e2_vfs_create_history_store (); *store = app.vfs_data; //send back the store pointer GtkListStore *lstore = app.vfs_data; for (member = rowslist; member != NULL; member = member->next) { e2_utils_rowstr_split ((gchar *)member->data, VFSDISPLAY, parts); proto = e2_vfs_get_protonum (parts[VFSPROTONAME]); if (proto == E2_VPROTO_INVALID) proto = E2_VPROTO_MINARCHIVE; //default to this ID = 0; //FIXME suitable ID //gtk >= 2.10 can handle &iter = NULL gtk_list_store_insert_with_values (lstore, &iter, -1, VFSPROTONAME, parts[VFSPROTONAME], VFSHOSTNAME, parts[VFSHOSTNAME], VFSPATH, parts[VFSPATH], VFSUSER, parts[VFSUSER], VFSPASSWORD, parts[VFSPASSWORD], VFSPORT, parts[VFSPORT], VFSPROTO, proto, VFSID, ID, VFSHIST, NULL, //no history list yet VFSDATA, NULL, //or extra data -1); //cleanup for (i = 0; i < VFSDISPLAY; i++) g_free (parts[i]); } } /** * @brief convert vfs places history data from liststore to glist, for cacheing * * @param data address of cache->data containing pointer to the store to process * * @return */ static void _e2_vfs_desync_history (gpointer *data) { gchar *rowstring; static GList *list = NULL; GtkTreeModel *model = GTK_TREE_MODEL ((GtkListStore *)*data); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (model, &iter)) { do { rowstring = e2_tree_row_to_string (model, &iter, VFSDISPLAY, 0); //save initial columns of data list = g_list_append (list, rowstring); } while (gtk_tree_model_iter_next (model, &iter)); } *data = (gpointer *) &list; //send back the list pointer, ready for cacheing } /** * @brief cleanup archive data for @a arch_path * @param arch_path archive path (actual or tmp/virtual) utf8 string * @param kill_history TRUE to cleanup archive-specific history list * @param kill_info TRUE to cleanup archive-specific other data * @return */ static void _e2_vfs_clear_arch_data_for_file (gchar *arch_path, gboolean kill_history, gboolean kill_info) { GList *hist_list, *member; E2_VFSArchHistory *arch; GtkTreeIter iter; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (app.vfs_data), &iter)) { if (e2_tree_find_iter_from_str_simple (GTK_TREE_MODEL (app.vfs_data), VFSPATH, arch_path, &iter, FALSE)) { gtk_tree_model_get (GTK_TREE_MODEL (app.vfs_data), &iter, VFSHIST, &hist_list, VFSDATA, &arch, -1); if (kill_history && hist_list != NULL) { e2_list_free_with_data (&hist_list); //CHECKME gtk_list_store_set (app.vfs_data, &iter, VFSHIST, NULL, -1); } if (kill_info && arch != NULL) { g_free (arch->virt_path); g_free (arch->tmp_path); g_node_destroy (arch->virt_root); for (member = arch->node_data; member != NULL; member = member->next) { E2_VFSArchNode *node = (E2_VFSArchNode*) member->data; g_free (node->path); if (node->line != NULL) g_free (node->line); if (node->info != NULL) DEALLOCATE (FileInfo, node->info); g_free (node); } g_list_free (arch->node_data); DEALLOCATE (E2_VFSArchHistory, arch); gtk_list_store_set (app.vfs_data, &iter, VFSDATA, NULL, -1); } } } } /** * @brief tree scan helper function to list FileInfo's in a virtual dir * If the data line for the node has not yet been processed into a fake * FileInfo (indictedf by FileInfo* == NULL) then it will be processed here * When FileInfo is created, the data line is cleaned * @param node pointer to node in vitrual tree for item to be processed * @param adddata data struct for the process * @return */ static void _e2_vfs_add_entry (GNode *node, E2_NodeFind *adddata) { FileInfo *newinfo = ALLOCATE (FileInfo); CHECKALLOCATEDWARN (newinfo, return;) guint index = GPOINTER_TO_INT (node->data); E2_VFSArchNode *data = g_list_nth_data (adddata->list, index); if (data->info == NULL) { //parse the line now data->info = ALLOCATE0 (FileInfo); //CHECKME nested () in macro CHECKALLOCATEDWARN (data->info, DEALLOCATE (FileInfo, newinfo); return;) gboolean (*infofunc) (gchar *, FileInfo *) = adddata->parser; if (!(*infofunc) (data->line, data->info)) { //FIXME warn user DEALLOCATE (FileInfo, newinfo); DEALLOCATE (FileInfo, data->info); return; } g_free (data->line); data->line = NULL; } memcpy (newinfo, &data->info, sizeof (FileInfo)); adddata->entries = g_list_append (adddata->entries, newinfo); } /** * @brief tree search helper function * Note: if there is no match, searchdata->parent is unchanged * @param searchdata data struct for the search * @return TRUE when a match is found (and then, searchdata->parent is set to the dir's node */ static gboolean _e2_vfs_find_dirnode (GNode *node, E2_NodeFind *searchdata) { guint index = GPOINTER_TO_INT (node->data); E2_VFSArchNode *nodedata = (E2_VFSArchNode *) g_list_nth_data (searchdata->list, index); //is this the node for the requested archive path? if (g_str_equal (searchdata->path, nodedata->path)) //path is correct { searchdata->parent = node; return TRUE; } return FALSE; } /** * @brief get archive data for @a arch_path * If data item for @a arch_path does not exist, then a new data structure will * be created * @param arch_path archive path (actual or tmp/virtual) utf8 string * @return pointer to data for the archive, or NULL in case of a problem */ static E2_VFSArchHistory *_e2_vfs_get_arch_data_for_file (gchar *arch_path) { E2_VFSArchHistory *arch = NULL; E2_VFSArchNode *newnode; GtkTreeIter iter; gboolean stored; E2_NodeFind finddata; E2_VFSProtocol proto = e2_vfs_get_protonum_for_file (arch_path); if (proto == E2_VPROTO_INVALID) return NULL; /* get archive data if archive is already shown in either filelist use that data if archive is already in history use that data ?? if archive has a parent already in history ?? work from parent's data to open this archive if archive is remote or inside another archive, get it into a local temp dir */ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (app.vfs_data), &iter)) { stored = e2_tree_find_iter_from_str_simple (GTK_TREE_MODEL (app.vfs_data), VFSPATH, arch_path, &iter, FALSE); if (stored) { gtk_tree_model_get (GTK_TREE_MODEL (app.vfs_data), &iter, VFSDATA, &arch, -1); if (arch != NULL) return arch; } } else stored = FALSE; //this is a brand new opening guint i, indx, lines_count; gchar *path, *path_seg, *command, *contents; gchar **path_split, **data_lines; GNode *parentnode; indx = proto-E2_VPROTO_MINARCHIVE; //get data-lines for each item in archive command = g_strdup_printf (listers [indx], arch_path); if (!e2_fs_get_command_output (command, &contents)) { //handle oops //WARN user g_free (command); return GINT_TO_POINTER (-1); } g_free (command); //create archive data structs arch = ALLOCATE0 (E2_VFSArchHistory); CHECKALLOCATEDWARN (arch, g_free (contents);return GINT_TO_POINTER (-1);) newnode = ALLOCATE0 (E2_VFSArchNode); CHECKALLOCATEDWARN (newnode, DEALLOCATE (E2_VFSArchHistory, arch); g_free (contents);return GINT_TO_POINTER (-1);) //setup root node for tree arch->virt_root = g_node_new (NULL); //0 index for root node newnode->path = g_strdup (G_DIR_SEPARATOR_S); arch->node_data = g_list_append (NULL, newnode); //newnode->info not needed, the archive-root is never in a filelist //FIXME set other arch data too data_lines = g_strsplit (contents, "\n", -1); g_free (contents); lines_count = g_strv_length (data_lines); //parse all data lines gchar* (*pathfunc) (gchar *) = pathfuncs [indx]; for (i = 0; i < lines_count; i++) { if (*data_lines[i] != '\0') //last line probably empty { path = (*pathfunc) (data_lines[i]); //same encoding as in archive //try to find an existing node with this path finddata.path = path; finddata.list = arch->node_data; finddata.parent = NULL; finddata.entries = NULL; g_node_traverse (arch->virt_root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) _e2_vfs_find_dirnode, &finddata); if (finddata.parent == NULL) { //no tree-node exists yet, create one now /*iteratively create any missing ancestor node(s) until we get to the one being processed Each node's data = index of item in ...->node_data list */ parentnode = arch->virt_root; //iterative parent pointer starts at tree root guint j, segs_count; gchar *parentnow, *pathnow = g_strdup (""); //split item path into segments for processing (any valid separator) path_split = g_strsplit_set (path, "\\/", -1); segs_count = g_strv_length (path_split); //ignore 1st segment which is empty (root) for (j = 1; j < segs_count; j++) { path_seg = path_split[j]; if (*path_seg == '\0') continue; //ignore empty segments //accumulate path with this segment parentnow = pathnow; pathnow = g_strconcat (parentnow, G_DIR_SEPARATOR_S, path_seg, NULL); g_free (parentnow); finddata.path = pathnow; finddata.parent = NULL; g_node_traverse (parentnode, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) _e2_vfs_find_dirnode, &finddata); if (finddata.parent == NULL) { //no tree-node exists yet, create one now newnode = ALLOCATE0 (E2_VFSArchNode); CHECKALLOCATEDWARN (newnode, break;) newnode->path = g_strdup (pathnow); //get index for next appended item gpointer ndata = (gpointer) g_list_length (arch->node_data); parentnode = g_node_append_data (parentnode, ndata); //now it's ok to change the list length arch->node_data = g_list_append (arch->node_data, newnode); } else parentnode = finddata.parent; } //the last segment is for the line we are now processing newnode->line = data_lines[i]; newnode->flagsptr = &arch->flags; //cleanup g_strfreev (path_split); } g_free (path); } else //line is empty, clean it now g_free (data_lines[i]); } g_free (data_lines); //clean pointers, but not actual lines //record new item in app.vfs_data if (!stored) { //CHECKME can this ever occur ? const gchar *data[6]; data[VFSPROTONAME] = archive_protocols [proto - E2_VPROTO_MINARCHIVE]; data[VFSHOSTNAME] = ""; data[VFSPATH] = arch_path; data[VFSUSER] = ""; data[VFSPASSWORD] = ""; data[VFSPORT] = ""; e2_vfs_store_history_data (NULL, data); //set iter to the row just appended gint n; n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.vfs_data), NULL); gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (app.vfs_data), &iter, NULL, n-1); } gtk_list_store_set (app.vfs_data, &iter, VFSDATA, arch, -1); return arch; } /** * @brief determine whether @a localpath is, or is in, a non-virtual directory * This is distinct from e2_fs_dir_is_native(), as @a localpath does not * need to be a directory, and FUSE-mounted items are ok because they can be * processed with normal stdio functions * @param localpath localised string, absolute path of item to check * * @return TRUE if @a localpath can be processed with normal stdio functions */ gboolean e2_fs_item_is_mounted (const gchar *localpath , GError **error) { #ifdef E2_VFSTMP //FIXME TESTS HERE e.g. //from kernel/filssystem/hal //from cd command parameters //from plugin data //from vfs history data //from other e2 data # if defined(__linux__) || defined(__FreeBSD__) GList *fusemounts, *member; fusemounts = e2_complete_mount_get_fusemounts_list (); for (member = fusemounts ; member != NULL; member = member->next) { //member->data is utf with no trailing "/" if (g_str_has_prefix (utfpath, (gchar *)member->data)) break; //path is a descendant of a fuse mountpoint } if (fusemounts != NULL) e2_list_free_with_data (&fusemounts); if (member != NULL) return TRUE; # endif #endif return TRUE; } /** * @brief read into memory the contents of directory @a open_path, in archive @a arch_path * Like e2_fs_read_whole_dir (), it produces a list of FileInfo's for use in a * filelist list-store * Local fs CWD is not changed ?? or to temp dir for the arcive ?? * Some sort of fake directory timestamp needed, for refresh-management purposes ? * @param open_path absolute path of dir in archive, utf8 string * @param arch_path archive path (actual or tmp/virtual) localised string * * @return list of FileInfo's for entries (maybe NULL), or -1 if a problem occurred */ GList *e2_vfs_read_archive_dir (gchar *open_path, gchar *arch_path) { E2_VFSProtocol proto = e2_vfs_get_protonum_for_file (arch_path); if (proto == E2_VPROTO_INVALID) return (GINT_TO_POINTER (-1)); //get archive data from history or new E2_VFSArchHistory *adata = _e2_vfs_get_arch_data_for_file (arch_path); if (adata == NULL) return GINT_TO_POINTER (-1); E2_NodeFind fdata; //find data-tree node for the dir to read fdata.path = open_path; fdata.list = adata->node_data; fdata.parent = NULL; //not needed any more fdata.entries = NULL; g_node_traverse (adata->virt_root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) _e2_vfs_find_dirnode, &fdata); if (fdata.parent != NULL) { fdata.parser = parsefuncs [proto-E2_VPROTO_MINARCHIVE]; //create a list of FileInfo's for that virtual dir g_node_children_foreach (fdata.parent, G_TRAVERSE_ALL, (GNodeForeachFunc) _e2_vfs_add_entry, &fdata); } else { //handle oops } return fdata.entries; } /* static void _e2_vfs_clear (E2_Unpackdata *data) { gchar *local = F_FILENAME_TO_LOCALE (data->utf_prefix); if (e2_fs_access2 (local) == 0) e2_task_backend_delete (local); F_FREE (local); g_free (data->utf_prefix); DEALLOCATE (E2_Unpackdata, data); //FIXME different refresh approach with E2_ASYNC e2_filelist_check_dirty (GINT_TO_POINTER(1)); } */ /* //package is assumed to have ascii-coded extension - ok ? static void _e2_vfs_repack (E2_Unpackdata *data) { gchar *command, *package = data->utf_prefix; //command strings are all designed to be executed from //the temp dir, on all its contents if ((strstr (package, ".tar.gz") != NULL) || (strstr (package, ".tgz") != NULL)) command = ">tar cf - . | gzip - > \"%s\""; else if (strstr (package, ".tar.bz2") != NULL) command = ">tar cf - . | bzip2 - > \"%s\""; else if (strstr (package, ".tar") != NULL) command = "tar cf \"%s\" ."; else if (strstr (package, ".zip") != NULL) command = "zip -r \"%s\" ."; else if (strstr (package, ".rar") != NULL) command = "rar u -ol \"%s\" ."; else //if (strstr (package, ".arj") != NULL) command = "arj u -al \"%s\" ."; command = g_strdup_printf (command, package); #ifdef E2_VFSTMP //FIXME dir when not mounted local #else gchar *dirnow = g_strdup (curr_view->dir); #endif //just chdir not enough, crashes ... //FIXME make string utf8 //FIXME store current vfs parameters for active pane E2_FSNewType resetfs = e2_vfs_get_change_type (curr_view); e2_pane_change_dir (NULL, #ifdef E2_VFSTMP //FIXME prefix FS_OPENLOCAL, curr_view->prefix, #endif data->view->prefix); //prevent refreshes in case the inactive pane shows the package e2_filelist_disable_refresh (); //or make it refresh less often (until the background command is completed) gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); data->pack_pid = (res == 0) ? e2_command_find_last_child (TRUE) : 0; e2_filelist_enable_refresh (); //or resume normal refresh rate g_free (command); e2_pane_change_dir (NULL, #ifdef E2_VFSTMP //FIXME prefix resetfs, curr_view->prefix, #endif dirnow); g_free (dirnow); //periodically check whether re-build finished, when so, cleanup the temp dir //FIXME make this cancellable at session end // data->pack_timer_id = g_timeout_add (500, (GSourceFunc) _e2_vfs_clean_dir, data); } */ /** * @brief unpack an archive into a temp dir * * @param package absolute path of archive, utf8 string * @param view data struct for the view in which to show archive's contents * * @return TRUE if action completed successfully, else FALSE */ /*static gboolean _e2_vfs_unpack (gchar *package, FileView *view) { gchar *command; //unpack-command string E2_VFSProtocol proto = e2_vfs_get_protonum_for_file (package); switch (proto) { //note: --atime-preserve in these tar commands prevents the //file-monitoring process from noticing anything in the temp dir case E2_VPROTO_TGZ: #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin) command = "tar -xpzf \"%s\""; #else //FIXME -z option is a gnu extension command = "tar --overwrite -xpzf \"%s\""; #endif break; case E2_VPROTO_TBZ2: #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin) command = "tar -xpjf \"%s\""; #else //FIXME -j option is a gnu extension command = "tar --overwrite -xpjf \"%s\""; #endif break; case E2_VPROTO_TAR: #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin) command = "tar -xpf \"%s\""; #else command = "tar --overwrite -xpf \"%s\""; #endif break; // case E2_VPROTO_DEB: //.deb command (for what version ?) provided by Martin Zelaia // command = "mkdir ./DEBIAN ./CONTENTS;>ar -x \"%s\" | tar -xfz control.tar.gz -C ./DEBIAN | tar -xfz data.tar.gz -C ./CONTENTS; rm control.tar.gz data.tar.gz;cp ./DEBIAN/control ./INFO;rm ./debian-binary"; // break; // case E2_VPROTO_RPM: // command = "rpm ?? \"%s\""; // break; case E2_VPROTO_ZIP: command = "unzip -o \"%s\""; break; // case E2_VPROTO_7Z: // command = "?? \"%s\""; // break; case E2_VPROTO_RAR: command = "rar x -o+ \"%s\""; break; case E2_VPROTO_ARJ: command = "arj x -y \"%s\""; break; default: { gchar *msg = g_strdup_printf ( _("'%s' is not a supported archive type"), package); e2_output_print_error (msg, TRUE); return FALSE; } break; } command = g_strdup_printf (command, package); //create a suitable temp dir for 'working' directory, utf8 string gchar *unpack_tmp = e2_fs_mktempdir (0770); if (unpack_tmp == NULL) { //FIXME handle error return FALSE; } //FIXME this stuff is really rough dev code // e2_window_set_cursor (GDK_WATCH); //after info is parsed, //we need to go to the temp dir to execute the command //is chdir enough ? E2_PaneRuntime *rt = (view == &app.pane1_view) ? &app.pane1 : &app.pane2; e2_pane_change_dir (rt, #ifdef E2_VFSTMP FS_OPENLOCAL, NULL, #endif unpack_tmp); //and runnding commands forces the CWD to match curr_view->dir, so we need to //set active pane, too gboolean swap = (rt != curr_pane); e2_filelist_disable_refresh (); //prevent nested updates when we check-dirty() if (swap) e2_pane_activate_other (); //unpack the archive into the temp directory #ifdef E2_ASYNC // gdk_threads_enter (); this is done outside this func #endif e2_command_run (command, E2_COMMAND_RANGE_DEFAULT); #ifdef E2_ASYNC // gdk_threads_leave (); #endif printd (DEBUG, "unpack command completed"); //update pane // e2_filelist_check_dirty (GINT_TO_POINTER(1)); if (swap) e2_pane_activate_other (); view->prefix = unpack_tmp; e2_filelist_enable_refresh (); // e2_window_set_cursor (GDK_LEFT_PTR); g_free (command); return TRUE; } */ /** * @brief cancel display of archive in pane associated with @a view * * @param index index of row in vfs data store with data for archive to open * @param view data struct for the view being processed * * @return */ static void _e2_vfs_close_archive (FileView *view) { //FIXME do other relevant things //store history data //store fake root string //pack file } /** * @brief cancel display a remote dir in pane associated with @a view * * @param view data struct for the view being processed * * @return */ static void _e2_vfs_close_remote (FileView *view) { //FIXME do other relevant things //store history data //store fake root string //read dir } /** * @brief setup to display a local dir in pane associated with @a view * * @param view data struct for the view being processed * * @return */ void e2_vfs_open_local (FileView *view) { e2_filelist_disable_refresh (); switch (view->dirtype) { case FS_LOCAL: case FS_FUSE: break; case FS_REMOTE: _e2_vfs_close_remote (view); break; case FS_ARCHIVE: _e2_vfs_close_archive (view); //case FS_ARCHIVE | FS_REMOTE: //remote archive //FIXME break; } guint index = (view == &app.pane1_view) ? E2_TOGGLE_PANE1VIRTUAL : E2_TOGGLE_PANE2VIRTUAL; e2_toolbar_toggle_button_set_state (toggles_array [index], TRUE); //FIXME do other relevant things //store history data //store fake root string //read dir //FIXME other adjustments view->dirtype = FS_LOCAL; //E2_OKVFSTMP e2_filelist_request_refresh (view->dir, TRUE); e2_filelist_enable_refresh (); } /** * @brief setup to display an archive in pane associated with @a view * The archive is opened at its "root" directory * Data for archive contents are extracted by the pertinent "list-contents" * command. FileInfo's are created only when a virtual dir in the archive is * to be displayed * @param index index of item in app.vfs_data * @param view data struct for the view being processed * * @return */ void e2_vfs_open_archive (gint index, FileView *view) { // e2_filelist_disable_refresh (); switch (view->dirtype) { case FS_LOCAL: case FS_FUSE: break; case FS_REMOTE: _e2_vfs_close_remote (view); break; case FS_ARCHIVE: _e2_vfs_close_archive (view); //case FS_ARCHIVE | FS_REMOTE: //remote archive //FIXME break; } GtkTreeIter iter; gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (app.vfs_data), &iter, NULL, index); gchar *package; gtk_tree_model_get (GTK_TREE_MODEL (app.vfs_data), &iter, VFSPATH, &package, -1); printd (DEBUG, "open package %s at index %d", package, index); GList *ventries; guint n; // ventries = e2_vfs_read_archive_dir (G_DIR_SEPARATOR_S, // "/home/maker/Projects/e2work/tango-scalable.tar.bz2"); //FIXME // ventries = e2_vfs_read_archive_dir (G_DIR_SEPARATOR_S, package); // n = g_list_length (ventries); // ventries = e2_vfs_read_archive_dir ("/ivman-0.6.6", package); // n = g_list_length (ventries); ventries = e2_vfs_read_archive_dir ("/ivman-0.6.6/src", package); n = g_list_length (ventries); _e2_vfs_clear_arch_data_for_file (package, FALSE, TRUE); //FIXME create custom liststore //CHECKME prevent freeze at session start, when this is inside an idle callback #ifdef E2_ASYNC gdk_threads_enter (); #endif //FIXME show store in view #ifdef E2_ASYNC gdk_threads_leave (); #endif //FIXME do other relevant things //fake root string view->prefix is stored in _e2_vfs_unpack view->tip = _e2_vfs_get_tip (index); //for archives, the tip is the archive path view->dirtype = FS_ARCHIVE; view->archvfs_curr = index; //FIXME store current browse-history data somewhere //change dir to fake root of opened archive //FIXME e2_pane_change_dir () needs to be updated //FIXME show it in dirline // E2_PaneRuntime *rt = (view == curr_view) ? curr_pane : other_pane; // g_free (rt->path); // rt->path = g_strdup (G_DIR_SEPARATOR_S); // g_strlcpy (view->dir, G_DIR_SEPARATOR_S, sizeof (view->dir)); //FIXME new history //FIXME status line //OR //E2_OKVFSTMP e2_filelist_request_refresh (view->dir, TRUE); e2_toolbar_update_vfs_toggle_button (FS_ARCHIVE, view->tip, view); // e2_filelist_enable_refresh (); } /** * @brief setup to display a remote dir in pane associated with @a view * * @param index index of row in vfs data store with data for site to open * @param view data struct for the view being processed * * @return */ void e2_vfs_open_remote (gint index, FileView *view) { e2_filelist_disable_refresh (); switch (view->dirtype) { case FS_LOCAL: case FS_FUSE: break; case FS_REMOTE: _e2_vfs_close_remote (view); break; case FS_ARCHIVE: _e2_vfs_close_archive (view); //case FS_ARCHIVE | FS_REMOTE: //remote archive //FIXME break; } gchar *tip = _e2_vfs_get_tip (index); e2_toolbar_update_vfs_toggle_button (FS_REMOTE, tip, view); g_free (tip); //FIXME do other relevant things //store history data //store fake root string //read dir view->dirtype = FS_REMOTE; view->remotevfs_curr = index; //E2_OKVFSTMP e2_filelist_request_refresh (view->dir, TRUE); e2_filelist_enable_refresh (); } /** * @brief idle callback to initialise vfs arrangements at session start * * This allows vfs initialisation at session startup to be deferred, * to prevent potential long delay before app window is created * Sets toggle buttons and tooltip to reflect vfs root * * @param data UNUSED data specified when the idle was connected * * @return FALSE to remove source */ gboolean e2_vfs_initialise (gpointer data) { e2_cache_store_register ("vfs-places-data", (gpointer *) &app.vfs_data, _e2_vfs_sync_history, _e2_vfs_desync_history); e2_cache_int_register ("pane1-archive-index", &app.pane1_view.archvfs_curr, -1); e2_cache_int_register ("pane2-archive-index", &app.pane2_view.archvfs_curr, -1); e2_cache_int_register ("pane1-remote-index", &app.pane1_view.remotevfs_curr, -1); e2_cache_int_register ("pane2-remote-index", &app.pane2_view.remotevfs_curr, -1); e2_cache_int_register ("pane1-fs-type", (gint *) &app.pane1_view.dirtype, FS_LOCAL); e2_cache_int_register ("pane2-fs-type", (gint *) &app.pane2_view.dirtype, FS_LOCAL); //dir changes require new and old (=startup) values /* E2_VFSProtocol proto1 = e2_vfs_get_change_type (curr_view); curr_view->fs = FS_LOCAL; E2_VFSProtocol proto2 = e2_vfs_get_change_type (other_view); other_view->fs = FS_LOCAL; //FIXME make these change dirs ... if (proto1 == FS_ARCHIVE) e2_vfs_open_archive (curr_view->archvfs_curr, curr_view); else if (proto1 == FS_REMOTE) e2_vfs_open_remote (curr_view->remotevfs_curr, curr_view); if (proto2 == FS_ARCHIVE) e2_vfs_open_archive (other_view->archvfs_curr, other_view); else if (proto2 == FS_REMOTE) e2_vfs_open_remote (other_view->remotevfs_curr, other_view); */ /* gchar *prefix, *startpath; if (proto1 != FS_LOCAL) { if (proto1 == FS_ARCHIVE) { //create a suitable temp dir for 'working' directory prefix = e2_fs_mktempdir (0770); if (prefix != NULL) { //unpack the archive now ?? startpath = g_strdup (G_DIR_SEPARATOR_S); } else { //FIXME handle error } } else { //FIXME prefix = NULL; startpath = g_strdup (G_DIR_SEPARATOR_S); } e2_pane_change_dir (curr_pane, #ifdef E2_OKVFSTMP proto1, prefix, #endif startpath); if (prefix != NULL) g_free (prefix); if (startpath != NULL) g_free (startpath); } if (proto2 != FS_LOCAL) { if (proto2 == FS_ARCHIVE) { //create a suitable temp dir for 'working' directory prefix = e2_fs_mktempdir (0770); if (prefix != NULL) { //unpack the archive now ?? startpath = g_strdup (G_DIR_SEPARATOR_S); } else { //FIXME handle error } } else { //FIXME prefix = NULL; startpath = g_strdup (G_DIR_SEPARATOR_S); } e2_pane_change_dir (other_pane, #ifdef E2_OKVFSTMP proto2, prefix, #endif startpath); if (prefix != NULL) g_free (prefix); if (startpath != NULL) g_free (startpath); } */ return FALSE; } /*******************/ /***** actions *****/ /*******************/ /** * @brief unpack an archive into a temp dir and show it in a pane * This func applies to both unpack and unpack_in_other. They are distinguished * by art->action->data = NULL or 0x1 respectively. * art->data may be name(s) (no path) of archive, or selected item(s) possibly * quoted * @param from the button, menu item etc which was activated * @param art action runtime data * * @return TRUE if action completed successfully, else FALSE **/ static gboolean _e2_vfs_unpack_action (gpointer from, E2_ActionRuntime *art) { #ifdef E2_ASYNC return (e2_task_enqueue_task (E2_TASK_UNPACK, art, from, _e2_vfs_unpackQ, e2_task_refresh_lists)); } static gboolean _e2_vfs_unpackQ (E2_ActionTaskData *qed) { //get archive data //FIXME apply this approach to all actions that may use a specific arg gchar *filename; if (qed->rt_data != NULL) { filename = e2_utils_get_first_arg (qed->rt_data, FALSE); if (filename == NULL) return FALSE; //command had no name to open } else { GPtrArray *names = qed->names; if (names == NULL) return FALSE; //nothing selected either filename = D_FILENAME_FROM_LOCALE ( ((E2_SelectedItemInfo *) *(names->pdata))->filename); } gchar *curr_dir = F_FILENAME_FROM_LOCALE (qed->currdir); //open archive in current or other pane ? gboolean curr_pane = (qed->action->data == NULL); #else //get archive data gchar *filename; if (art->data != NULL) { filename = e2_utils_get_first_arg (art->data, FALSE); if (filename == NULL) return FALSE; } else { GPtrArray *names = e2_fileview_get_selected (curr_view); if (names == NULL) return FALSE; //nothing selected either filename = D_FILENAME_FROM_LOCALE ( ((E2_SelectedItemInfo *) *(names->pdata))->filename); } # ifdef E2_VFSTMP //FIXME dir when not mounted local # else gchar *curr_dir = curr_view->dir; # endif //to open in current or other pane ? gboolean curr_pane = (art->action->data == NULL); #endif E2_VFSProtocol proto = e2_vfs_get_protonum_for_file (filename); if (proto == E2_VPROTO_INVALID) { gchar *msg = g_strdup_printf (_("'%s' is not a supported archive type"), filename); e2_output_print_error (msg, TRUE); g_free (filename); #ifdef E2_ASYNC F_FREE (curr_dir); #endif return FALSE; } //setup vfs data in history store const gchar *data[VFSDISPLAY]; data[VFSPROTONAME] = (proto < E2_VPROTO_MINARCHIVE) ? remote_protocols [proto] : archive_protocols [proto - E2_VPROTO_MINARCHIVE]; data[VFSHOSTNAME] = ""; data[VFSPATH] = g_strconcat (curr_dir, filename, NULL); data[VFSUSER] = ""; data[VFSPASSWORD] = ""; data[VFSPORT] = ""; e2_vfs_store_history_data ((GtkTreeIter *)NULL, data); //append this record //open archive using store data gint index = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (app.vfs_data), NULL) - 1; FileView *view = (curr_pane) ? curr_view : other_view ; e2_vfs_open_archive (index, view); g_free (filename); g_free ((gchar *)data[VFSPATH]); #ifdef E2_ASYNC F_FREE (curr_dir); #else //FIXME E2_INCLIST - just do other view e2_filelist_check_dirty (GINT_TO_POINTER(1)); #endif return TRUE; } /******************/ /***** public *****/ /******************/ /** * @brief create filesystem error based on errno * * @param error pointer to error data struct, or NULL * * @return */ void e2_fs_set_error_from_errno (GError **error) { if (error != NULL) { g_clear_error (error); // GFileError err_no = g_file_error_from_errno (errno); // *error = g_error_new_literal (G_FILE_ERROR, err_no, g_str (errno)); *error = g_error_new_literal (G_FILE_ERROR, errno, g_strerror (errno)); } } /** * @brief create custom error * * @param error pointer to error data struct, or NULL * @param code error code * @param format printf()-style format string for the error message * * @return */ void e2_fs_set_error_custom (GError **error, gint code, const gchar *format, ...) { if (error != NULL) { gchar *built; g_clear_error (error); if (format != NULL) { const gchar *name = format; gchar *freeme; built = g_strdup (format); va_list args; va_start (args, format); while (name != NULL) { freeme = built; built = g_strconcat (freeme, ", ", name, NULL); g_free (freeme); name = va_arg (args, const gchar*); } va_end (args); } else built = NULL; g_set_error (error, G_FILE_ERROR, code, built); if (built != NULL) g_free (built); } } /******** VFS REPLACEMENTS FOR STANDARD FS FUNCTIONS *********/ gint e2_fs_access (const gchar *localpath, gint mode, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = access (localpath, mode); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_lstat (const gchar *localpath, struct stat *buf, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = lstat (localpath, buf); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_fstat (gint descriptor, struct stat *buf, GError **error) { gint result; gchar *localpath = ?; if (e2_fs_item_is_mounted (localpath, error)) { result = fstat (descriptor, buf); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_open (const gchar *localpath, gint flags, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = open (localpath, flags); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } ssize_t e2_fs_read (gint descriptor, gpointer buffer, size_t size, GError **error) { size_t result; const gchar *localpath = ?; if (e2_fs_item_is_mounted (localpath, error)) { result = read (descriptor, buffer, size); if (result < 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_close (gint descriptor) //, GError *error) //currently applied only to chiild io channels { gint result; const gchar *localpath = ?; GError *error = NULL; if (e2_fs_item_is_mounted (localpath, &error)) { result = close (descriptor); // if (result != 0) // e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } E2_FILE *e2_fs_file_open (const gchar *localpath, const gchar *how, GError **error) { E2_FILE *result; if (e2_fs_item_is_mounted (localpath, error)) { result = fopen (localpath, how); if (result != NULL) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return NULL; } } gint e2_fs_file_close (E2_FILE *stream, GError **error) { gint result; const gchar *localpath = ?; if (e2_fs_item_is_mounted (localpath, error)) { result = fclose (stream); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_unlink (const gchar *localpath, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = unlink (localpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_remove (const gchar *localpath, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = remove (localpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_rmdir (const gchar *localpath, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = rmdir (localpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_mkdir (const gchar *localpath, mode_t mode, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = mkdir (localpath, mode); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_chdir_local (const gchar *localpath, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = chdir (localpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } DIR *e2_fs_dir_open (const gchar *localpath, GError **error) { DIR *result; if (e2_fs_item_is_mounted (localpath, error)) { result = opendir (localpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return NULL; } } gint e2_fs_dir_close (DIR *dir, GError **error) { gint result; const gchar *localpath = ?; if (e2_fs_item_is_mounted (localpath, error)) { result = closedir (dir); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_dir_read (DIR *dirp, struct dirent *entry, struct dirent **result, GError **error) { gint success; const gchar *localpath = ?; if (e2_fs_item_is_mounted (localpath, error)) { success = readdir_r (dirp, entry, result); if (success != 0) e2_fs_set_error_from_errno (error); return success; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_rename (const gchar *oldpath, const gchar *newpath, GError **error) { gint result; if (e2_fs_item_is_mounted (oldpath, error) && e2_fs_item_is_mounted (newpath, error)) { result = rename (oldpath, newpath); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_chmod (const gchar *localpath, mode_t mode, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = chmod (localpath, mode); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_chown (const gchar *localpath, uid_t owner, gid_t group, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = chown (localpath, owner, group); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_lchown (const gchar *localpath, uid_t owner, gid_t group, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = lchown (localpath, owner, group); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_utime (const gchar *localpath, const struct utimbuf *buf, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = utime (localpath, buf); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_symlink (const gchar *target, gchar *name, GError **error) { gint result; if (e2_fs_item_is_mounted (target, error) && e2_fs_item_is_mounted (name, error)) { result = symlink (target, name); //CHECKME relative links ok ? if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } gint e2_fs_readlink (const gchar *localpath, gchar *targetbuf, size_t bufsize, GError **error) { gint result; if (e2_fs_item_is_mounted (localpath, error)) { result = readlink (localpath, targetbuf, bufsize); if (result != 0) e2_fs_set_error_from_errno (error); return result; } else //need to do virtual { if (error != NULL) //don't care about errors sofar g_error_free (*error); #ifdef E2_VFSTMP //FIXME do virtual change #endif return -1; } } /** * @brief register actions related to vfs * @return **/ void e2_vfs_actions_register (void) { gchar *action_name = g_strconcat(_A(5),".",_A(91),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_vfs_unpack_action, NULL, TRUE); //data = pointerised 0 = current pane, arg = %%f action_name = g_strconcat(_A(5),".",_A(107),NULL); e2_action_register (action_name, E2_ACTION_TYPE_ITEM, _e2_vfs_unpack_action, GINT_TO_POINTER (1), TRUE, 0, NULL); //data = pointerised 1 = other pane //there are also some pane-specific vfs actions in e2_pane_create() action_name = g_strconcat (_A(10),".",_A(106),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_pane_vfs_menu_show, NULL, FALSE); action_name = g_strconcat(_A(50),".",_A(106),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, e2_vfs_create_history_dialog, NULL, FALSE); } #endif //def E2_VFS