/* $Id$ Copyright (C) 2006-2011 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, see http://www.gnu.org/licenses. */ /** @file plugins/e2p_times.c @brief plugin for setting item times, and related functions This is NOT thread-safe */ #include "emelfm2.h" #include #include #include #include "e2_plugins.h" #include "e2_filelist.h" #include "e2_dialog.h" #include "e2_ownership_dialog.h" #include "e2_task.h" typedef struct _E2_TouchData { gboolean continued_after_problem; time_t mtime; time_t atime; time_t ctime; GList *dirdata; } E2_TouchData; typedef struct _E2_TimesDlgRuntime { GtkWidget *mcurrent; GtkWidget *acurrent; GtkWidget *ccurrent; GtkWidget *mdate_combo; GtkWidget *adate_combo; GtkWidget *cdate_combo; GtkWidget *mtime_combo; GtkWidget *atime_combo; GtkWidget *ctime_combo; GtkWidget *set_mtime_button; GtkWidget *set_atime_button; GtkWidget *set_ctime_button; GtkWidget *recurse_button; gboolean *recurse; gboolean permission; DialogButtons real_choice; //store for over-ride dialog choice E2_TouchData *data; } E2_TimesDlgRuntime; static GList *mdate_history = NULL; static GList *mtime_history = NULL; static GList *adate_history = NULL; static GList *atime_history = NULL; static GList *cdate_history = NULL; static GList *ctime_history = NULL; static gboolean _e2p_task_timesQ (E2_ActionTaskData *qed); /** @brief change times of @a path @param path localised string, absolute path of item to change @param sb pointer to struct stat for with info about the current item @param data pointer to replacement time data @return TRUE if the change was completed */ static gboolean _e2pt_touch1 (VPATH *path, const struct stat *statptr, E2_TouchData *data) { struct utimbuf tb; struct timeval systime1, systime2, strt, nd; struct timezone tz; //old or new mod time tb.modtime = (data->mtime == (time_t ) -1) ? statptr->st_mtime : data->mtime; //old or new access time tb.actime = (data->atime == (time_t ) -1) ? statptr->st_atime : data->atime; gboolean retval = TRUE; struct tm *tmptr; gboolean fixc = (data->ctime != (time_t) -1); if (fixc) { //store current sys time gettimeofday (&systime1, &tz); //set sys time to required ctime approx. time_t _now = time (NULL); strt.tv_sec = data->ctime; strt.tv_usec = 0; tmptr = localtime (&_now); if (tmptr->tm_isdst > 0) { //daylight savings is in effect strt.tv_sec -= 3600; //FIXME correct offset } else printd (DEBUG, "DST hack NOT done"); settimeofday (&strt, NULL); } if (fixc || tb.modtime != statptr->st_mtime || tb.actime != statptr->st_atime) { E2_ERR_DECLARE if (e2_fs_utime (path, &tb E2_ERR_PTR())) { #ifdef E2_VFSTMP //FIXME handle error #endif E2_ERR_CLEAR retval = FALSE; } } if (fixc) { //find elapsed time since sys time was reset gettimeofday (&nd, NULL); gint nsec; /* if (nd.tv_usec < strt.tv_usec) { nsec = (strt.tv_usec - nd.tv_usec) / 1000000 + 1; strt.tv_usec -= 1000000 * nsec; strt.tv_sec += nsec; } if (nd.tv_usec - strt.tv_usec > 1000000) { nsec = (strt.tv_usec - nd.tv_usec) / 1000000; strt.tv_usec += 1000000 * nsec; strt.tv_sec -= nsec; } */ systime2.tv_sec = systime1.tv_sec + nd.tv_sec - strt.tv_sec; systime2.tv_usec = systime1.tv_usec + nd.tv_usec - strt.tv_usec; if (systime2.tv_usec > 1000000) { nsec = systime2.tv_usec / 1000000 + 1; systime2.tv_usec -= 1000000 * nsec; systime2.tv_sec += nsec; } //set sys time = old sys time + elapsed time //no DST correction needed settimeofday (&systime2, &tz); } return retval; } /** @brief callback function for recursive directory touches Tree is being walked breadth-first, not physical. Dirs are made accessible and writable if not already so and it's permitted, dirs are added to a list to be processed after all the tree has been traversed, Other items are changed as requested (if possible) Error messages expect BGL off @param localpath absolute path of item to change, localised string @param statptr pointer to struct stat with info about @a localpath @param status code from the walker, indicating what type of report it is @param user_data pointer to user-specified data @return E2TW_CONTINUE on success, others as appropriate */ static E2_TwResult _e2_task_twcb_touch (VPATH *localpath, const struct stat *statptr, E2_TwStatus status, E2_TouchData *user_data) { E2_TwResult retval = E2TW_CONTINUE; //default error code = none E2_ERR_DECLARE switch (status) { mode_t mode, newmode; E2P_DirEnt *dirfix; GList *member; #ifdef E2_VFS VPATH ddata; #endif case E2TW_DP: //dir completion, touch, revert mode if needed, cleanup #ifdef E2_VFS ddata.spacedata = localpath->spacedata; #endif for (member = user_data->dirdata; member != NULL; member = member->next) { dirfix = member->data; if (dirfix != NULL) { if (!strcmp (dirfix->path, localpath)) { #ifdef E2_VFS ddata.path = dirfix->path; if (!_e2pt_touch1 (&ddata, statptr, user_data)) #else if (!_e2pt_touch1 (dirfix->path, statptr, user_data)) #endif retval = E2TW_FIXME; #ifdef E2_VFS if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) //CHECKME ctime effect { e2_fs_error_local (_("Cannot change times of %s"), localpath E2_ERR_MSGL()); retval = E2TW_FIXME; } g_free (dirfix->path); DEALLOCATE (E2P_DirEnt, dirfix); user_data->dirdata = g_list_delete_link (user_data->dirdata, member); break; } } // else //should never happen CHECKME ok when walking list ? // user_data->dirdata = g_list_delete_link (user_data->dirdata, member); } break; case E2TW_DRR: //directory now readable //CHECKME retval |= E2TW_DRKEEP; //no permission reversion in walker, as that stuffs ctime? case E2TW_D: //ensure dir is writable, if we can if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0) { //take a shot at doing the change, anyhow, probably fails _e2pt_touch1 (localpath, statptr, user_data); //FIXME warn user about failure retval |= E2TW_SKIPSUB; //don't try to do any descendant } else //dir can be processed { //add this dir to list of items to touch afterwards dirfix = ALLOCATE (E2P_DirEnt); CHECKALLOCATEDWARNT (dirfix, retval=E2TW_STOP;break;) dirfix->path = g_strdup (VPSTR (localpath)); dirfix->mode = statptr->st_mode & ALLPERMS; //want to restore the original value user_data->dirdata = g_list_prepend (user_data->dirdata, dirfix); } break; case E2TW_DM: //dir not opened (reported upstream) case E2TW_DL: //ditto case E2TW_DNR: //unreadable directory (for which, error is reported upstream) //touch for this will probably fail, but try anyhow mode = statptr->st_mode; //ensure dir is writable, if we can, don't need X permission newmode = e2_fs_tw_adjust_dirmode (localpath, statptr, W_OK); if (newmode == 0) { //take a shot at doing the change, anyhow, probably fails _e2pt_touch1 (localpath, statptr, user_data); //FIXME warn user about failure retval = E2TW_FIXME; } else //dir can be processed { if (!_e2pt_touch1 (localpath, statptr, user_data)) retval = E2TW_FIXME; if (newmode != mode) e2_fs_chmod (localpath, mode E2_ERR_NONE()); //CHECKME ctime effect } break; case E2TW_SL: case E2TW_SLN: case E2TW_F: if (!_e2pt_touch1 (localpath, statptr, user_data)) retval = E2TW_FIXME; break; // case E2TW_NS: //un-statable item (for which, error is reported upstream) default: retval = E2TW_STOP; break; } if (retval & E2TW_SKIPSUB) user_data->continued_after_problem = TRUE; if (retval & E2TW_FIXME) { user_data->continued_after_problem = TRUE; retval &= ~E2TW_FIXME; //continue after bad item } return retval; } /** @brief change time(s) of item @a path to values stored in @a statbuf If invoked on a non-dir, or a dir without recursion, it is processed here. If recursion is invoked on a dir, a ntfw funtion is invoked. By that, all nested dir access permissons will be set to include x, non-dir items will be have their new times set when they are 'reported'. Finally, here, dirs will be set to their original permissions (bottom-up order) at the end of the process Recursive touch works on the host filesystem only. Links are not affected, their target is processed. (OK ??) Error messages expect BGL open @param path localised string, absolute path of item to process @param statbuf ptr to data struct with times to set @param recurse TRUE to do recursive change (ignored if @a path is not a dir) @return TRUE if operation succeeds */ static gboolean _e2p_touch (VPATH *localpath, E2_TouchData *data, gboolean recurse) { struct stat statbuf; E2_ERR_DECLARE if (recurse) { //confirm whether path is a dir or not if (e2_fs_stat (localpath, &statbuf E2_ERR_PTR())) //look _through_ links { //abort if we can't find the item e2_fs_error_local (_("Cannot get current data for %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } if (S_ISDIR (statbuf.st_mode)) { //recursive touch //no processed dirs yet data->dirdata = NULL; gboolean retval = e2_fs_tw (localpath, _e2_task_twcb_touch, data, -1, //flags for fix-DNR, thru-links, this filesystem only, breadth-first E2TW_FIXDIR | E2TW_MOUNT E2_ERR_PTR()); if (data->dirdata != NULL) { //touch and revert dir permissions, LIFO (=bottom-up) order #ifdef E2_VFS VPATH ddata; ddata.spacedata = localpath->spacedata; #endif GList *member; for (member = data->dirdata; member != NULL; member = member->next) { E2P_DirEnt *dirfix = member->data; #ifdef E2_VFS ddata.path = dirfix->path; if (e2_fs_lstat (&ddata, &statbuf E2_ERR_NONE()) //no link pass-thru || !_e2pt_touch1 (&ddata, &statbuf, data)) #else if (e2_fs_lstat (dirfix->path, &statbuf E2_ERR_NONE()) //no link pass-thru || !_e2pt_touch1 (dirfix->path, &statbuf, data)) #endif retval = FALSE; #ifdef E2_VFS if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()) #else if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()) #endif && E2_ERR_ISNOT (ENOENT)) e2_fs_error_local (_("Cannot change permissions of %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR g_free (dirfix->path); DEALLOCATE (E2P_DirEnt, dirfix); } g_list_free (data->dirdata); } // g_free (data); stacked, not heaped if (!retval) { //FIXME handle error E2_ERR_CLEAR } return retval; } else //not dir, handle without recurse recurse = FALSE; } if (!recurse) { //get current times if (e2_fs_lstat (localpath, &statbuf E2_ERR_PTR())) //no link pass-thru { e2_fs_error_local (_("Cannot get current times of %s"), localpath E2_ERR_MSGL()); E2_ERR_CLEAR return FALSE; } return (_e2pt_touch1 (localpath, &statbuf, data)); } return FALSE; //warning prevention only } /** @brief convert strings in @a date_combo and/or @a time_combo to corresponding time variable This is called only from within callback, BGL closed @param current label containing string form of item's current date-time @param date_combo widget with string for new date @param time_combo widget with string for new time @param newdt store for new timevalue (set to -1 if problem occurs) @return TRUE if new time_t is determined successfully */ static gboolean _e2p_times_parse_time (GtkWidget *current, GtkWidget *date_combo, GtkWidget *time_combo, time_t *newdt) { struct tm tm; //clear result structure memset (&tm, '\0', sizeof (tm)); const gchar *date = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (date_combo))) #else GTK_ENTRY (GTK_BIN (date_combo)->child) #endif ); const gchar *time = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (time_combo))) #else GTK_ENTRY (GTK_BIN (time_combo)->child) #endif ); const gchar *currdate = gtk_label_get_text (GTK_LABEL (current)); const gchar *currtime = strchr (currdate, ' ') + 1; //ascii scanning for space char const gchar *cp; gchar *newvalue; if (*date != '\0') { if (*time != '\0') newvalue = g_strconcat (date, " ", time, NULL); else newvalue = g_strconcat (date, " ", currtime, NULL); } else if (*time != '\0') { gchar *freeme = g_strndup (currdate, currtime - currdate - 1); newvalue = g_strconcat (freeme, " ", time, NULL); g_free (freeme); } else { newvalue = g_strdup (currdate); } cp = strptime (newvalue, "%x %X", &tm); if (cp != NULL && *cp == '\0') //all of the string was processed { tm.tm_isdst = -1; //don't correct for daylight saving *newdt = mktime (&tm); //may be set to -1 } else *newdt = (time_t) -1; if (*newdt == (time_t) -1) { gchar *msg = g_strdup_printf (_("Cannot interpret date-time %s"), newvalue); e2_output_print_error (msg, TRUE); } g_free (newvalue); return (*newdt != (time_t) -1); } /** @brief handle apply or apply-to-all button press Converts all entered date-time strings to calendar values If there's a problem, it may set a flag to produce a cancel response for the dialog as a whole @param widget UNUSED the clicked button widget @param rt pointer to dialog data struct @return */ static void _e2p_times_apply_cb (GtkWidget *widget, E2_TimesDlgRuntime *rt) { if (rt->permission) { gboolean success = TRUE; //default condition-flags rt->real_choice = OK; // GtkWidget *entry; const gchar *entrytext; //store any valid new dates/times if ( #ifdef USE_GTK2_14 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->set_mtime_button)) #else GTK_TOGGLE_BUTTON (rt->set_mtime_button)->active #endif ) { success = _e2p_times_parse_time (rt->mcurrent, rt->mdate_combo, rt->mtime_combo, &rt->data->mtime); if (success) { /*as dialog is destroyed each iteration, no point in deferring backup until later //update combo history entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->mdate_combo)); #else GTK_BIN (rt->mdate_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->mtime_combo)); #else GTK_BIN (rt->mtime_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->mdate_combo))) #else GTK_ENTRY (GTK_BIN (rt->mdate_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &mdate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->mtime_combo))) #else GTK_ENTRY (GTK_BIN (rt->mtime_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &mtime_history, NULL, 0, FALSE); } else rt->real_choice = NO; //signal parse-failure } else rt->data->mtime = (time_t) -1; if (success && #ifdef USE_GTK2_14 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->set_atime_button)) #else GTK_TOGGLE_BUTTON (rt->set_atime_button)->active #endif ) { success = _e2p_times_parse_time (rt->acurrent, rt->adate_combo, rt->atime_combo, &rt->data->atime); if (success) { /* //update combo history entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->adate_combo)); #else GTK_BIN (rt->adate_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->atime_combo)); #else GTK_BIN (rt->atime_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->adate_combo))) #else GTK_ENTRY (GTK_BIN (rt->adate_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &adate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->atime_combo))) #else GTK_ENTRY (GTK_BIN (rt->atime_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &atime_history, NULL, 0, FALSE); } else rt->real_choice = NO; //signal parse-failure } else rt->data->atime = (time_t) -1; if (success && #ifdef USE_GTK2_14 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->set_ctime_button)) #else GTK_TOGGLE_BUTTON (rt->set_ctime_button)->active #endif ) { success = _e2p_times_parse_time (rt->ccurrent, rt->cdate_combo, rt->ctime_combo, &rt->data->ctime); if (success) { //warn about system clock changes gchar *prompt = _("Changing 'ctime' requires temporary changes to the system clock." " That is normally unwise, as typically, other things rely on" " system time."); DialogButtons choice = e2_dialog_warning (prompt, NULL); if (choice != OK) { success = FALSE; rt->data->ctime = (time_t) -1; } } else rt->real_choice = NO; //signal parse-failure if (success) { /* //update combo history entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->cdate_combo)); #else GTK_BIN (rt->cdate_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); entry = #ifdef USE_GTK2_14 gtk_bin_get_child (GTK_BIN (rt->ctime_combo)); #else GTK_BIN (rt->ctime_combo)->child; #endif entrytext = gtk_entry_get_text (GTK_ENTRY (entry)); if (*entrytext != '\0') e2_combobox_activated_cb (entry, NULL); */ entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->cdate_combo))) #else GTK_ENTRY (GTK_BIN (rt->cdate_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &cdate_history, NULL, 0, FALSE); entrytext = gtk_entry_get_text ( #ifdef USE_GTK2_14 GTK_ENTRY (gtk_bin_get_child (GTK_BIN (rt->ctime_combo))) #else GTK_ENTRY (GTK_BIN (rt->ctime_combo)->child) #endif ); if (*entrytext != '\0') e2_list_update_history (entrytext, &ctime_history, NULL, 0, FALSE); } } else rt->data->ctime = (time_t) -1; if (success) { //anything valid to change ? if (rt->data->mtime == (time_t) -1 && rt->data->atime == (time_t) -1 && rt->data->ctime == (time_t) -1 ) rt->real_choice = CANCEL; else { rt->real_choice = OK; //the entered choice stands confirmed if (rt->recurse_button != NULL) *rt->recurse = #ifdef USE_GTK2_14 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->recurse_button)); #else GTK_TOGGLE_BUTTON (rt->recurse_button)->active; #endif } // return; } else if (rt->real_choice != NO) //error message already printed by the date interpeter rt->real_choice = CANCEL; } else { //no permisson, the ok click was pointless ... rt->real_choice = CANCEL; } } /** @brief create comboboxentry widget for the dialog @param history history list for the widget (static for the session) @return the widget */ static GtkWidget *_e2p_times_create_combo (GList *history) { GtkWidget *combo = e2_combobox_get ((ActivateFunc)NULL, NULL, &history, E2_COMBOBOX_HAS_ENTRY | E2_COMBOBOX_NO_AUTO_HISTORY | E2_COMBOBOX_FOCUS_ON_CHANGE | E2_COMBOBOX_CYCLE_HISTORY); //show the last-used value, if any if (e2_combobox_has_history (GTK_COMBO_BOX (combo))) e2_combobox_set_active (combo, 0); // gtk_entry_set_activates_default (GTK_ENTRY //#ifdef USE_GTK2_14 // (gtk_bin_get_child (GTK_BIN (combo))), //#else // (GTK_BIN (combo)->child), //#endif // TRUE); // gtk_widget_set_size_request (combo, 40, -1); return combo; } /** @brief create and run time-change dialog Expects BGL open/off. @param local_dir path of dir containing item to process, localised string with trailer @param spacedata pointer to space data for active pane @param info pointer to selection data struct for the item being processed @param recurse_ret pointer to store for user's choice for a recursive change @param statbuf pointer to filesystem data struct for the item being processed @param multi TRUE if the change is part of a multi-item selection @return enumerator corresponing to the clicked dialog button */ static DialogButtons _e2p_times_dialog_run (gchar *local_dir, #ifdef E2_VFS PlaceInfo *spacedata, #endif E2_SelectedItemInfo *info, gboolean *recurse_ret, E2_TouchData *data, gboolean multi) { DialogButtons choice; GtkWidget *times_dialog; GtkWidget *dialog_vbox, *sub_vbox; GtkWidget *hbox; GtkWidget *table; struct stat statbuf; struct tm *tm_ptr; gchar date_string[32]; E2_TimesDlgRuntime rt; gchar *local = e2_utils_strcat (local_dir, info->filename); #ifdef E2_VFS VPATH tdata = { local, spacedata }; if (e2_fs_lstat (&tdata, &statbuf E2_ERR_NONE())) #else if (e2_fs_lstat (local, &statbuf E2_ERR_NONE())) #endif { g_free (local); return CANCEL; } E2_ERR_DECLARE rt.permission = #ifdef E2_VFS e2_fs_check_write_permission (&tdata E2_ERR_PTR()); #else e2_fs_check_write_permission (local E2_ERR_PTR()); #endif //FIXME handle error gboolean thisis_dir = #ifdef E2_VFS e2_fs_is_dir3 (&tdata E2_ERR_PTR()); #else e2_fs_is_dir3 (local E2_ERR_PTR()); #endif //FIXME handle error g_free (local); //copy pointers rt.data = data; rt.recurse = recurse_ret; gdk_threads_enter (); times_dialog = e2_dialog_create (NULL, NULL, _("times"), DUMMY_RESPONSE_CB, NULL); gdk_threads_leave (); dialog_vbox = #ifdef USE_GTK2_14 gtk_dialog_get_content_area (GTK_DIALOG (times_dialog)); #else GTK_DIALOG (times_dialog)->vbox; #endif gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), E2_PADDING); hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (dialog_vbox), hbox, TRUE, TRUE, E2_PADDING); //top, bottom padding gchar *labeltype = (thisis_dir) ? _("Directory name") : _("Filename") ; gchar *utf = F_DISPLAYNAME_FROM_LOCALE (info->filename); gchar *label_text = g_strdup_printf ("%s: %s", labeltype, utf); e2_widget_add_mid_label (hbox, label_text, 0, TRUE, E2_PADDING); //L, R padding F_FREE (utf, info->filename); g_free (label_text); sub_vbox = gtk_vbox_new (FALSE, E2_PADDING); gtk_box_pack_start (GTK_BOX (dialog_vbox), sub_vbox, TRUE, TRUE, E2_PADDING_LARGE); gint rows = (thisis_dir) ? 5 : 4; table = e2_widget_add_table (sub_vbox, rows, 5, FALSE, TRUE, E2_PADDING); //4 rows, 5 cols, !homogen, expand gtk_table_set_col_spacings (GTK_TABLE (table), E2_PADDING); e2_widget_add_mid_label_to_table (table, _("Current values"), 0.5,1,2,0,1); e2_widget_add_mid_label_to_table (table, _("New date"), 0.5, 2,3,0,1); e2_widget_add_mid_label_to_table (table, _("New time"), 0.5, 3,4,0,1); e2_widget_add_mid_label_to_table (table, _("Accessed"), 0, 0,1,1,2); e2_widget_add_mid_label_to_table (table, _("Content Modified"), 0,0,1,2,3); e2_widget_add_mid_label_to_table (table, _("Inode Changed"), 0, 0,1,3,4); tm_ptr = localtime (&statbuf.st_atime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.acurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,1,2); g_free (utf); tm_ptr = localtime (&statbuf.st_mtime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.mcurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,2,3); g_free (utf); tm_ptr = localtime (&statbuf.st_ctime); strftime (date_string, sizeof (date_string), "%x %X", tm_ptr); utf = e2_utf8_from_locale (date_string); rt.ccurrent = e2_widget_add_mid_label_to_table (table, utf, 0,1,2,3,4); g_free (utf); rt.adate_combo = _e2p_times_create_combo (adate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.adate_combo, 2,3,1,2); rt.mdate_combo = _e2p_times_create_combo (mdate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.mdate_combo, 2,3,2,3); rt.cdate_combo = _e2p_times_create_combo (cdate_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.cdate_combo, 2,3,3,4); rt.atime_combo = _e2p_times_create_combo (atime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.atime_combo, 3,4,1,2); rt.mtime_combo = _e2p_times_create_combo (mtime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.mtime_combo, 3,4,2,3); rt.ctime_combo = _e2p_times_create_combo (ctime_history); gtk_table_attach_defaults (GTK_TABLE (table), rt.ctime_combo, 3,4,3,4); rt.set_atime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,1,2);//gint left, gint right, gint top, gint bottom rt.set_mtime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,2,3 ); rt.set_ctime_button = e2_button_add_toggle_to_table (table, _("_Set"), FALSE, NULL, NULL, 4,5,3,4); if (thisis_dir) //no longer do this for 'multi' rt.recurse_button = e2_button_add_toggle_to_table (table, _("R_ecurse:"), FALSE, NULL, NULL, 4,5,4,5 ); else rt.recurse_button = NULL; if (!rt.permission) //no right to change item times { gtk_widget_set_sensitive (rt.mdate_combo, FALSE); gtk_widget_set_sensitive (rt.adate_combo, FALSE); gtk_widget_set_sensitive (rt.cdate_combo, FALSE); gtk_widget_set_sensitive (rt.mtime_combo, FALSE); gtk_widget_set_sensitive (rt.atime_combo, FALSE); gtk_widget_set_sensitive (rt.ctime_combo, FALSE); gtk_widget_set_sensitive (rt.set_mtime_button, FALSE); gtk_widget_set_sensitive (rt.set_atime_button, FALSE); gtk_widget_set_sensitive (rt.set_ctime_button, FALSE); gtk_widget_set_sensitive (rt.recurse_button, FALSE); e2_dialog_setup_auth (dialog_vbox); } else if (getuid () != 0) { //only root can alter system time, which is needed to change ctime gtk_widget_set_sensitive (rt.ctime_combo, FALSE); gtk_widget_set_sensitive (rt.cdate_combo, FALSE); gtk_widget_set_sensitive (rt.set_ctime_button, FALSE); } //block until user responds E2_DialogFlags flags = E2_DIALOG_BLOCKED; // add buttons in the order that they will appear GtkWidget *btn; E2_Button no_btn; if (multi) { e2_dialog_set_negative_response (times_dialog, E2_RESPONSE_NOTOALL); e2_dialog_add_defined_button (times_dialog, &E2_BUTTON_CANCEL); btn = e2_dialog_add_custom_button (times_dialog, &E2_BUTTON_APPLYTOALL, FALSE, E2_BUTTON_APPLYTOALL.tip, _e2p_times_apply_cb, &rt); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); e2_button_derive (&no_btn, &E2_BUTTON_NO, BTN_NO_SKIP); flags |= E2_DIALOG_MULTI; } else { e2_dialog_set_negative_response (times_dialog, GTK_RESPONSE_NO); e2_button_derive (&no_btn, &E2_BUTTON_NO, BTN_NO_CANCEL); } e2_dialog_add_defined_button (times_dialog, &no_btn); btn = e2_dialog_add_custom_button (times_dialog, &E2_BUTTON_APPLY, TRUE, NULL, _e2p_times_apply_cb, &rt); if (!rt.permission) gtk_widget_set_sensitive (btn, FALSE); gdk_threads_enter (); e2_dialog_setup (times_dialog, app.main_window); do { choice = e2_dialog_run (times_dialog, NULL, flags); } while (rt.real_choice == NO); //after date/time parse error, keep trying /*as dialog is destroyed each iteration, no gain in deferring this from the response callback //backup combo histories before dialog destruction e2_combobox_save_history (rt.mdate_combo, &mdate_history); e2_combobox_save_history (rt.mtime_combo, &mtime_history); e2_combobox_save_history (rt.adate_combo, &adate_history); e2_combobox_save_history (rt.atime_combo, &atime_history); e2_combobox_save_history (rt.cdate_combo, &cdate_history); e2_combobox_save_history (rt.ctime_combo, &ctime_history); */ gtk_widget_destroy (times_dialog); gdk_threads_leave (); if (rt.real_choice == CANCEL) choice = CANCEL; return choice; } /** @brief show/change time(s) of selected items in active pane The actual operation is performed by a separate back-end function @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 _e2p_task_times (gpointer from, E2_ActionRuntime *art) { return (e2_task_enqueue_task (E2_TASK_TIMESET, art, from, _e2p_task_timesQ, e2_task_refresh_lists)); } static gboolean _e2p_task_timesQ (E2_ActionTaskData *qed) { //printd (DEBUG, "task: times"); GPtrArray *names = qed->names; gchar *curr_local = qed->currdir; GString *path = g_string_sized_new (PATH_MAX+NAME_MAX); guint count; E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata; gboolean multisrc = names->len > 1; gboolean all = FALSE; E2_TouchData data; #ifdef E2_VFS VPATH sdata; sdata.spacedata = qed->currspace; #endif /* out-of-loop setup = FIXME GtkWidget *dialog; dialog = e2_permissions_dialog_setup (info, &recurse); e2_dialog_add_buttons_simple (dialog, buttons ..., NULL); */ // prepare_dialog(); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, times task"); #endif e2_filelist_disable_refresh (); e2_task_advise (); for (count=0; count < names->len; count++, iterator++) { DialogButtons choice; gboolean permission; gboolean recurse; //".." entries filtered when names compiled g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir #ifdef E2_VFS sdata.path = path->str; #endif if (all) { //check if we have permission to change this item //NB this tests _all_ w permissions, touch not so sophisticated ... #ifdef E2_VFS permission = e2_fs_check_write_permission (&sdata E2_ERR_NONE()); #else permission = e2_fs_check_write_permission (path->str E2_ERR_NONE()); #endif if (!permission) { #ifdef E2_VFS sdata.path = (*iterator)->filename; #endif e2_fs_error_simple ( _("You do not have authority to change time(s) for %s"), #ifdef E2_VFS &sdata); #else (*iterator)->filename); #endif } choice = (permission) ? OK : CANCEL; } else { recurse = FALSE; data.continued_after_problem = FALSE; data.mtime = (time_t) -1; data.atime = (time_t) -1; data.ctime = (time_t) -1; data.dirdata = NULL; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, times dialog"); #endif e2_filelist_enable_refresh (); //allow updates while we wait *qed->status = E2_TASK_PAUSED; choice = _e2p_times_dialog_run (curr_local, #ifdef E2_VFS qed->currspace, #endif *iterator, &recurse, &data, multisrc); *qed->status = E2_TASK_RUNNING; #ifdef E2_REFRESH_DEBUG printd (DEBUG, "disable refresh, permissions dialog"); #endif e2_filelist_disable_refresh (); } switch (choice) { case YES_TO_ALL: all = TRUE; choice = OK; case OK: #ifdef E2_INCLIST # ifdef E2_VFS if (_e2p_touch (&sdata, &data, recurse)) # else if (_e2p_touch (path->str, &data, recurse)) # endif { //FIXME update line in treeview } #else # ifdef E2_FAM # ifdef E2_VFS _e2p_touch (&sdata, &data, recurse); # else _e2p_touch (path->str, &data, recurse); # endif # else # ifdef E2_VFS if (_e2p_touch (&sdata, &data, recurse)) { sdata.path = curr_local; //make the file-list refresher notice successful change e2_fs_utime (&sdata, NULL E2_ERR_NONE()); } # else if (_e2p_touch (path->str, &data, recurse)) //make the file-list refresher notice successful change e2_fs_utime (curr_local, NULL E2_ERR_NONE()); # endif # endif #endif case CANCEL: break; default: choice = NO_TO_ALL; // break flag; break; } if (choice == NO_TO_ALL) break; } g_string_free (path, TRUE); //FIXME E2_INCLIST - just do other view if it's same e2_window_clear_status_message (); #ifdef E2_REFRESH_DEBUG printd (DEBUG, "enable refresh, permissions task"); #endif e2_filelist_enable_refresh (); return TRUE; } //aname must be confined to this module static gchar *aname; /** @brief plugin initialization function, called by main program @param p ptr to plugin data struct @return TRUE if the initialization succeeds, else FALSE */ gboolean init_plugin (Plugin *p) { #define ANAME "times" aname = _("timeset"); p->signature = ANAME VERSION; p->menu_name = _("Change _times.."); p->description = _("Change any of the time properties of selected items"); p->icon = "plugin_timeset"E2ICONTB; //use icon file pathname if appropriate if (p->action == NULL) { //don't free name string here E2_Action plugact = {g_strconcat (_A(6),".",aname,NULL),_e2p_task_times,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL}; p->action = e2_plugins_action_register (&plugact); if G_LIKELY((p->action != NULL)) return TRUE; g_free (plugact.name); } return FALSE; } /** @brief cleanup transient things for this plugin @param p pointer to data struct for the plugin @return TRUE if all cleanups were completed */ gboolean clean_plugin (Plugin *p) { gchar *action_name = g_strconcat (_A(6),".",aname,NULL); gboolean ret = e2_plugins_action_unregister (action_name); g_free (action_name); if (ret) { if (mdate_history != NULL) e2_list_free_with_data (&mdate_history); if (mtime_history != NULL) e2_list_free_with_data (&mtime_history); if (adate_history != NULL) e2_list_free_with_data (&adate_history); if (atime_history != NULL) e2_list_free_with_data (&atime_history); if (cdate_history != NULL) e2_list_free_with_data (&cdate_history); if (ctime_history != NULL) e2_list_free_with_data (&ctime_history); } return ret; }