From 68b718ac833c46735efbe0cb11b203613316ba76 Mon Sep 17 00:00:00 2001 From: Stephen Watson Date: Tue, 2 Dec 2003 19:21:24 +0000 Subject: [PATCH] r3247: Experimental extended attributes support for setting MIME types --- ROX-Filer/Help/Changes | 2 + ROX-Filer/src/Makefile.in | 4 +- ROX-Filer/src/action.c | 204 +++++++++++++++++++++++++++++++++++++++++++++ ROX-Filer/src/action.h | 1 + ROX-Filer/src/config.h.in | 3 + ROX-Filer/src/configure.in | 3 + ROX-Filer/src/diritem.c | 3 +- ROX-Filer/src/menu.c | 16 ++++ ROX-Filer/src/type.c | 26 +++++- ROX-Filer/src/type.h | 2 + ROX-Filer/src/xtypes.c | 163 ++++++++++++++++++++++++++++++++++++ ROX-Filer/src/xtypes.h | 23 +++++ 12 files changed, 446 insertions(+), 4 deletions(-) create mode 100644 ROX-Filer/src/xtypes.c create mode 100644 ROX-Filer/src/xtypes.h diff --git a/ROX-Filer/Help/Changes b/ROX-Filer/Help/Changes index 0a138c7a..58d5e4d0 100644 --- a/ROX-Filer/Help/Changes +++ b/ROX-Filer/Help/Changes @@ -5,6 +5,8 @@ 02-Dec-2003 ~~~~~~~~~~~ Allow editing a symlink's target from the Properties box (Arnaud Calvo). +Experimental extended attributes support for setting MIME types (Stephen +Watson). 29-Nov-2003 ~~~~~~~~~~~ diff --git a/ROX-Filer/src/Makefile.in b/ROX-Filer/src/Makefile.in index 718ce0a5..fa7c85ec 100644 --- a/ROX-Filer/src/Makefile.in +++ b/ROX-Filer/src/Makefile.in @@ -27,7 +27,7 @@ SRCS = abox.c action.c appinfo.c appmenu.c bind.c bookmarks.c \ modechange.c mount.c options.c panel.c pinboard.c pixmaps.c \ remote.c rox_gettext.c run.c sc.c session.c support.c \ tasklist.c toolbar.c type.c usericons.c view_collection.c \ - view_details.c view_iface.c wrapped.c xml.c + view_details.c view_iface.c wrapped.c xml.c xtypes.c OBJECTS = abox.o action.o appinfo.o appmenu.o bind.o bookmarks.o \ cell_icon.o choices.o collection.o dir.o diritem.o display.o \ @@ -37,7 +37,7 @@ OBJECTS = abox.o action.o appinfo.o appmenu.o bind.o bookmarks.o \ modechange.o mount.o options.o panel.o pinboard.o pixmaps.o \ remote.o rox_gettext.o run.o sc.o session.o support.o \ tasklist.o toolbar.o type.o usericons.o view_collection.o \ - view_details.o view_iface.o wrapped.o xml.o + view_details.o view_iface.o wrapped.o xml.o xtypes.o ############ Things to keep the same diff --git a/ROX-Filer/src/action.c b/ROX-Filer/src/action.c index b32d1760..63f650c8 100644 --- a/ROX-Filer/src/action.c +++ b/ROX-Filer/src/action.c @@ -51,6 +51,8 @@ #include "dir.h" #include "icon.h" #include "mount.h" +#include "type.h" +#include "xtypes.h" /* Parent->Child messages are one character each: * @@ -100,6 +102,7 @@ static unsigned long file_counter; /* For Disk Usage */ static struct mode_change *mode_change = NULL; /* For Permissions */ static FindCondition *find_condition = NULL; /* For Find */ +static MIME_type *type_change=NULL; /* Only used by child */ static gboolean o_force = FALSE; @@ -117,6 +120,7 @@ static Option o_action_newer; */ static guchar *last_chmod_string = NULL; static guchar *last_find_string = NULL; +static guchar *last_settype_string = NULL; /* Set to one of the above before forking. This may change over a call to * reply(). It is reset to NULL once the text is parsed. @@ -275,6 +279,45 @@ static void show_chmod_help(gpointer data) gtk_widget_show_all(help); } + +static void show_settype_help(gpointer data) +{ + GtkWidget *help; + GtkWidget *text; + + help = gtk_dialog_new_with_buttons( + _("Set type reference"), + NULL, 0, + GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(help), GTK_RESPONSE_CANCEL); + + text = gtk_label_new(NULL); + gtk_misc_set_padding(GTK_MISC(text), 2, 2); + gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(help)->vbox), text); + gtk_label_set_selectable(GTK_LABEL(text), TRUE); + gtk_label_set_markup(GTK_LABEL(text), _( +"Normally ROX-Filer determines the type of a regular file\n" +"by matching it's name against a pattern. To change the\n" +"type of the file you must rename it.\n" +"\n" +"Newer file systems can support something called 'Extended\n" +"Attributes' which can be used to store additional data with\n" +"each file as named parameters. ROX-Filer uses the\n" +"'user.mime_type' attribute to store file types.\n" +"\n" +"File types are only supported for regular files, not\n" +"directories, devices, pipes or sockets, and then only\n" +"on certain file systems and where the OS implements them.\n" +"\n" +ATTR_MAN_PAGE )); + + g_signal_connect(help, "response", + G_CALLBACK(gtk_widget_destroy), NULL); + + gtk_widget_show_all(help); +} + static void process_message(GUIside *gui_side, const gchar *buffer) { ABox *abox = gui_side->abox; @@ -1116,6 +1159,85 @@ static void do_chmod(const char *path, const char *unused) } } +static void do_settype(const char *path, const char *unused) +{ + struct stat info; + const char *comment; + + check_flags(); + + if (mc_lstat(path, &info)) + { + send_error(); + return; + } + if (S_ISLNK(info.st_mode)) + return; + + if (!quiet) + { + printf_send("<%s", path); + printf_send(">"); + if (!printf_reply(from_parent, FALSE, + _("?Change type of '%s'?"), path)) + return; + } + for (;;) + { + if (new_entry_string) + { + type_change=mime_type_lookup(new_entry_string); + null_g_free(&new_entry_string); + } + + if (type_change) + break; + + printf_send(_("!Invalid type - " + "change it and try again\n")); + if (!printf_reply(from_parent, TRUE, + _("?Change type of '%s'?"), path)) + return; + } + comment=mime_type_comment(type_change); + + if (!o_brief) + printf_send(_("'Changing type of '%s' to '%s'\n"), path, + comment); + + if (mc_lstat(path, &info)) + { + send_error(); + return; + } + if (S_ISLNK(info.st_mode)) + return; + + if(S_ISREG(info.st_mode)) + { + if (xtype_set(path, type_change)) + { + send_error(); + return; + } + + send_check_path(path); + + } + else if(!S_ISDIR(info.st_mode)) + { + send_mount_path(path); + + if (o_recurse) + { + guchar *safe_path; + safe_path = g_strdup(path); + for_dir_contents(do_settype, safe_path, unused); + g_free(safe_path); + } + } +} + /* We want to copy 'object' into directory 'dir'. If 'action_leaf' * is set then that is the new leafname, otherwise the leafname stays * the same. @@ -1675,6 +1797,29 @@ static void chmod_cb(gpointer data) send_done(); } +static void settype_cb(gpointer data) +{ + GList *paths = (GList *) data; + + for (; paths; paths = paths->next) + { + guchar *path = (guchar *) paths->data; + struct stat info; + + send_dir(path); + + if (mc_stat(path, &info) != 0) + send_error(); + else if (S_ISLNK(info.st_mode)) + printf_send(_("!'%s' is a symbolic link\n"), + g_basename(path)); + else + do_settype(path, NULL); + } + + send_done(); +} + static void list_cb(gpointer data) { GList *paths = (GList *) data; @@ -1898,6 +2043,65 @@ out: null_g_free(&new_entry_string); } +/* Set the MIME type of the selected items */ +void action_settype(GList *paths, gboolean force_recurse, const char *oldtype) +{ + GtkWidget *abox; + GUIside *gui_side; + static GList *presets = NULL; + gboolean recurse = force_recurse || o_action_recurse.int_value; + + if (!paths) + { + report_error(_("You need to select the items " + "whose type you want to change")); + return; + } + + if (!presets) + { + presets = mime_type_name_list(); + } + + if (!last_settype_string) + last_settype_string = g_strdup("text/plain"); + + if (oldtype) + new_entry_string = g_strdup(oldtype); + else + new_entry_string = g_strdup(last_settype_string); + + abox = abox_new(_("Set type"), FALSE); + gui_side = start_action(abox, settype_cb, paths, + o_action_force.int_value, + o_action_brief.int_value, + recurse, + o_action_newer.int_value); + + if (!gui_side) + goto out; + + abox_add_flag(ABOX(abox), + _("Brief"), _("Don't list processed files"), + 'B', o_action_brief.int_value); + abox_add_flag(ABOX(abox), + _("Recurse"), _("Change contents of subdirectories"), + 'R', recurse); + + gui_side->default_string = &last_settype_string; + abox_add_combo(ABOX(abox), presets, new_entry_string, + new_help_button(show_settype_help, NULL)); + + g_signal_connect(ABOX(abox)->entry, "changed", + G_CALLBACK(entry_changed), gui_side); + + number_of_windows++; + gtk_widget_show(abox); + +out: + null_g_free(&new_entry_string); +} + /* If leaf is NULL then the copy has the same name as the original. * quiet can be -1 for default. */ diff --git a/ROX-Filer/src/action.h b/ROX-Filer/src/action.h index e87ca1c1..50201e65 100644 --- a/ROX-Filer/src/action.h +++ b/ROX-Filer/src/action.h @@ -21,5 +21,6 @@ void action_link(GList *paths, const char *dest, const char *leaf); void action_eject(GList *paths); void show_condition_help(gpointer data); void set_find_string_colour(GtkWidget *widget, const guchar *string); +void action_settype(GList *paths, gboolean force_recurse, const char *oldtype); #endif /* _ACTION_H */ diff --git a/ROX-Filer/src/config.h.in b/ROX-Filer/src/config.h.in index 720c85ad..7ba85a0d 100644 --- a/ROX-Filer/src/config.h.in +++ b/ROX-Filer/src/config.h.in @@ -20,6 +20,9 @@ #undef HAVE_REGEX_H +#undef HAVE_GETXATTR +#undef HAVE_ATTROPEN + /* Enable extensions - used for dnotify support */ #ifndef _GNU_SOURCE # define _GNU_SOURCE diff --git a/ROX-Filer/src/configure.in b/ROX-Filer/src/configure.in index fa826d09..12700d45 100644 --- a/ROX-Filer/src/configure.in +++ b/ROX-Filer/src/configure.in @@ -142,6 +142,9 @@ AC_TYPE_SIZE_T dnl Checks for library functions. AC_CHECK_FUNCS(gethostname unsetenv mkdir rmdir strdup strtol getopt_long) +dnl Check for extended attribute support +AC_CHECK_FUNCS(attropen getxattr) + dnl Extract version info from AppInfo.xml AC_MSG_CHECKING(extracting version information) [ diff --git a/ROX-Filer/src/diritem.c b/ROX-Filer/src/diritem.c index 0c8588ee..1c813f66 100644 --- a/ROX-Filer/src/diritem.c +++ b/ROX-Filer/src/diritem.c @@ -44,6 +44,7 @@ #include "options.h" #include "fscache.h" #include "pixmaps.h" +#include "xtypes.h" static Option o_ignore_exec; @@ -167,7 +168,7 @@ void diritem_restat(const guchar *path, DirItem *item, struct stat *parent) g_free(link_path); } else - item->mime_type = type_from_path(path); + item->mime_type = xtype_get(path); /* Note: for symlinks we need the mode of the target */ if (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) diff --git a/ROX-Filer/src/menu.c b/ROX-Filer/src/menu.c index 1fb1a083..4dabbee3 100644 --- a/ROX-Filer/src/menu.c +++ b/ROX-Filer/src/menu.c @@ -75,6 +75,7 @@ typedef enum { FILE_CHMOD_ITEMS, FILE_FIND, FILE_OPEN_VFS_AVFS, + FILE_SET_TYPE, } FileOp; typedef void (*ActionFn)(GList *paths, @@ -208,6 +209,7 @@ static GtkItemFactoryEntry filer_menu_def[] = { {">" N_("Set Icon..."), NULL, file_op, FILE_SET_ICON, NULL}, {">" N_("Properties"), NULL, file_op, FILE_PROPERTIES, "", GTK_STOCK_PROPERTIES}, {">" N_("Count"), NULL, file_op, FILE_USAGE, NULL}, +{">" N_("Set type..."), NULL, file_op, FILE_SET_TYPE, NULL}, {">" N_("Permissions"), NULL, file_op, FILE_CHMOD_ITEMS, NULL}, {">", NULL, NULL, 0, ""}, {">" N_("Find"), NULL, file_op, FILE_FIND, "", GTK_STOCK_FIND}, @@ -980,6 +982,14 @@ static void chmod_items(FilerWindow *filer_window) destroy_glist(&paths); } +static void set_type_items(FilerWindow *filer_window) +{ + GList *paths; + paths = filer_selected_items(filer_window); + action_settype(paths, FALSE, NULL); + destroy_glist(&paths); +} + static void find(FilerWindow *filer_window) { GList *paths; @@ -1741,6 +1751,9 @@ static void file_op(gpointer data, FileOp action, GtkWidget *unused) case FILE_PROPERTIES: prompt = _("Properties of ... ?"); break; + case FILE_SET_TYPE: + prompt = _("Set type of ... ?"); + break; case FILE_RUN_ACTION: prompt = _("Set run action for ... ?"); break; @@ -1788,6 +1801,9 @@ static void file_op(gpointer data, FileOp action, GtkWidget *unused) case FILE_CHMOD_ITEMS: chmod_items(window_with_focus); return; + case FILE_SET_TYPE: + set_type_items(window_with_focus); + return; case FILE_FIND: find(window_with_focus); return; diff --git a/ROX-Filer/src/type.c b/ROX-Filer/src/type.c index 7db90f0e..ba6136d8 100644 --- a/ROX-Filer/src/type.c +++ b/ROX-Filer/src/type.c @@ -242,6 +242,24 @@ const char *basetype_name(DirItem *item) return _("Unknown"); } +static void append_names(gpointer key, gpointer value, gpointer udata) +{ + GList **list=(GList **) udata; + + *list=g_list_prepend(*list, key); +} + +/* Return list of all mime type names */ +GList *mime_type_name_list(void) +{ + GList *list=NULL; + + g_hash_table_foreach(type_hash, append_names, &list); + list=g_list_sort(list, (GCompareFunc) strcmp); + + return list; +} + /* MIME-type guessing */ /* Get the type of this file - stats the file and uses that if @@ -349,6 +367,11 @@ static char *handler_for(MIME_type *type) return open; } +MIME_type *mime_type_lookup(const char *type) +{ + return get_mime_type(type, FALSE); +} + /* Actions for types */ gboolean type_open(const char *path, MIME_type *type) @@ -1406,7 +1429,8 @@ static void find_comment(MIME_type *type) } if (!type->comment) - type->comment = g_strdup(_("No description")); + type->comment = g_strdup_printf("%s/%s", type->media_type, + type->subtype); for (i = 0; i < n_dirs; i++) g_free(dirs[i]); diff --git a/ROX-Filer/src/type.h b/ROX-Filer/src/type.h index c4bbcc1a..f75167d6 100644 --- a/ROX-Filer/src/type.h +++ b/ROX-Filer/src/type.h @@ -65,5 +65,7 @@ gchar *describe_current_command(MIME_type *type); GdkColor *type_get_colour(DirItem *item, GdkColor *normal); void reread_mime_files(void); extern const char *mime_type_comment(MIME_type *type); +extern MIME_type *mime_type_lookup(const char *type); +extern GList *mime_type_name_list(void); #endif /* _TYPE_H */ diff --git a/ROX-Filer/src/xtypes.c b/ROX-Filer/src/xtypes.c new file mode 100644 index 00000000..622368ef --- /dev/null +++ b/ROX-Filer/src/xtypes.c @@ -0,0 +1,163 @@ +/* + * $Id$ + * + * ROX-Filer, filer for the ROX desktop project + * Copyright (C) 2003, the ROX-Filer team. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* + * xtypes.c - Extended filesystem attribute support for MIME types + */ + +#include "config.h" +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef HAVE_GETXATTR +#include +#endif + +#include "global.h" +#include "type.h" +#include "xtypes.h" + +#define XTYPE_ATTR "user.mime_type" + +#if defined(HAVE_GETXATTR) +/* Linux implementation */ + +MIME_type *xtype_get(const char *path) +{ + ssize_t size; + gchar *buf; + MIME_type *type=NULL; + + errno=0; + size=getxattr(path, XTYPE_ATTR, "", 0); + if(size>0) { + buf=g_new(gchar, size+1); + size=getxattr(path, XTYPE_ATTR, buf, size); + + if(size>0) { + buf[size]=0; + type=mime_type_lookup(buf); + } + g_free(buf); + + } + if(type) + return type; + + /* Fall back to non-extended */ + return type_from_path(path); +} + +int xtype_set(const char *path, const MIME_type *type) +{ + int res; + gchar *ttext; + + errno=0; + ttext=g_strdup_printf("%s/%s", type->media_type, type->subtype); + res=setxattr(path, XTYPE_ATTR, ttext, strlen(ttext), 0); + g_free(ttext); + + return res; +} + +#elif defined(HAVE_ATTROPEN) +/* Solaris 9 implementation */ + +MIME_type *xtype_get(const char *path) +{ + int fd; + char buf[1024]; + int nb; + MIME_type *type=NULL; + + errno=0; + fd=attropen(path, XTYPE_ATTR, O_RDONLY); + + /*printf("%s: fd=%d ", path, fd);*/ + if(fd>0) { + nb=read(fd, buf, sizeof(buf)); + /*printf("nb=%d ", nb);*/ + if(nb>0) { + buf[nb]=0; + /*printf("buf=%s ", buf);*/ + type=mime_type_lookup(buf); + } + close(fd); + } + /*printf("%s -> %s\n", path, type? mime_type_comment(type): "Unknown");*/ + if(type) + return type; + + /* Fall back to non-extended */ + return type_from_path(path); +} + +int xtype_set(const char *path, const MIME_type *type) +{ + int fd; + gchar *ttext; + int nb; + + errno=0; + fd=attropen(path, XTYPE_ATTR, O_WRONLY|O_CREAT, 0644); + if(fd>0) { + ttext=g_strdup_printf("%s/%s", type->media_type, type->subtype); + nb=write(fd, ttext, strlen(ttext)); + if(nb==strlen(ttext)) + ftruncate(fd, (off_t) nb); + g_free(ttext); + + close(fd); + + if(nb>0) + return 0; + } + + return 1; /* Set type failed */ +} + +#else +/* No extended attricutes available */ + +MIME_type *xtype_get(const char *path) +{ + /* Fall back to non-extended */ + return type_from_path(path); +} + +int xtype_set(const char *path, const MIME_type *type) +{ + errno=ENOSYS; + return 1; /* Set type failed */ +} + +#endif + diff --git a/ROX-Filer/src/xtypes.h b/ROX-Filer/src/xtypes.h new file mode 100644 index 00000000..46cc4f44 --- /dev/null +++ b/ROX-Filer/src/xtypes.h @@ -0,0 +1,23 @@ +/* + * ROX-Filer, filer for the ROX desktop project + * By Thomas Leonard, . + * + * Extended filesystem attribute support for MIME types + */ + +#ifndef _XTYPES_H +#define _XTYPES_H + +#if defined(HAVE_GETXATTR) +#define ATTR_MAN_PAGE "See the attr(5) man page for full details." +#elif defined(HAVE_ATTROPEN) +#define ATTR_MAN_PAGE "See the fsattr(5) man page for full details." +#else +#define ATTR_MAN_PAGE "You do not appear to have OS support." +#endif + +/* Prototypes */ +extern MIME_type *xtype_get(const char *path); +extern int xtype_set(const char *path, const MIME_type *type); + +#endif -- 2.11.4.GIT