/* $Id$ Copyright (C) 2007-2009 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 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, contact the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file plugins/optional/e2p_vfs.c @brief grab-bag of vfs-library-independent functions needed for vfs operations protocol management fs-operations interface URI handling namespace changes / mounting Placeinfo management vtab (history) management dialogs actions */ /* 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 figure out & apply plugin [un]loading process */ #include "emelfm2.h" #ifdef E2_VFS #include #include #include #include #include #include #include #include #ifndef MNT_LOCAL # include #endif #include "e2_dialog.h" #include "e2_task.h" #include "e2_filelist.h" #include "e2_plugins.h" //static gboolean _e2_vfs_unpackQ (E2_ActionTaskData *qed); void e2_list_update_history_simple (gpointer latest, GList **history); static guint32 _e2_vfs_create_signature (const guchar *string); /***********************/ /* protocol management */ /***********************/ GList *remote_history; GList *arch_history; #ifdef E2_VFS_COLLECTIONS GList *synth_history; #endif /* actual history lists are cached, see above these arrays have default values only, no regard to whal's actually available in handler-libraries the strings are in same order as E2_VFSProtocol enum in header Not all the strings are put into default protocol lists ensure RPROTOCOUNT in header is defined in header in accord with no of strings here */ gchar *remote_protocols[RPROTOCOUNT] = { "ftp", "ftps", "http", "https", //end of listed items "ssh", "fish", "fsp" }; //ensure APROTOCOUNT in header is defined in header in accord with no of strings //here //these all have corresponding E2_VPROTO_... gchar *archive_protocols[APROTOCOUNT] = { "tbz2", "tgz", "tar", "zip", //end of listed items "7z", "ace", "ape", "ar", "arc", "arj", "cab", "cpio", "deb", "flac", "lha", "lzma", "rar", "rpm", "shn", "zoo" }; //ensure SPROTOCOUNT in header is defined in header in accord with no of strings //here gchar *synth_protocols[SPROTOCOUNT] = { //no listed items }; #define XAPROTOCOUNT 16 //special-cases, where archive-file-extension != protocol gchar *archxtypes[XAPROTOCOUNT] = { "a", //see ar "ark", //see arc "cbr", //see rar "cbz", //see zip "ear", //see zip "jar", //see zip "lzh", //see lha "pk3", //see zip "pk4", //see zip "sue", //see arc "tar.bz2",//see tbz2 "taz" //see tgz "tbz", //see tbz2 "tar.gz",//see tgz "wsz", //see zip "xpi" //see zip }; //and the matching protocol no's guint archxnums[XAPROTOCOUNT] = { E2_VPROTO_AR, E2_VPROTO_ARC, E2_VPROTO_RAR, E2_VPROTO_ZIP, E2_VPROTO_ZIP, E2_VPROTO_ZIP, E2_VPROTO_LHA, E2_VPROTO_ZIP, E2_VPROTO_ZIP, E2_VPROTO_ARC, E2_VPROTO_TBZ2, E2_VPROTO_TGZ, E2_VPROTO_TBZ2, E2_VPROTO_TGZ, E2_VPROTO_ZIP, E2_VPROTO_ZIP }; /* peazip Full support: 7Z, 7Z-sfx, BZip2, GZip/TGZ, PAQ8F, PAQ8JD, PAQ8L, PEA, QUAD, split (.001), TAR, ZIP; Browse/extract support: ACE, ARJ, CAB, CHM, CPIO, ISO, Java archives (JAR, EAR, WAR), Linux installers (DEB, PET/PUP, RPM, SLP), LHA, LZH, Open Office file types, PAK/PK3/PK4, RAR, Windows installers (NSIS, some MSI), Z/TZ */ /** @brief try to determine protocol-type corresponding to @a protoname @param protoname string form of protocol, "ftp" etc @return the number which matches the type, or E2_VPROTO_INVALID */ static E2_VFSDialogType _e2_vfs_match_prototype (const gchar *protoname) { guint i; if (g_list_find_custom (remote_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL) return REMOTE_DLG; if (g_list_find_custom (arch_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL) return ARCH_DLG; #ifdef E2_VFS_COLLECTIONS if (g_list_find_custom (synth_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL) return SYNTH_DLG; #endif for (i = 0; i < RPROTOCOUNT; i++) { if (g_str_equal (protoname, remote_protocols[i])) return REMOTE_DLG; } for (i = 0; i < APROTOCOUNT; i++) { //note - not necessarily a suffix if (strstr (protoname, archive_protocols[i]) != NULL) return ARCH_DLG; } for (i = 0; i < XAPROTOCOUNT; i++) { //note - not necessarily a suffix if (strstr (protoname, archxtypes[i]) != NULL) return ARCH_DLG; } #ifdef E2_VFS_COLLECTIONS for (i = 0; i < SPROTOCOUNT; i++) { if (g_str_equal (protoname, synth_protocols[i])) return SYNTH_DLG; } #endif return E2_VPROTO_INVALID; } /** @brief determine protocol-type enum corresponding to @a protoname, based on user-choice @param protoname string form of protocol, "ftp" etc @return the number which matches the type, or E2_VPROTO_INVALID */ static DialogButtons _e2_vfs_ask_prototype (const gchar *protoname, E2_VFSDialogType *type) { gchar *text = g_strdup_printf (_("Is %s for:"), protoname); GtkWidget *dialog = e2_dialog_create (NULL, text, _("protocol"), DUMMY_RESPONSE_CB, NULL); g_free (text); GtkWidget *dialog_vbox = #ifdef USE_GTK2_14 gtk_dialog_get_content_area (GTK_DIALOG (dialog)); #else GTK_DIALOG (dialog)->vbox; #endif GSList *group; GtkWidget *leader, *next, *last; leader = e2_button_add_radio (dialog_vbox, _("_Archives"), NULL, FALSE, FALSE, E2_PADDING, NULL, NULL); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); next = e2_button_add_radio (dialog_vbox, _("_Remote computers"), group, FALSE, FALSE, E2_PADDING, NULL, NULL); #ifdef E2_VFS_COLLECTIONS group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (leader)); last = e2_button_add_radio (dialog_vbox, _("_Other"), group, TRUE, FALSE, E2_PADDING, NULL, NULL); #endif //block until user selects DialogButtons choice = e2_dialog_show (dialog, app.main_window, E2_DIALOG_BLOCKED, &E2_BUTTON_CANCEL, &E2_BUTTON_APPLY, NULL); if (choice == OK) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (leader))) *type = ARCH_DLG; else #ifdef E2_VFS_COLLECTIONS if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (next))) #endif *type = REMOTE_DLG; #ifdef E2_VFS_COLLECTIONS else *type = SYNTH_DLG; #endif } gtk_widget_destroy (dialog); //now it's safe to destroy the widgets return choice; } /** @brief determine protocol enum corresponding to @a protoname @param protoname string form of protocol, "ftp" etc @return the number which matches the string, or E2_VPROTO_INVALID */ //E2_VFSTMP WHY DO WE NEED A PROTOCOL NUMBER, ANYWAY ? static E2_VFSProtocol _e2_vfs_get_protonum (const gchar *protoname) { //FIXME make this dynamic, using history lists as well as arrays E2_VFSProtocol proto = E2_VPROTO_INVALID; gint i; guint count = g_list_length (remote_history); for (i = 0; i < count; i++) { // if (g_str_equal (protoname, remote_protocols[i])) if (g_str_equal (protoname, g_list_nth_data (remote_history,i))) { //THIS IS UTTER CRAP FIXME proto = (E2_VFSProtocol) i; break; } } if (proto == E2_VPROTO_INVALID) { count = g_list_length (arch_history); for (i = 0; i < count; i++) { // if (g_str_equal (protoname, archive_protocols[i])) if (g_str_equal (protoname, g_list_nth_data (arch_history,i))) { //THIS IS UTTER CRAP FIXME proto = (E2_VFSProtocol) i + E2_VPROTO_MINARCHIVE; break; } } } if (proto == E2_VPROTO_INVALID) { count = g_list_length (synth_history); for (i = 0; i < count; i++) { // if (g_str_equal (protoname, synth_protocols[i])) if (g_str_equal (protoname, g_list_nth_data (synth_history,i))) { //THIS IS UTTER CRAP FIXME proto = (E2_VFSProtocol) i + E2_VPROTO_MINSYNTH; break; } } } return proto; } /** @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 */ static E2_VFSProtocol _e2_vfs_get_protonum_for_file (gchar *filename) { //FIXME make this dynamic, using archives history list as well as arrays E2_VFSProtocol proto = LASTPROTOCOUNT; GList *match; gchar *target; guint i; for (i = 0; i < APROTOCOUNT; i++) { //note - not necessarily a suffix if (strstr (filename, archive_protocols[i]) != NULL) { proto = E2_VPROTO_MINARCHIVE + i; return proto; } } for (i = 0; i < XAPROTOCOUNT; i++) { //note - not necessarily a suffix if (strstr (filename, archxtypes[i]) != NULL) { proto = archxnums[i]; return proto; } } if ((target = strrchr (filename, '.')) != NULL && target > (filename + sizeof(gchar))) { target++; if (*target == '\0') proto = E2_VPROTO_INVALID; } else proto = E2_VPROTO_INVALID; if (proto != E2_VPROTO_INVALID) { match = g_list_find_custom (arch_history, target, (GCompareFunc) e2_list_strcmp); if (match != NULL) //THIS IS UTTER CRAP FIXME proto = (E2_VFSProtocol) g_list_position (arch_history, match); else proto = E2_VPROTO_INVALID; } return proto; } /** @brief update relevant protocol-history list @param prototype enumerator of which list to update @param protoname string form of protocol, "ftp" etc @return */ static void _e2_vfs_update_protocol_history (E2_VFSDialogType prototype, const gchar *protoname) { switch (prototype) { case REMOTE_DLG: e2_list_update_history (protoname, &remote_history, NULL, E2_VPROTO_MINARCHIVE, FALSE); break; case ARCH_DLG: e2_list_update_history (protoname, &arch_history, NULL, E2_VPROTO_MINSYNTH - E2_VPROTO_MINARCHIVE, FALSE); break; #ifdef E2_VFS_COLLECTIONS case SYNTH_DLG: e2_list_update_history (protoname, &synth_history, NULL, 0, FALSE); break; #endif default: break; } } /** @brief update all relevant protocol-history lists with data from a liststore Can be used for the main vfs data store or for a protocol store of a dialog combo renderer @param prototype enumerator of which list to update @param protoname string form of protocol, "ftp" etc @return */ static void _e2_vfs_update_all_protocol_history (GtkTreeModel *mdl, guint colnum) { GtkTreeIter iter; if (gtk_tree_model_get_iter_first (mdl, &iter)) { gchar *protoname; do { gtk_tree_model_get (mdl, &iter, colnum, &protoname, -1); if (protoname != NULL) { g_strstrip (protoname); if (*protoname == '\0') { g_free (protoname); continue; } //if protocol is already listed, ignore it if (g_list_find_custom (remote_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL || g_list_find_custom (arch_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL #ifdef E2_VFS_COLLECTIONS || g_list_find_custom (synth_history, protoname, (GCompareFunc) e2_list_strcmp) != NULL #endif ) { g_free (protoname); continue; } //decide what sort of protocol this is //if it's in a default array, that's easy guint i; for (i = 0; i < RPROTOCOUNT; i++) { if (g_str_equal (protoname, remote_protocols[i])) { _e2_vfs_update_protocol_history (REMOTE_DLG, protoname); break; } } if (i < RPROTOCOUNT) { g_free (protoname); continue; } for (i = 0; i < APROTOCOUNT; i++) { if (g_str_equal (protoname, archive_protocols [i])) { _e2_vfs_update_protocol_history (REMOTE_DLG, protoname); break; } } if (i < APROTOCOUNT) { g_free (protoname); continue; } #ifdef E2_VFS_COLLECTIONS for (i = 0; i < SPROTOCOUNT; i++) { if (g_str_equal (protoname, synth_protocols[i])) { _e2_vfs_update_protocol_history (SYNTH_DLG, protoname); break; } } if (i < SPROTOCOUNT) { g_free (protoname); continue; } #endif //otherwise... ? FIXME #ifdef E2_VFS_COLLECTIONS //park for now in another list, user can clean up later _e2_vfs_update_protocol_history (SYNTH_DLG, protoname); #endif //OR simply ignore this one g_free (protoname); } } while (gtk_tree_model_iter_next (mdl, &iter)); } } /***************************/ /* fs-operations interface */ /***************************/ /** @brief decide which handler applies to @a place //FIX have regard to namespace and also desired function //FIX handle functions which have 2 different places @param place pointer to namespace data @return handler enumerator */ static E2_HandlerLib _e2_vfs_choose_libhandler (PlaceInfo *place) //.operation-type) { if (place == NULL) return E2_LIBNONE; return E2_LIBGVFS; } /** @brief setup for vfs function by getting relevant handler lib if not already present @param libtype handler enumerator @return pointer to functions interface for the handler, NULL on error */ static E2_FsFuncs *_e2_vfs_get_libhandler (E2_HandlerLib libtype) { E2_FsFuncs *iface; switch (libtype) { case E2_LIBGVFS: if (vfuncs[E2_LIBGVFS] == NULL) //gvfs plugin not loaded { e2_plugins_load_plugin ("gvfs"VERSION); iface = vfuncs [E2_LIBGVFS]; //may still be NULL break; } default: iface = NULL; break; } return iface; } /** @brief dialog response callback Optionally converts the actual dialog response to the interface standard @param dialog UNUSED the dialog where the response was generated @param response the generated response number @param w pointer to wait-data struct @return */ static void _e2p_vfs_slow_response_cb (GtkDialog *dialog, gint response, E2_Wait *w) { w->choice = e2_dialog_response_decode (response); e2_main_loop_quit (w->loopdata); } /** @brief slow-vfs-operation timeout callback @param opdata pointer to management data @return FALSE always */ static gboolean _e2_vfs_slow_operation_cb (E2_VFSMonitor *opdata) { // g_source_remove (opdata->timer_id); //in case dialog opens, and user is slow to respond g_mutex_lock (opdata->mutx); opdata->timer_id = 0; DialogButtons choice; //to allow this block to be killed by _e2_vfs_finish_operation(), don't use normal mainloop func opdata->loopdata = e2_main_loop_new (FALSE); if (opdata->loopdata != NULL) { //create a blocked dialog opdata->slow_dialog = e2_dialog_slow (_("VFS activity"), _("process"), DUMMY_RESPONSE_CB, NULL); g_mutex_unlock (opdata->mutx); //default result in case of abort E2_Wait wdata = { CANCEL, opdata->loopdata }; g_signal_connect (G_OBJECT (opdata->slow_dialog), "response", G_CALLBACK (_e2p_vfs_slow_response_cb), &wdata); gtk_window_present (GTK_WINDOW (opdata->slow_dialog)); e2_main_loop_run (opdata->loopdata); g_mutex_lock (opdata->mutx); opdata->loopdata = NULL; g_mutex_unlock (opdata->mutx); choice = wdata.choice; } else { g_mutex_unlock (opdata->mutx); choice = IGNORE; } g_mutex_lock (opdata->mutx); gtk_widget_destroy (opdata->slow_dialog); opdata->slow_dialog = NULL; g_mutex_unlock (opdata->mutx); if (choice == CANCEL) { g_cancellable_cancel (opdata->c); //CHECKME do something with opdata->error // g_mutex_free (opdata->mutx); } else if (choice != YES_TO_ALL) //quiet { if (opdata->interval < 30) opdata->interval += 10; g_mutex_lock (opdata->mutx); opdata->timer_id = #ifdef USE_GLIB2_14 g_timeout_add_seconds (opdata->interval, #else g_timeout_add (opdata->interval * 1000, #endif (GSourceFunc)_e2_vfs_slow_operation_cb, opdata); g_mutex_unlock (opdata->mutx); } // else // g_mutex_free (opdata->mutx); return FALSE; } /** @brief "response" signal callback for progress dialog @param dialog the dialog where the response was generated @param response enumerator of activated widget or event @param opdata pointer to operation data struct @return */ static void _e2_vfs_progdialog_response_cb (GtkDialog *dialog, gint response, E2_VFSMonitor *opdata) { g_mutex_lock (opdata->mutx); if (opdata->progress_dialog != NULL) { gtk_widget_destroy (opdata->progress_dialog); //= GTK_WIDGET (dialog) opdata->progress_dialog = NULL; opdata->progbar = NULL; } if (response == E2_RESPONSE_NOTOALL && (opdata->flags & E2VFS_CANCEL)) { if (opdata->c != NULL) g_cancellable_cancel (opdata->c); //else kill a thread ? } g_mutex_unlock (opdata->mutx); } /** @brief setup for vfs function by relevant handler lib @a funcoffset is to check for non-NULL pointer, indicating function presence @param place namespace data @param funcoffset offset into the lib functions interface @param opdata set of management parametets for the operation @return TRUE if all done as expected */ static gboolean _e2_vfs_start_operation (PlaceInfo *place, off_t funcoffset, E2_VFSMonitor *opdata) { E2_HandlerLib lib = _e2_vfs_choose_libhandler (place); if (lib < 0) return FALSE; E2_FsFuncs *iface = _e2_vfs_get_libhandler (lib); if (iface == NULL) return FALSE; if (*(gpointer *)(iface + funcoffset) == NULL) return FALSE; //some things in the data need to be preserved E2_VFSMonitorType saveflags = opdata->flags; const gchar *progtitle = opdata->progtitle; const gchar *progmsg = opdata->progmsg; //main label content const gchar *progfmt = opdata->progfmt; //a format sring with a "%d", for printing current percentage memset (opdata, 0, sizeof (E2_VFSMonitor)); opdata->flags = saveflags; opdata->iface = iface; if (opdata->iface->prefunc != NULL) { opdata->iface->prefunc (); } if (saveflags & E2VFS_CANCEL && lib == E2_LIBGVFS) opdata->c = g_cancellable_new (); if (saveflags & E2VFS_TIMER) { opdata->interval = 10; opdata->mutx = g_mutex_new (); opdata->timer_id = #ifdef USE_GLIB2_14 g_timeout_add_seconds (10, #else g_timeout_add (10000, #endif (GSourceFunc)_e2_vfs_slow_operation_cb, opdata); } /* else if (saveflags & E2VFS_THREAD) { } */ /*CHECKME setup error? if (saveflags & E2VFS_NEWERR) { *opdata->error = ?? } */ if (saveflags & E2VFS_PROG) { const gchar *realtitle = (progtitle != NULL) ? progtitle : _("vfs operation"); // gchar *realmsg = (progmsg != NULL) ? (gchar *)progmsg : F_FILENAME_FROM_LOCALE (?); // gchar *realmsg = (progmsg != NULL) ? (gchar *)progmsg : func? (funcoffset); opdata->progress_dialog = e2_dialog_create (NULL, progmsg, realtitle, _e2_vfs_progdialog_response_cb, opdata); // if (realmsg != progmsg) // g_free (realmsg); gtk_dialog_set_has_separator (GTK_DIALOG (opdata->progress_dialog), FALSE); GtkWidget *dialog_vbox = #ifdef USE_GTK2_14 gtk_dialog_get_content_area (GTK_DIALOG (opdata->progress_dialog)); #else GTK_DIALOG (opdata->progress_dialog)->vbox; #endif opdata->progbar = gtk_progress_bar_new (); // gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (opdata->progbar), 0.0); //some initial text on the progress bar? if (progfmt != NULL) { opdata->progfmt = progfmt; //reinstate for others to use gchar *progresstext = g_strdup_printf (progfmt, 0); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (opdata->progbar), progresstext); g_free (progresstext); } GTK_WIDGET_SHOW (opdata->progbar); gtk_box_pack_start (GTK_BOX (dialog_vbox), opdata->progbar, TRUE, TRUE, E2_PADDING_LARGE); e2_dialog_setup (opdata->progress_dialog, app.main_window); //E2_VFSTMP //allow cancelling the operation if (saveflags & E2VFS_CANCEL) { E2_Button stop_btn; stop_btn = E2_BUTTON_CANCEL; stop_btn.tip = _("Terminate current activity"); stop_btn.showflags |= E2_BTN_TIPPED; e2_dialog_add_defined_button (opdata->progress_dialog, &stop_btn); } gdk_threads_enter (); gtk_widget_show_all (opdata->progress_dialog); gdk_threads_leave (); } return TRUE; } /** @brief tidy up progress dialog after short delay for user to absorb status @param dialog the dialog widget to close @return FALSE always */ static gboolean _e2_vfs_progess_clear_cb (GtkWidget *dialog) { gdk_threads_enter (); gtk_widget_destroy (dialog); gdk_threads_leave (); return FALSE; } /** @brief tidy up after vfs function by relevant handler lib @param iface pointer to handler operations table @param opdata pointer to operation managment data @return TRUE if all done as expeced */ static gboolean _e2_vfs_finish_operation (E2_VFSMonitor *opdata) { if (opdata->mutx != NULL) { g_mutex_lock (opdata->mutx); if (opdata->slow_dialog != NULL) { //DOES THIS WORK IN ANOTHER THREAD? //cause the timer callback to resume operation and exit normally (no mutex change there) if (opdata->loopdata != NULL) { E2_MainLoop *tmp = opdata->loopdata; opdata->loopdata = NULL; e2_main_loop_quit (tmp); } g_mutex_unlock (opdata->mutx); //callback still using the mutex for a while g_usleep (50000); //so wait before mutex destruction } //else g_mutex_free (opdata->mutx); } /* if (opdata->error != NULL) { gdk_threads_enter (); //CHECKME any error mssage to display ? // g_error_free (GError *); gdk_threads_leave (); } */ //CHECKME these ok outside mutex lock ? NULL them ? if (opdata->flags & E2VFS_CANCEL && opdata->c != NULL) g_object_unref (G_OBJECT (opdata->c)); if (opdata->loopdata != NULL) { e2_main_loop_quit (opdata->loopdata); opdata->loopdata = NULL; } if (opdata->flags & E2VFS_PROG && opdata->progress_dialog != NULL) { if (opdata->flags & E2VFS_CANCEL) gtk_dialog_set_response_sensitive (GTK_DIALOG (opdata->progress_dialog), E2_RESPONSE_NOTOALL, FALSE); g_signal_handlers_disconnect_matched (G_OBJECT (opdata->progress_dialog), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, _e2_vfs_progdialog_response_cb, opdata); #ifdef USE_GLIB2_14 g_timeout_add_seconds (2, #else g_timeout_add (2000, #endif (GSourceFunc)_e2_vfs_progess_clear_cb, opdata->progress_dialog); } if (opdata->iface->postfunc!= NULL) return (opdata->iface->postfunc ()); return TRUE; } /* each virtual func should do like this: E2_VFSMonitor opdata; memset (&opdata, 0, sixeof (E2_VFSMonitor)); some things in opdata set to non-0 to flag desired setup E2_FsFuncs *iface = NULL; e2_vfs_begin_operation (PlaceInfo *place, funcoffset, &opdata); CHECK what about 2-place op's ? E2_FsFuncs *iface2 = NULL; e2_vfs_begin_operation (PlaceInfo *otherplace, funcoffset2?, &opdata-with-final-flags); handle 2-step op as appropriate e.g. get; do; put; if (iface != NULL) { (*iface->some function) (handler-args) e2_vfs_operation_finish (iface, &opdata); } OR maybe setup so that (*iface->some function) (handler-args) is run inside some func called by a func that does everything else here */ /********************/ /*** URI handling ***/ /********************/ //#include static PlaceInfo *_e2_vfs_get_place_from_string (ViewInfo *view, const gchar *given_placename) { PlaceInfo *spacedata; gchar *s, *placename; GList *history; gint hist_cur; placename = g_strdup (given_placename); g_strstrip (placename); if (*placename == '=') { s = e2_utils_find_whitespace (placename + sizeof (gchar)); if (s == placename + sizeof (gchar)) { s = e2_utils_pass_whitespace (s); if (s != NULL) placename = s; //otherwise, the string is just "=" which might be an alias } } if (g_str_equal (placename, "%v")) spacedata = curr_view->spacedata; else if (g_str_equal (placename, "%V")) spacedata = other_view->spacedata; else if (*placename == '+') { if (view == &app.pane1_view) { hist_cur = vfs.pane1_spacecurrent; history = vfs.pane1_spacehistory; } else { hist_cur = vfs.pane2_spacecurrent; history = vfs.pane2_spacehistory; } if (history == NULL) { g_free (placename); return (PlaceInfo *)-1; } //FIXME check for +, +N spacedata = NULL; //FIXME } else if (*placename == '-') { if (view == &app.pane1_view) { hist_cur = vfs.pane1_spacecurrent; history = vfs.pane1_spacehistory; } else { hist_cur = vfs.pane2_spacecurrent; history = vfs.pane2_spacehistory; } if (history == NULL) { g_free (placename); return (PlaceInfo *)-1; } //FIXME check for -, -N, spacedata = NULL; //FIXME } else if (g_str_has_prefix (placename, _("first"))) { if (view == &app.pane1_view) { hist_cur = vfs.pane1_spacecurrent; history = vfs.pane1_spacehistory; } else { hist_cur = vfs.pane2_spacecurrent; history = vfs.pane2_spacehistory; } if (history == NULL) { g_free (placename); return (PlaceInfo *)-1; } //FIXME check first, first+N spacedata = NULL; //FIXME } else if (g_str_has_prefix (placename, _("last"))) { if (view == &app.pane1_view) { hist_cur = vfs.pane1_spacecurrent; history = vfs.pane1_spacehistory; } else { hist_cur = vfs.pane2_spacecurrent; history = vfs.pane2_spacehistory; } if (history == NULL) { g_free (placename); return (PlaceInfo *)-1; } //FIXME check for last, last-N spacedata = NULL; //FIXME } else { spacedata = NULL; //FIXME #ifdef E2_VFSTMP //FIXME check for any alias //FIXME check for URI gchar *s, *pathhost = NULL; //g_filename_from_uri() no good here (works only for file:// and strips protocol) if ((s = strstr (placename, "://")) == NULL) { //split string into 2 parts, if we can s += 3; //skip located ascii bytes s = strchr (s, G_DIR_SEPARATOR); if (s == NULL) { g_free (placename); return NULL; } // pathhost = g_strndup (path, s - path); // path = g_strdup (s); } //gvfs function GDecodedUri *_g_decode_uri (placename); #endif } g_free (placename); return spacedata; } static gchar *_e2_vfs_get_uri_from_place (PlaceInfo *spacedata) { gchar *uri = g_strdup ("FIXME"); // gchar *uri = _g_encode_uri (decoded, FALSE); //gboolean allow_utf8); return uri; } /*********************/ /* namespace changes */ /*********************/ static gchar hex[] = "0123456789abcdef"; gchar *e2_utils_ascify_signature (guint32 number) { guint8 bytes [4]; //sizeof (guint32) / 8 gchar result [9]; //sizeof (sum) * 2 + 1 gint i; guint32 *passer; passer = (guint32 *)bytes; *passer = number; for (i = 0; i < 4; ++i) { result [2 * i] = hex [(bytes[i] >> 4) & 0xf]; result [2 * i + 1] = hex [bytes[i] & 0xf]; } result [2 * i] = 0; return g_strdup (result); } gboolean e2_utils_unascify_signature (gchar *string, guint32 *converted) { guint8 bytes [4]; //sizeof (guint32) / 8 guint8 j; gint i; guint32 *processed; gchar *s; if (strlen (string) < 8) return FALSE; //force lc string ? for (i = 0; i < 4; ++i) { s = strchr (hex, string [2 * i]); if (s == NULL) return FALSE; j = (s - hex) / sizeof (gchar); bytes [i] = j << 4; s = strchr (hex, string [2 * i + 1]); if (s == NULL) return FALSE; j = (s - hex) / sizeof (gchar); bytes [i] |= j; } processed = (guint32 *)bytes; *converted = *processed; return TRUE; } gchar *e2_utils_ascify_string (gchar *unsignedstring) { gint len = strlen (unsignedstring); gchar result [len * 2 + 1]; gint i; for (i = 0; i < len; ++i) { result [2 * i] = hex [(unsignedstring[i] >> 4) & 0xf]; result [2 * i + 1] = hex [unsignedstring[i] & 0xf]; } result [2 * i] = 0; return g_strdup (result); } gboolean e2_utils_unascify_string (gchar *string, gchar **converted) { gint i, j; gint len = strlen (string); guint8 p; gchar result [len / 2 + 1]; gchar c; gchar *s; for (i = 0, j = 0; i < len; i+=2, j++) { s = strchr (hex, string [i]); if (s == NULL) return FALSE; p = (s - hex) / sizeof (gchar); c = p << 4; s = strchr (hex, string [i + 1]); if (s == NULL) return FALSE; p = (s - hex) / sizeof (gchar); c |= p; result [j] = c; } result [j] = 0; *converted = g_strdup (result); return TRUE; } /** @brief change namespace for @a view to @a spacedata @param view pointer to data struct for view being changed @param spacedata pointer to data struct for the new namespace, NULL for native fs @param history TRUE to update corresonding history list @return TRUE if change is completed */ gboolean e2_vfs_set_space (ViewInfo *view, PlaceInfo *spacedata, gboolean history) { static gint change_blocked = 0; //prevents both panes if (view->spacedata == spacedata) return TRUE; //already there if (change_blocked++ > 0) //block re-entrant changes { //FIXME wait a while } #ifdef E2_VFSTMP //backup stuff WHERE TO PUT LOCAL DIR LISTS ? //replace backed-up stuff for new spacedata //[un]mounting when appropriate - (optimise how ?) #endif view->spacedata = spacedata; if (history) { GList *thishist = (view == &app.pane1_view) ? vfs.pane1_spacehistory : vfs.pane2_spacehistory; e2_list_update_history_simple (spacedata, &thishist); //FIXME reorganise the list as for cd } #ifdef E2_VFSTMP //run space-change hooklist //change dir to last-used one from new history #endif change_blocked--; //unblock re-entrant changes return TRUE; } /** @brief update place history list This is a simpler form of the string-list update function @a latest will be prepended to the list. @param latest pointer to be listed @param history store for history list pointer @return */ void e2_list_update_history_simple (gpointer latest, GList **history) { GList *tmp = g_list_find (*history, latest); if (tmp != NULL) { //found the string to be added in history already if (tmp != *history) { //somewhere later in the list *history = g_list_remove_link (*history, tmp); *history = g_list_concat (tmp, *history); } } else { //new entry tmp = *history; if (tmp == NULL || tmp->data != latest) *history = g_list_prepend (*history, latest); } } /** @brief interpret @a placename into a new or existing Placeinfo, and open it This always updates the spaces history, unless there is actually no change of space @param placename string form of place to open, utf-8 @param view pointer to data struct for the view to be changed @return */ gboolean e2_vfs_set_named_space (ViewInfo *view, const gchar *placename) { PlaceInfo *spacedata = _e2_vfs_get_place_from_string (view, placename); if (spacedata == (PlaceInfo *)-1) return FALSE; return (e2_vfs_set_space (view, spacedata, TRUE)); } /** @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 */ //FIX THIS - NO PROTONUM, INDEX UNRELIABLE static gchar *_e2_vfs_get_tip (gint index) { gchar *text; GtkTreeIter iter; if (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (vfs.vtab), &iter, NULL, index)) { gchar *protname, *host; E2_VFSProtocol proto; gtk_tree_model_get (GTK_TREE_MODEL (vfs.vtab), &iter, VFSPROTONAME, &protname, VFSHOSTNAME, &host, VFSPATH, &text, // VFSPROTO, &proto, -1); if (proto < E2_VPROTO_MINARCHIVE) //FIXME check lists insead of arrays { //remote site g_free (text); text = g_strconcat (protname, "://", host, NULL); g_free (protname); g_free (host); } } else text = ""; return g_strdup (text); } /** @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 UTF-8 string */ static gchar *_e2_utils_get_absolute_dirpath (gchar *path, E2_FSDirType type, ViewInfo *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 construct qualified path for an item in a virtual dir @param localpath localised path string possibly with prepended vfs signature @return newly-allocated, fully-qulified path string, NULL upon missing vfs data */ //FIXME this is bad static gchar *_e2_vfs_make_virtual_path (VPATH *localpath) { if (!e2_fs_item_is_mounted (localpath)) return g_strdup (VPSTR (localpath)); //CHECKME is this optimal ? #ifdef E2_VFSTMP FIXME #endif guint32 *vfstabid = (guint32 *)(localpath + 1); guint32 signature = *vfstabid; guint32 value; GtkTreeModel *model = GTK_TREE_MODEL(vfs.vtab); GtkTreeIter iter; if (!gtk_tree_model_get_iter_first (model, &iter)) return NULL; do { gtk_tree_model_get (model, &iter, VFSID, &value, -1); if (value == signature) break; } while (gtk_tree_model_iter_next (model, &iter)); if (value != signature) return NULL; // return needed vfs stuff .... gchar *prefix = ""; //FIXME //skip the leading esc, and 32-bit signature gchar *fullpath = g_strconcat (prefix, localpath + 5, NULL); return fullpath; } /** @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 */ static 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); g_free (utfdir); F_FREE (localdir, utfdir); utfdir = D_FILENAME_FROM_LOCALE (unique); VPATH ddata = { unique, NULL }; E2_ERR_DECLARE //make it if (e2_fs_recurse_mkdir (&ddata, 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); //BGL must be closed for this e2_output_print_error (msg, TRUE); g_free (unique); g_free (utfdir); E2_ERR_CLEAR return NULL; } g_free (unique); return utfdir; } /** @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 VIEWDIR @param view pointer to view data struct. May be NULL if @a type requires data from curr_view. @return newly-allocated utf8 string */ static gchar *_e2_utils_get_work_dirpath (E2_FSDirType type, ViewInfo *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 get the "work" dir for the filelist associated with @a view @param view pointer to view data struct @return newly-allocated path string, localised, no trailing / */ static gchar *_e2_fs_get_localdir (ViewInfo *view) { gchar *localdir; if (view->spacedata == NULL) { localdir = D_FILENAME_TO_LOCALE (view->dir); *(localdir + strlen (localdir) - sizeof (gchar)) = '\0'; //strip ascii trailer } else { #ifdef E2_VFS #ifdef E2_VFSTMP FIXME #else localdir = view->spacedata->workplace; //FIXME BIGTIME #endif #else localdir = "/tmp"; #endif } return localdir; } /** @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 */ static 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 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 (ViewInfo *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 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, ViewInfo *view) { E2_ToggleType num = (view == &app.pane1_view) ? E2_TOGGLE_PANE1SPACE: E2_TOGGLE_PANE2SPACE; 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; #ifdef USE_GTK2_12TIPS g_free (ex->true_tip); //CHECKME ok ? ex->true_tip = g_strdup (tiptext); //CHECKME set ex->tool widget tip #else g_free (ex->tips->tip_text); //CHECKME ok ? ex->tips->tip_text = g_strdup (tiptext); #endif } } } } /** @brief vfs menu-item callbacks First one is to change space to one of recent-places @param menu_item the selected menu item widget @param view pointer to data struct for view to be changed @return */ static void _e2_menu_vfs_item_virtual_cb (GtkMenuItem *menu_item, ViewInfo *view) { #ifdef E2_VFSTMP FIXME use function direct #endif /* if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_space)) { PlaceInfo *place = (PlaceInfo *) g_object_get_data (G_OBJECT (menu_item), "vfsplace"); vfs.set_space (view, place, FALSE); } else printd (WARN, "Cannot find vfs plugin"); */ PlaceInfo *place = (PlaceInfo *) g_object_get_data (G_OBJECT (menu_item), "vfsplace"); e2_vfs_set_space (view, place, FALSE); } /** @brief change space to local */ static void _e2_menu_vfs_item_local_cb (GtkMenuItem *menu_item, ViewInfo *view) { #ifdef E2_VFSTMP FIXME use function direct #endif /* if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.set_space)) vfs.set_space (view, NULL, FALSE); else printd (WARN, "Cannot find vfs plugin"); */ e2_vfs_set_space (view, NULL, FALSE); } /** @brief change space to archive */ static void _e2_menu_vfs_openarch_cb (GtkMenuItem *menu_item, ViewInfo *view) { e2_vfs_dialog_create (ARCH_DLG, view); } /** @brief change space to "remote" dir */ static void _e2_menu_vfs_openremote_cb (GtkMenuItem *menu_item, ViewInfo *view) { e2_vfs_dialog_create (REMOTE_DLG, view); } #ifdef E2_VFS_COLLECTIONS /** @brief change space to "remote" dir */ static void _e2_menu_vfs_opencollect_cb (GtkMenuItem *menu_item, ViewInfo *view) { e2_vfs_dialog_create (SYNTH_DLG, view); } #endif /** @brief change some aspect of current space, (and apply it?) */ static void _e2_menu_vfs_item_adjust_cb (GtkMenuItem *menu_item, ViewInfo *view) { /* if (e2_fs_vfsfunc_ready ((gpointer *)&vfs.show_adjustdialog)) { */ PlaceInfo *spacedata = curr_view->spacedata; if (spacedata != NULL) { // vfs.show_adjustdialog (view); E2_VFSDialogType type; if ((spacedata->dirtype & FS_ARCHIVE) != 0) type = ARCH_DLG; //might also be remote else if ((spacedata->dirtype & FS_REMOTE) != 0) type = REMOTE_DLG; else type = SYNTH_DLG; e2_vfs_dialog_create (type, view); //FIXME some way to apply any change made ? #ifdef E2_VFSTMP FIXME #endif } /* } else printd (WARN, "Cannot find vfs plugin"); */ } /** @brief create most of vfs menu for showing in pane to which @a view belongs The stub of the menu (item which allows loading this plugin) is in _e2_pane_vfs_menu_show(), the rest is here @param menu the widget to which items will be added @param view data struct for view to be changed @param history TRUE after a -click, to show a history menu @return */ void _e2_vfs_places_menu_create (GtkWidget *menu, ViewInfo *view, gboolean history) { GtkWidget *item; GList *thishist = (view == &app.pane1_view) ? vfs.pane1_spacehistory : vfs.pane2_spacehistory; if (history) { //offer up recent places, if any PlaceInfo *spacedata; GList *member; for (member = thishist; member != NULL; member = member->next) { spacedata = (PlaceInfo *)member->data; if (spacedata != view->spacedata) { item = e2_menu_add (menu, spacedata->alias, NULL, NULL, _e2_menu_vfs_item_virtual_cb, view); g_object_set_data (G_OBJECT (item), "vfsplace", spacedata); } } } else { if (view->spacedata == NULL) { //CHECKME need tips here ? e2_menu_add (menu, _("_Archive"), NULL, NULL, _e2_menu_vfs_openarch_cb, view); e2_menu_add (menu, _("_Remote"), NULL, NULL, _e2_menu_vfs_openremote_cb, view); #ifdef E2_VFS_COLLECTIONS e2_menu_add (menu, _("_Collection"), NULL, NULL, _e2_menu_vfs_opencollect_cb, view); #endif e2_menu_add_separator (menu); } else //currently showing a vdir { item = e2_menu_add (menu, _("_Local filesystem"), NULL, NULL, _e2_menu_vfs_item_local_cb, view); //don't use real radio menu items, to prevent double callbacks // gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), TRUE); //FIXME add some same-type go-forward and go-backward items // e2_menu_add_separator (menu); if (thishist != NULL) { GList *member = g_list_find (thishist, view->spacedata); //FIXME get and apply tip from data item = e2_menu_add (menu, _("_Previous place"), NULL, NULL, _e2_menu_vfs_item_virtual_cb, view); if (member != NULL && member->next != NULL) //additions are prepended to list { #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, ((PlaceInfo *)member->next->data)->alias); g_object_set_data (G_OBJECT (item), "vfsplace", member->next->data); } else gtk_widget_set_sensitive (item, FALSE); item = e2_menu_add (menu, _("_Next place"), NULL, NULL, _e2_menu_vfs_item_virtual_cb, view); if (member != NULL && member->prev != NULL) //additions are prepended to list { #ifdef USE_GTK2_12TIPS gtk_widget_set_tooltip_text ( #else e2_widget_set_tooltip (NULL, #endif item, ((PlaceInfo *)member->prev->data)->alias); g_object_set_data (G_OBJECT (item), "vfsplace", member->prev->data); } else gtk_widget_set_sensitive (item, FALSE); e2_menu_add_separator (menu); } // if (view->spacedata != NULL) // { e2_menu_add (menu, _("_Adjust current place"), NULL, NULL, _e2_menu_vfs_item_adjust_cb, view); } } } /************************/ /* Placeinfo management */ /************************/ /* * @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, there is no parent checking, 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 # ifdef MNT_LOCAL struct statfs fstatus; gchar localpath = F_FILENAME_TO_LOCALE (utfpath); gint result = statfs (localpath, &fstatus); F_FREE (localpath, utfpath); if (result) { if (result == ESTALE) { printd (DEBUG, "%s resides in an unmounted VFS", utfpath); //CHECKME mount it ? } return TRUE; //any operation on utfpath will fail ASAP } if (!(fstatus.f_flags & MNT_LOCAL)) return FALSE; # else struct statvfs fstatus; gchar *localpath; localpath = F_FILENAME_TO_LOCALE (text); retry: if (statvfs (localpath, &fstatus)) { if (errno == EINTR) goto retry; // else if (errno == ENOLINK) // re-mount it ?? // else if (errno == ENOENT && e2_utils_get_parent_path (localpath, TRUE)) // goto retry; F_FREE (localpath, text); return FALSE; } F_FREE (localpath, text); # endif //FIXME move some tests to fs module ? //FIXME MORE TESTS e.g. //from string contents e.g. "//:" //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 update all vptrs to reflect any change in vtab store contents */ static void _e2_vfs_update_mtablist (void) { PlaceInfo *spacedata; GList *member; for (member = vfs.vmtab; member != NULL; member = member->next) { spacedata = (PlaceInfo *)member->data; if (0) { //E2_VFSTMP //get matching signature ? treerowref ? //if changed content //change same in spacedata if possible //else log this place into backup (since it was mounted i.e. valid mountpoint) //cleanup or backup member->data = NULL; } } vfs.vmtab = g_list_remove_all (vfs.vmtab, NULL); } /** @brief update pane place-history lists to reflect any change in vtab store and vmtab list contents This is done after _e2_vfs_update_mtablist() */ static void _e2_vfs_clean_placehistories (void) { GList *member; for (member = vfs.pane1_spacehistory; member != NULL; member = member->next) { //if member no longer in vmtab list, or is somehow now unmounted, NULL the data if (g_list_find (vfs.vmtab, member->data) == NULL) member->data = NULL; else if (((PlaceInfo *)member->data)->mountstate != E2PLACE_MOUNTED) member->data = NULL; } for (member = vfs.pane2_spacehistory; member != NULL; member = member->next) { if (g_list_find (vfs.vmtab, member->data) == NULL) member->data = NULL; else if (((PlaceInfo *)member->data)->mountstate != E2PLACE_MOUNTED) member->data = NULL; } vfs.pane1_spacehistory = g_list_remove_all (vfs.pane1_spacehistory, NULL); vfs.pane2_spacehistory = g_list_remove_all (vfs.pane2_spacehistory, NULL); } //THIS PROBABLY needs to be public, each use of a vfunc may need to check this static gboolean _e2_vfs_place_is_mounted (PlaceInfo *space) { /* data only exist for mounted places now GList *member; for (member = vfs.space_history; member != NULL; member = member->next) { if (member->data == space) return (space->mountstate == E2PLACE_MOUNTED); } return FALSE; another list of cleared-from-history places too ? */ return (space->mountstate == E2PLACE_MOUNTED); } static void _e2_vfs_clear_place_history (GList **history) { GList *member; PlaceInfo *data; for (member = *history; member != NULL; member = member->next) { data = (PlaceInfo *)member->data; if (data->priv_name != NULL) g_free (data->priv_name); if (data->tip != NULL) g_free (data->tip); if (data->plain_pw != NULL) g_free (data->plain_pw); if (data->alias != NULL) g_free (data->alias); if (data->workplace != NULL) g_free (data->workplace); //E2_VFSTMP GtkTreeRowReference ? //lists of strings if (data->pane1_entered != NULL) e2_list_free_with_data (&data->pane1_entered); if (data->pane2_entered != NULL) e2_list_free_with_data (&data->pane1_entered); //lists of ALLOCATE'd E2_DirHistoryEntry's if (data->pane1_history != NULL) e2_fileview_clean_history (&data->pane1_history); if (data->pane2_history != NULL) e2_fileview_clean_history (&data->pane2_history); //these ones share data if (data->pane1_visited != NULL) g_list_free (data->pane1_visited); if (data->pane2_visited != NULL) g_list_free (data->pane2_visited); DEALLOCATE (PlaceInfo, data); } } /** @brief cleanup history data This may be called at session-end, or when plugin is unloaded FIXME need an interface for use by main shutdown function @return */ static void _e2_vfs_clear_places_history (void) { // _e2_vfs_clear_place_history (&vfs.pane1_spacehistory); // _e2_vfs_clear_place_history (&vfs.pane2_spacehistory); _e2_vfs_clear_place_history (&vfs.vmtab); g_list_free (vfs.pane1_spacehistory); g_list_free (vfs.pane2_spacehistory); } /**************************************/ /* places-store (vfs.vtab) management */ /**************************************/ /** @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; if (columns < 1) columns = gtk_tree_model_get_n_columns (model); for (j = 0; j < level; j++) treerow = g_string_append_c (treerow, '\t'); 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 (str_data[0] != '\0') { if (j == 0 && str_data[0] == '>') treerow = g_string_append (treerow, "\\"); if (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 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 create empty liststore for vfs places history data @return */ static void _e2_vfs_create_history_store (void) { //CHECKME worth having a protocol category column ? vfs.vtab = gtk_list_store_new (VFSCOLCOUNT, G_TYPE_STRING, //0 protocol (string) G_TYPE_STRING, //1 hostname G_TYPE_STRING, //2 archive path G_TYPE_STRING, //3 user G_TYPE_STRING, //4 password (plaintext) G_TYPE_STRING, //5 port (string) G_TYPE_STRING, //6 alias //end of editable items G_TYPE_STRING, //7 pane1 lastdir G_TYPE_STRING, //8 pane2 lastdir //end of cached items G_TYPE_BOOLEAN,//9 gboolean TRUE when this place is ready to use (R/only if in dialog) // G_TYPE_INT, //10 protonum (integer) G_TYPE_BOOLEAN,//10 row has been edited G_TYPE_UINT, //11 CRC-32 signature, for matching store entries G_TYPE_POINTER //12 ptr to PlaceInfo data struct, or NULL ); } /** @brief save 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 VFSDISPLAY-sized array of strings to store @return */ static void _e2_vfs_store_history_data (GtkTreeIter *iter, const gchar *parts[]) { // E2_VFSProtocol protonum = _e2_vfs_get_protonum (parts[VFSPROTONAME]); //CHECKME worth having a protocol category column ? //FIXME create a suitable ID string guint32 ID = _e2_vfs_create_signature ((guchar *)"FIXMEstring"); if (iter == NULL) { GtkTreeIter iter2; //gtk >= 2.10 can handle &iter2 = NULL gtk_list_store_insert_with_values (vfs.vtab, &iter2, -1, VFSPROTONAME, parts[VFSPROTONAME], VFSHOSTNAME, parts[VFSHOSTNAME], VFSPATH, parts[VFSPATH], VFSUSER, parts[VFSUSER], VFSPASSWORD, parts[VFSPASSWORD], VFSPORT, parts[VFSPORT], VFSALIAS, parts[VFSALIAS], VFSAHIST1, "", VFSAHIST2, "", VFSMOUNTED, FALSE, // VFSPROTO, protonum, VFSID, ID, VFSDATA, NULL,//init with non-existent data ptr -1); } else { gtk_list_store_set (vfs.vtab, iter, VFSPROTONAME, parts[VFSPROTONAME], VFSHOSTNAME, parts[VFSHOSTNAME], VFSPATH, parts[VFSPATH], VFSUSER, parts[VFSUSER], VFSPASSWORD, parts[VFSPASSWORD], VFSPORT, parts[VFSPORT], VFSALIAS, parts[VFSALIAS], // VFSPROTO, protonum, VFSID, ID, // don't change the PlaceInfo pointer, mounted flag // or any history pointer -1); } } /** @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; guint32 ID; gchar *parts[VFSDISPLAY]; GtkTreeIter iter; GList *member; _e2_vfs_create_history_store (); *store = vfs.vtab; //send back the store pointer GtkListStore *lstore = vfs.vtab; for (member = rowslist; member != NULL; member = member->next) { e2_utils_rowstr_split ((gchar *)member->data, VFSDISPLAY, parts); //CHECKME worth having a protocol category column ? // proto = _e2_vfs_get_protonum (parts[VFSPROTONAME]); // if (proto == E2_VPROTO_INVALID) // proto = E2_VPROTO_MINARCHIVE; //default to this //FIXME create a suitable ID string ID = _e2_vfs_create_signature ((guchar *)"FIXMEstring"); //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], VFSALIAS, parts[VFSALIAS], VFSAHIST1, "", VFSAHIST2, "", VFSMOUNTED, FALSE, // VFSPROTO, proto, VFSEDITED, FALSE, VFSID, ID, VFSDATA, NULL,//init with non-existent data ptrs -1); //cleanup for (i = 0; i < VFSDISPLAY; i++) g_free (parts[i]); } } /** @brief convert vfs places history data from liststore to glist, for cacheing Saves only initial 6 columns of data @param store pointer to liststore to process, vfs.vtab @param unsync_data UNUSED pointer to data assigned when the cache was initialised @return the constructed list of strings */ static GList * _e2_vfs_desync_history (gpointer store, gpointer unsync_data) { GList *rowslist; GtkTreeModel *model; GtkTreeIter iter; rowslist = NULL; model = GTK_TREE_MODEL ((GtkListStore *)store); if (gtk_tree_model_get_iter_first (model, &iter)) { gchar *rowstring; do { rowstring = e2_tree_row_to_string (model, &iter, VFSDISPLAY, 0); rowslist = g_list_append (rowslist, rowstring); } while (gtk_tree_model_iter_next (model, &iter)); } return rowslist; } /** @brief calculate CRC-32 value for @a string For matching a vfs-table entry. See also: _e2pcr_getcrc32() @param string pointer to the string to be processed @return the 32-bit signature for @a string */ static guint32 _e2_vfs_create_signature (const guchar *string) { /* Standards : ISO 3309, ITU-T V.42, ANSI X3.66, IEEE 802.3, FIPS PUB 71 Initializing value : ffffffff Finalizing value : ffffffff Polynomial value : 04c11db7 (mirror value = edb88320) Polynomial : x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 This code is derived from lib_crc v.1.15, by Lammert Bies */ static guint32 crc_table [256]; static gboolean crc_table_init = FALSE; guint32 crc, long_c; guint8 indx; guchar c; const guchar *s; if (!crc_table_init) { guint i, j; for (i = 1; i < 256; i++) //crc_tab[0] is always 0 { crc = (guint32)i; for (j = 0; j < 8; j++) { if (crc & 0x1) crc = ( crc >> 1 ) ^ 0xedb88320; else crc = crc >> 1; } crc_table [i] = crc; } crc_table_init = TRUE; } crc = 0xffffffff; s = string; while ((c = *s) != '\0') { long_c = (guint32)c & 0xff ; indx = (crc ^ long_c) & 0xff; crc = (crc >> 8) ^ crc_table [indx]; s++; } crc ^= 0xffffffff; return crc; } /***************/ /*** dialogs ***/ /***************/ //get dialog functions #define INCLUDED_IN_PARENT #include "e2_vfs_dialog.c" #undef INCLUDED_IN_PARENT /***************/ /*** actions ***/ /***************/ /** @brief create and show vtab history dialog @param from the button, menu item etc which was activated @param art action runtime data @return TRUE always */ static gboolean _e2_vfs_create_history_dialog (gpointer from, E2_ActionRuntime *art) { //need to know which pane to change if the user proceeds E2_PaneRuntime *rt = e2_pane_get_runtime (from, art->data, NULL); e2_vfs_create_history_dialog (rt->view); return TRUE; } static gboolean _e2_vfs_unpackQ (E2_ActionTaskData *qed); /** @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) { 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_part (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); //FIXME g_free (filename); F_FREE (curr_dir, qed->currdir); //FIXME E2_INCLIST - just do relevant view #ifdef E2_FAM e2_filelist_request_refresh (CURRDIR, FALSE); e2_filelist_request_refresh (OTHRDIR, TRUE); #else e2_filelist_check_dirty (GINT_TO_POINTER(1)); #endif return TRUE; } /** @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 @param data UNUSED data specified when the idle was connected @return FALSE to remove source */ static gboolean _e2_vfs_initialise (gpointer data) { #ifdef E2_VFSTMP checkme can be private func ? #endif //do stuff return FALSE; } /** @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 "vfs" p->signature = ANAME VERSION; p->menu_name = ""; p->description = _("Vfs functions interface"); p->icon = ""; if (p->action == NULL) { //there is no user-initated action for this plugin //do-nothing-action, in case the user puts plugin into menu gchar *action_name = g_strconcat(_A(1),".",_A(121),NULL); p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM, e2_action_inaction, NULL, FALSE, 0, NULL); //register "non-menu" vfs-related actions //(there are also some pane-specific vfs actions in e2_pane.c, which may //cause this plugin to be loaded) action_name = g_strconcat(_A(6),".",_A(104),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(6),".",_A(123),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 action_name = g_strconcat(_A(13),".",_A(120),NULL); e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM, _e2_vfs_create_history_dialog, NULL, TRUE); // 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); e2_cache_list_register ("vfs-remote-types", &remote_history); e2_cache_list_register ("vfs-archive-types", &arch_history); #ifdef E2_VFS_COLLECTIONS e2_cache_list_register ("vfs-synth-types", &synth_history); #endif guint i; if (remote_history == NULL) { for (i = 0; i < RPROTOINIT; i++) remote_history = g_list_append (remote_history, g_strdup (remote_protocols [i])); } if (arch_history == NULL) { for (i = 0; i < APROTOINIT; i++) arch_history = g_list_append (arch_history, g_strdup (archive_protocols [i])); } #ifdef E2_VFS_COLLECTIONS if (synth_history == NULL) { for (i = 0; i < SPROTOINIT; i++) synth_history = g_list_append (synth_history, g_strdup (synth_protocols [i])); } #endif //this relies on history lists, so done after default lists are done e2_cache_store_register ("vfs-places-data", (gpointer *) &vfs.vtab, _e2_vfs_sync_history, _e2_vfs_desync_history, NULL); //update protocol history lists from data now in store if (vfs.vtab != NULL) _e2_vfs_update_all_protocol_history (GTK_TREE_MODEL (vfs.vtab), VFSPROTONAME); //do the slow things when we can g_idle_add ((GSourceFunc) _e2_vfs_initialise, NULL); vfs.show_adjustdialog = e2_vfs_dialog_create;//setup pointer for running specific dialog vfs.show_historydialog = e2_vfs_create_history_dialog;//setup pointer for running general dialog vfs.set_space = e2_vfs_set_space; //and for changing space vfs.set_namedspace = e2_vfs_set_named_space; vfs.create_placesmenu = _e2_vfs_places_menu_create; vfs.start_operation = _e2_vfs_start_operation; vfs.finish_operation = _e2_vfs_finish_operation; vfs.clean_history = _e2_vfs_clear_places_history; vfs.loaded = TRUE; return TRUE; } 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) { vfs.loaded = FALSE; //ASAP gchar *action_name = g_strconcat (_A(1),".",_A(121),NULL); e2_plugins_action_unregister (action_name); g_free (action_name); action_name = g_strconcat(_A(6),".",_A(104),NULL); e2_action_unregister (action_name); g_free (action_name); action_name = g_strconcat(_A(6),".",_A(123),NULL); e2_action_unregister (action_name); g_free (action_name); action_name = g_strconcat(_A(13),".",_A(120),NULL); e2_action_unregister (action_name); g_free (action_name); /*see memset below vfs.show_adjustdialog = NULL; vfs.show_historydialog = NULL; vfs.set_space = NULL; vfs.set_namedspace = NULL; vfs.create_placesmenu = NULL; vfs.clean_history = NULL; */ //backup the cache data e2_cache_unregister ("vfs-places-data"); // e2_cache_unregister ("pane1-fs-type"); // e2_cache_unregister ("pane2-fs-type"); e2_cache_unregister ("vfs-remote-types"); e2_cache_unregister ("vfs-archive-types"); e2_cache_unregister ("vfs-synth-types"); //now we can cleanup e2_list_free_with_data (&remote_history); e2_list_free_with_data (&arch_history); e2_list_free_with_data (&synth_history); _e2_vfs_clear_places_history (); //CHECKME keep list until session-end, in case reloaded? if (vfs.vtab != NULL) g_object_unref (G_OBJECT (vfs.vtab)); //CHECKME keep store until session-end, in case reloaded? memset (&vfs, 0, sizeof (E2_VFSInterface)); //do other stuff ? return TRUE; } #endif //def E2_VFS