Recently, GIO and nautilus have introduced the concept of content types,
which are similar to mimetypes and of the form x-content/foo. But
instead of individual files or bytestreams, content types apply to
volumes/directory hierarchies (mostly for use on removable media).
Typical examples are "Audio CD" or "Video DVD" or "Digital Photos". The
complete list of content types currently recognized by nautilus can be
found in /usr/share/mime/packages/nautilus.xml
( http://svn.gnome.org/viewvc/nautilus/trunk/data/nautilus.xml.in?view=markup )
It is useful to note that a tree/medium may match more than one
content type. This is perfectly fine; e.g. many phones acts as both
audio players, video players and digital cameras.
Also, note that applications may need further information than just the
content type (e.g. x-content/audio-player). The shared mime database
provides generic information like name, comment and icon.
Device-specific information is available e.g. in hal and is beyond the
scope of what is proposed here.
A number of GNOME applications have been patched to include support for
content types in their desktop files; this is done simply by adding the
x-content/* type to the MimeType field in the desktop entry file;
examples can be found in this bug
http://bugzilla.gnome.org/show_bug.cgi?id=510319 and it's dependents.
To associate content types with volumes, nautilus use a number of
information sources:
Out-of-band information can lead to tagging a device/medium with a
x-content/* type; for example if we know (due to vendor/product hardware
identifiers) that a given device is also an audio player then it's fine
to tag it with x-content/audio-player and x-content/video-player.
Some media don't have content per se (e.g. blank discs and audio
discs); these needs to be tagged manually using more knowledge about the
device/medium.
But if the medum has content, nautilus looks at it to determine a
suitable content type. Currently, it uses a hardcoded set of tests,
which mostly look like this:
Does it contain a nonempty directory named 'dcim' or 'DCIM' ?
--> Digital Photos
Does it have an executable file named '.autorun' or 'autorun.sh' or
'autorun.exe' ?
--> 'Software CD'
Etc
It would be very useful to replace these hardcoded tests with a generic
pattern language for matching trees. This would allow e.g.
- other file managers and programs to use the same criteria for
assigning content types.
- PackageKit to install a content type for 'servicepack cds' with the
necessary magic to make nautilus recognize such cds and do the right
thing.
- libvirt to install a content type for 'vm on a usb stick' that would
automatically start virt-manager when the usb stick is inserted.
Concretely, I propose to add a <treemagic> element that would work
pretty similar to the existing <magic> element. It contains one or more
<treematch> elements, which can be nested.
The <treemagic> element has an optional 'priority' attribute.
The <treematch> element has a mandatory 'path' attribute which specifies
what part of the tree it applies to, and several optional attributes
that specify the conditions that must be met. The 'type' attribute can
have the values "file", "directory" or "link". The boolean attributes
'executable' and 'non-empty' can be used to specify that a file must be
executable or a directory non-empty. The 'mimetype' attribute can be
used to specify the mimetype for the file specified by the path. The
boolean attribute 'ignore-case' can be used to specify that case should
be ignored when matching the path.
Examples:
<treemagic priority="75">
<treematch path="dcim" type="directory" ignore-case="true"
nonempty="true"/>
</treemagic>
<treemagic>
<treematch path=".autorun" type="file" executable="true"/>
<treematch path="autorun" type="file" executable="true"/>
<treematch path="autorun.sh" type="file" executable="true"/>
</treemagic>
<treemagic>
<treematch path="BDAV" type="directory" non-empty="true">
<treematch path="BDMV" type="directory" non-empty="true"/>
</treematch>
</treemagic>
For caching, the proposal is to write treemagic files with a similar
structure to the magic file:
[50:x-content/image-dcf]
>"dcim"=directory,ignore-case,nonempty
1>"autorun.sh"=file,executable,application/x-shellscript
I'm attaching patches for the shared-mime-info spec, for
update-mime-database (to create treemagic files) and a standalone
implementation of tree matching using the treemagic files (this will
become part of a future g_mount_guess_content_type() function in GIO).
If these additions are accepted, I will also prepare a patch to
add the content types that are currently defined in nautilus.xml
to freedesktop.xml.
Comments ?
Matthias
Index: shared-mime-info-spec.xml
===================================================================
RCS file: /cvs/mime/shared-mime-info/shared-mime-info-spec.xml,v
retrieving revision 1.60
diff -u -r1.60 shared-mime-info-spec.xml
--- shared-mime-info-spec.xml 9 Jun 2008 09:27:30 -0000 1.60
+++ shared-mime-info-spec.xml 10 Jun 2008 04:04:08 -0000
@@ -1,8 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
- <!ENTITY updated "25 January 2008">
- <!ENTITY version "0.17">
+ <!ENTITY updated "10 June 2008">
+ <!ENTITY version "0.18">
]>
<article id="index">
@@ -343,6 +343,34 @@
If <userinput>localName</userinput> is present but empty then the document element may have
any name, but the namespace must still match.
</para></listitem>
+ <listitem><para>
+<userinput>treemagic</userinput> elements contain a list of <userinput>treematch</userinput> elements,
+any of which may match, and an optional <userinput>priority</userinput> attribute for all of the
+contained rules. The default priority value is 50, and the maximum is 100.
+ </para><para>
+Each <userinput>treematch</userinput> element has a number of attributes:
+
+<informaltable>
+ <tgroup cols="3">
+ <thead><row><entry>Attribute</entry><entry>Required?</entry><entry>Value</entry></row></thead>
+ <tbody>
+
+ <row><entry>path</entry><entry>Yes</entry><entry>A path that must be present on the mounted volume/filesystem.</entry></row>
+
+ <row><entry>type</entry><entry>No</entry><entry>The type of path. Possible values: <userinput>file</userinput>, <userinput>directory</userinput>, <userinput>link</userinput></entry></row>
+
+ <row><entry>ignore-case</entry><entry>No</entry><entry>Whether path should be matched case-insensitively. Possible values: <userinput>true</userinput>, <userinput>false</userinput></entry></row>
+
+ <row><entry>executable</entry><entry>No</entry><entry>Whether the file must be executable. Possible values: <userinput>true</userinput>, <userinput>false</userinput></entry></row>
+
+ <row><entry>non-empty</entry><entry>No</entry><entry>Whether the directory must be non-empty. Possible values: <userinput>true</userinput>, <userinput>false</userinput></entry></row>
+
+ </tbody></tgroup>
+</informaltable>
+
+<userinput>treematch</userinput> elements can be nested, meaning that both the outer and the inner <userinput>treematch</userinput>
+must be satisfied for a "match".
+ </para></listitem>
</itemizedlist>
Applications may also define their own elements, provided they are namespaced to prevent collisions.
Unknown elements are copied directly to the output XML files like <userinput>comment</userinput>
@@ -574,6 +602,39 @@
</para>
</sect2>
<sect2>
+ <title>The treemagic files</title>
+ <para>
+The tree magic data is stored in a file with a format that is very similar to the magic file format.
+ </para>
+ <para>
+The file starts with the magic string "MIME-TreeMagic\0\n". There is no version number in the file.
+Incompatible changes will be handled by creating both the current `treemagic' and a newer `treemagic2'
+in the new format. Where possible, changes will be made in a compatible fashion.
+ </para>
+ <para>
+The rest of the file is made up of a sequence of small sections. Each section is introduced by giving
+the priority and type in brackeds, followed by a newline character. Higher priority entries come
+first. Example:
+<screen>[50:x-content/image-dcf]\n</screen>
+Each line in the section takes the form:
+<screen>[ indent ] ">" "\"" path "\"" "=" type [ "," option ]* "\n"</screen>
+<informaltable>
+ <tgroup cols="2">
+ <thead><row><entry>Part</entry><entry>Meaning</entry></row></thead>
+ <tbody>
+
+ <row><entry>indent</entry><entry>The nesting depth of the rule.</entry></row>
+ <row><entry>path</entry><entry>The path to match.</entry></row>
+ <row><entry>type</entry><entry>The required file type, one of "file", "directory", "link" or "any"</entry></row>
+ <row><entry>option</entry><entry>Optional for the optional attributes of <userinput>treematch</userinput> elements.
+Possible values are "executable", "ignore-case", "non-empty", or a MIME type</entry></row>
+ </tbody>
+ </tgroup>
+</informaltable>
+ </para><para>
+ </para>
+ </sect2>
+ <sect2>
<title>The mime.cache files</title>
<para>
The <filename>mime.cache</filename> files contain the same information as the
@@ -847,6 +908,19 @@
</para>
</sect2>
<sect2>
+ <title>Content types</title>
+ <para>
+Traditional MIME types apply to individual files or bytestreams. It is often useful to
+apply the same methodologies when classifying mountable volumes or filesystems. The x-content type
+has been introduced for this purpose. Typical examples are x-content/audio-dvd, x-content/blank-cd
+or x-content/image-dcf.
+ </para>
+ <para>
+Matching of content types works with <userinput>treemagic</userinput> elements, which are
+analogous to the <userinput>magic</userinput> elements used for MIME type matching.
+ </para>
+ </sect2>
+ <sect2>
<title>Security implications</title>
<para>
The system described in this document is intended to allow different programs
--- update-mime-database.c.beforetree 2008-06-09 00:08:13.000000000 -0400
+++ update-mime-database.c 2008-06-09 23:02:22.000000000 -0400
@@ -66,6 +66,12 @@
/* A parsed <match> element */
typedef struct _Match Match;
+/* A parsed <treemagic> element */
+typedef struct _TreeMagic TreeMagic;
+
+/* A parsed <treematch> element */
+typedef struct _TreeMatch TreeMatch;
+
/* A parsed <glob> element */
typedef struct _Glob Glob;
@@ -102,6 +108,23 @@
GList *matches;
};
+struct _TreeMagic {
+ int priority;
+ Type *type;
+ GList *matches;
+};
+
+struct _TreeMatch {
+ char *path;
+ gboolean ignore_case;
+ gboolean executable;
+ gboolean non_empty;
+ gint type;
+ char *mimetype;
+
+ GList *matches;
+};
+
/* Maps MIME type names to Types */
static GHashTable *types = NULL;
@@ -114,6 +137,9 @@
/* 'magic' nodes */
static GPtrArray *magic_array = NULL;
+/* 'treemagic' nodes */
+static GPtrArray *tree_magic_array = NULL;
+
/* Maps MIME type names to superclass names */
static GHashTable *subclass_hash = NULL;
@@ -132,6 +158,8 @@
/* Static prototypes */
static Magic *magic_new(xmlNode *node, Type *type, GError **error);
+static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error);
+
static void g_log_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
@@ -372,6 +400,20 @@
else
g_return_val_if_fail(magic == NULL, FALSE);
}
+ else if (strcmp((char *)field->name, "treemagic") == 0)
+ {
+ TreeMagic *magic;
+
+ magic = tree_magic_new(field, type, error);
+
+ if (!*error)
+ {
+ g_return_val_if_fail(magic != NULL, FALSE);
+ g_ptr_array_add(tree_magic_array, magic);
+ }
+ else
+ g_return_val_if_fail(magic == NULL, FALSE);
+ }
else if (strcmp((char *)field->name, "comment") == 0 ||
strcmp((char *)field->name, "acronym") == 0 ||
strcmp((char *)field->name, "expanded-acronym") == 0)
@@ -857,6 +899,25 @@
return retval;
}
+/* Comparison function to get the tree magic rules in priority order */
+static gint cmp_tree_magic(gconstpointer a, gconstpointer b)
+{
+ TreeMagic *aa = *(TreeMagic **) a;
+ TreeMagic *bb = *(TreeMagic **) b;
+ int retval;
+
+ if (aa->priority > bb->priority)
+ return -1;
+ else if (aa->priority < bb->priority)
+ return 1;
+
+ retval = strcmp(aa->type->media, bb->type->media);
+ if (!retval)
+ retval = strcmp(aa->type->subtype, bb->type->subtype);
+
+ return retval;
+}
+
/* Write out 'n' as a two-byte big-endian number to 'stream' */
static void write16(FILE *stream, guint32 n)
{
@@ -1398,6 +1459,222 @@
return magic;
}
+static TreeMatch *tree_match_new(void)
+{
+ TreeMatch *match;
+
+ match = g_new(TreeMatch, 1);
+ match->path = NULL;
+ match->ignore_case = 0;
+ match->executable = 0;
+ match->non_empty = 0;
+ match->type = 0;
+ match->mimetype = NULL;
+ match->matches = NULL;
+
+ return match;
+}
+
+static void tree_match_free(TreeMatch *match)
+{
+ GList *next;
+
+ g_return_if_fail(match != NULL);
+
+ for (next = match->matches; next; next = next->next)
+ tree_match_free((TreeMatch *) next->data);
+
+ g_list_free(match->matches);
+
+ g_free(match->path);
+ g_free(match->mimetype);
+
+ g_free(match);
+}
+
+/* Turn the list of child nodes of 'parent' into a list of TreeMatches */
+static GList *build_tree_matches(xmlNode *parent, GError **error)
+{
+ xmlNode *node;
+ GList *out = NULL;
+ char *attr;
+
+ g_return_val_if_fail(error != NULL, NULL);
+
+ for (node = parent->xmlChildrenNode; node; node = node->next)
+ {
+ TreeMatch *match;
+
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (node->ns == NULL || xmlStrcmp(node->ns->href, FREE_NS) != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Element found with non-freedesktop.org "
+ "namespace"));
+ break;
+ }
+
+ if (strcmp((char *)node->name, "treematch") != 0)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Expected <treematch> element, but found "
+ "<%s> instead"), node->name);
+ break;
+ }
+
+ match = tree_match_new();
+
+ attr = my_xmlGetNsProp(node, "path", NULL);
+ if (attr)
+ {
+ match->path = g_strdup (attr);
+ xmlFree (attr);
+ }
+ else
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Missing 'path' attribute in <treematch>"));
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "type", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "file") == 0)
+ {
+ match->type = 1;
+ }
+ else if (strcmp (attr, "directory") == 0)
+ {
+ match->type = 2;
+ }
+ else if (strcmp (attr, "link") == 0)
+ {
+ match->type = 3;
+ }
+ else
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Invalid 'type' attribute in <treematch>"));
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "executable", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->executable = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "ignore-case", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->ignore_case = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "non-empty", NULL);
+ if (attr)
+ {
+ if (strcmp (attr, "true") == 0)
+ {
+ match->non_empty = 1;
+ }
+ xmlFree(attr);
+ }
+ }
+ if (!*error)
+ {
+ attr = my_xmlGetNsProp(node, "mimetype", NULL);
+ if (attr)
+ {
+ match->mimetype = g_strdup (attr);
+ xmlFree(attr);
+ }
+ }
+
+ if (*error)
+ {
+ tree_match_free(match);
+ break;
+ }
+
+ out = g_list_append(out, match);
+
+ match->matches = build_tree_matches(node, error);
+ if (*error)
+ break;
+ }
+
+ return out;
+}
+
+static void tree_magic_free(TreeMagic *magic)
+{
+ GList *next;
+
+ g_return_if_fail(magic != NULL);
+
+ for (next = magic->matches; next; next = next->next)
+ tree_match_free((TreeMatch *) next->data);
+ g_list_free(magic->matches);
+
+ g_free(magic);
+}
+
+/* Create a new TreeMagic object by parsing 'node' (a <treemagic> element) */
+static TreeMagic *tree_magic_new(xmlNode *node, Type *type, GError **error)
+{
+ TreeMagic *magic = NULL;
+ int prio;
+
+ g_return_val_if_fail(node != NULL, NULL);
+ g_return_val_if_fail(type != NULL, NULL);
+ g_return_val_if_fail(error != NULL, NULL);
+
+ prio = get_priority(node);
+
+ if (prio == -1)
+ {
+ g_set_error(error, MIME_ERROR, 0,
+ _("Bad priority (%d) in <treemagic> element"), prio);
+ }
+ else
+ {
+ magic = g_new(TreeMagic, 1);
+ magic->priority = prio;
+ magic->type = type;
+ magic->matches = build_tree_matches(node, error);
+
+ if (*error)
+ {
+ gchar *old = (*error)->message;
+ tree_magic_free(magic);
+ magic = NULL;
+ (*error)->message = g_strconcat(
+ _("Error in <treematch> element: "), old, NULL);
+ g_free(old);
+ }
+ }
+
+ return magic;
+}
+
/* Write a list of Match elements (and their children) to the 'magic' file */
static void write_magic_children(FILE *stream, GList *matches, int indent)
{
@@ -1442,6 +1719,62 @@
write_magic_children(stream, magic->matches, 0);
}
+/* Write a list of TreeMatch elements (and their children) to the 'treemagic' file */
+static void write_tree_magic_children(FILE *stream, GList *matches, int indent)
+{
+ GList *next;
+
+ for (next = matches; next; next = next->next)
+ {
+ TreeMatch *match = (TreeMatch *) next->data;
+
+ if (indent)
+ g_fprintf(stream,
+ "%d>\"%s\"=",
+ indent,
+ match->path);
+ else
+ g_fprintf(stream, ">\"%s\"=", match->path);
+
+ switch (match->type)
+ {
+ default:
+ case 0:
+ fputs("any", stream);
+ break;
+ case 1:
+ fputs("file", stream);
+ break;
+ case 2:
+ fputs("directory", stream);
+ break;
+ case 3:
+ fputs("link", stream);
+ break;
+ }
+ if (match->ignore_case)
+ fputs (",ignore-case", stream);
+ if (match->executable)
+ fputs (",executable", stream);
+ if (match->non_empty)
+ fputs (",non-empty", stream);
+ if (match->mimetype)
+ g_fprintf (stream, ",%s", match->mimetype);
+
+ fputc('\n', stream);
+
+ write_tree_magic_children(stream, match->matches, indent + 1);
+ }
+}
+/* Write a whole TreeMagic element to the 'treemagic' file */
+static void write_tree_magic(FILE *stream, TreeMagic *magic)
+{
+ g_fprintf(stream, "[%d:%s/%s]\n", magic->priority,
+ magic->type->media, magic->type->subtype);
+
+ write_tree_magic_children(stream, magic->matches, 0);
+}
+
/* Check each of the directories with generated XML files, looking for types
* which we didn't get on this scan, and delete them.
*/
@@ -3076,6 +3409,7 @@
namespace_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
magic_array = g_ptr_array_new();
+ tree_magic_array = g_ptr_array_new();
subclass_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, free_string_list);
alias_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
@@ -3225,6 +3559,28 @@
{
FILE *stream;
char *path;
+ int i;
+ path = g_strconcat(mime_dir, "/treemagic.new", NULL);
+ stream = open_or_die(path);
+ fwrite("MIME-TreeMagic\0\n", 1, 16, stream);
+
+ if (tree_magic_array->len)
+ g_ptr_array_sort(tree_magic_array, cmp_tree_magic);
+ for (i = 0; i < tree_magic_array->len; i++)
+ {
+ TreeMagic *magic = (TreeMagic *) tree_magic_array->pdata[i];
+
+ write_tree_magic(stream, magic);
+ }
+ fclose(stream);
+
+ atomic_update(path);
+ g_free(path);
+ }
+
+ {
+ FILE *stream;
+ char *path;
path = g_strconcat(mime_dir, "/mime.cache.new", NULL);
stream = open_or_die(path);
@@ -3237,6 +3593,8 @@
g_ptr_array_foreach(magic_array, (GFunc)magic_free, NULL);
g_ptr_array_free(magic_array, TRUE);
+ g_ptr_array_foreach(tree_magic_array, (GFunc)tree_magic_free, NULL);
+ g_ptr_array_free(tree_magic_array, TRUE);
g_hash_table_destroy(types);
g_hash_table_destroy(globs_hash);
/*
* Copyright (C) 2008 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Matthias Clasen <[EMAIL PROTECTED]>
*/
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <gio/gio.h>
typedef struct
{
gchar *path;
GFileType type;
guint ignore_case : 1;
guint executable : 1;
guint non_empty : 1;
guint on_disc : 1;
gchar *mimetype;
GList *matches;
} TreeMatchlet;
typedef struct
{
gchar *contenttype;
gint priority;
GList *matches;
} TreeMatch;
static GList *tree_matches = NULL;
static void
tree_matchlet_free (TreeMatchlet *matchlet)
{
g_list_foreach (matchlet->matches, (GFunc)tree_matchlet_free, NULL);
g_list_free (matchlet->matches);
g_free (matchlet->path);
g_free (matchlet->mimetype);
g_slice_free (TreeMatchlet, matchlet);
}
static void
tree_match_free (TreeMatch *match)
{
g_list_foreach (match->matches, (GFunc)tree_matchlet_free, NULL);
g_list_free (match->matches);
g_free (match->contenttype);
g_slice_free (TreeMatch, match);
}
static void
tree_magic_shutdown (void)
{
g_list_foreach (tree_matches, (GFunc)tree_match_free, NULL);
g_list_free (tree_matches);
tree_matches = NULL;
}
static TreeMatch *
parse_header (gchar *line)
{
gint len;
gchar *s;
TreeMatch *match;
len = strlen (line);
if (line[0] != '[' || line[len - 1] != ']')
return NULL;
line[len - 1] = 0;
s = strchr (line, ':');
match = g_slice_new0 (TreeMatch);
match->priority = atoi (line + 1);
match->contenttype = g_strdup (s + 1);
return match;
}
static TreeMatchlet *
parse_match_line (gchar *line,
gint *depth)
{
gchar *s, *p;
TreeMatchlet *matchlet;
gchar **parts;
gint i;
matchlet = g_slice_new0 (TreeMatchlet);
if (line[0] == '>') {
*depth = 0;
s = line;
}
else {
*depth = atoi (line);
s = strchr (line, '>');
}
s += 2;
p = strchr (s, '"');
*p = 0;
matchlet->path = g_strdup (s);
s = p + 1;
parts = g_strsplit (s, ",", 0);
if (strcmp (parts[0], "=file") == 0)
matchlet->type = G_FILE_TYPE_REGULAR;
else if (strcmp (parts[0], "=directory") == 0)
matchlet->type = G_FILE_TYPE_DIRECTORY;
else if (strcmp (parts[0], "=link") == 0)
matchlet->type = G_FILE_TYPE_SYMBOLIC_LINK;
else
matchlet->type = G_FILE_TYPE_UNKNOWN;
for (i = 1; parts[i]; i++) {
if (strcmp (parts[i], "executable") == 0)
matchlet->executable = 1;
else if (strcmp (parts[i], "ignore-case") == 0)
matchlet->ignore_case = 1;
else if (strcmp (parts[i], "non-empty") == 0)
matchlet->non_empty = 1;
else if (strcmp (parts[i], "on-disc") == 0)
matchlet->on_disc = 1;
else
matchlet->mimetype = g_strdup (parts[i]);
}
g_strfreev (parts);
return matchlet;
}
static gint
cmp_match (gconstpointer a, gconstpointer b)
{
const TreeMatch *aa = (const TreeMatch *)a;
const TreeMatch *bb = (const TreeMatch *)b;
return bb->priority - aa->priority;
}
static void
insert_match (TreeMatch *match)
{
tree_matches = g_list_insert_sorted (tree_matches, match, cmp_match);
}
static void
insert_matchlet (TreeMatch *match,
TreeMatchlet *matchlet,
gint depth)
{
if (depth == 0)
match->matches = g_list_append (match->matches, matchlet);
else {
GList *last;
TreeMatchlet *m;
last = g_list_last (match->matches);
if (!last) {
tree_matchlet_free (matchlet);
g_warning ("can't insert matchlet at depth %d", depth);
return;
}
m = (TreeMatchlet *) last->data;
depth--;
while (depth > 0) {
last = g_list_last (m->matches);
if (!last) {
tree_matchlet_free (matchlet);
g_warning ("can't insert matchlet at depth %d", depth);
return;
}
m = (TreeMatchlet *) last->data;
depth--;
}
m->matches = g_list_append (m->matches, matchlet);
}
}
static void
read_tree_magic_from_directory (const gchar *prefix)
{
gchar *filename;
gchar *text;
gsize len;
gchar **lines;
gint i;
TreeMatch *match;
TreeMatchlet *matchlet;
gint depth;
filename = g_build_filename (prefix, "mime", "treemagic", NULL);
if (g_file_get_contents (filename, &text, &len, NULL)) {
if (strcmp (text, "MIME-TreeMagic") == 0) {
lines = g_strsplit (text + strlen ("MIME-TreeMagic") + 2, "\n", 0);
for (i = 0; lines[i] && lines[i][0]; i++) {
if (lines[i][0] == '[') {
match = parse_header (lines[i]);
insert_match (match);
}
else {
matchlet = parse_match_line (lines[i], &depth);
insert_matchlet (match, matchlet, depth);
}
}
}
else
g_warning ("%s: header not found, skipping\n", filename);
g_free (text);
g_strfreev (lines);
}
g_free (filename);
}
typedef struct
{
gchar *path;
gint depth;
gboolean ignore_case;
gchar **components;
gchar **case_components;
GFileEnumerator **enumerators;
GFile **children;
} Enumerator;
static gboolean
component_match (Enumerator *e,
gint depth,
const gchar *name)
{
gchar *case_folded, *key;
gboolean found;
if (strcmp (name, e->components[depth]) == 0)
return TRUE;
if (!e->ignore_case)
return FALSE;
case_folded = g_utf8_casefold (name, -1);
key = g_utf8_collate_key (case_folded, -1);
found = strcmp (key, e->case_components[depth]) == 0;
g_free (case_folded);
g_free (key);
return found;
}
static GFile *
next_match_recurse (Enumerator *e,
gint depth)
{
GFile *file;
GFileInfo *info;
const gchar *name;
while (TRUE) {
if (e->enumerators[depth] == NULL) {
if (depth > 0) {
file = next_match_recurse (e, depth - 1);
if (file) {
e->children[depth] = file;
e->enumerators[depth] = g_file_enumerate_children (file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
}
}
if (e->enumerators[depth] == NULL)
return NULL;
}
while ((info = g_file_enumerator_next_file (e->enumerators[depth], NULL, NULL))) {
name = g_file_info_get_name (info);
if (component_match (e, depth, name)) {
file = g_file_get_child (e->children[depth], name);
g_object_unref (info);
return file;
}
g_object_unref (info);
}
g_object_unref (e->enumerators[depth]);
e->enumerators[depth] = NULL;
g_object_unref (e->children[depth]);
e->children[depth] = NULL;
}
}
static GFile *
enumerator_next (Enumerator *e)
{
return next_match_recurse (e, e->depth - 1);
}
static Enumerator *
enumerator_new (GFile *root,
const char *path,
gboolean ignore_case)
{
Enumerator *e;
gint i;
gchar *case_folded;
e = g_new0 (Enumerator, 1);
e->path = g_strdup (path);
e->ignore_case = ignore_case;
e->components = g_strsplit (e->path, G_DIR_SEPARATOR_S, -1);
e->depth = g_strv_length (e->components);
if (e->ignore_case) {
e->case_components = g_new0 (char *, e->depth + 1);
for (i = 0; e->components[i]; i++) {
case_folded = g_utf8_casefold (e->components[i], -1);
e->case_components[i] = g_utf8_collate_key (case_folded, -1);
g_free (case_folded);
}
}
e->children = g_new0 (GFile *, e->depth);
e->children[0] = g_object_ref (root);
e->enumerators = g_new0 (GFileEnumerator *, e->depth);
e->enumerators[0] = g_file_enumerate_children (root,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
return e;
}
static void
enumerator_free (Enumerator *e)
{
gint i;
for (i = 0; i < e->depth; i++) {
if (e->enumerators[i])
g_object_unref (e->enumerators[i]);
if (e->children[i])
g_object_unref (e->children[i]);
}
g_free (e->enumerators);
g_free (e->children);
g_strfreev (e->components);
if (e->case_components)
g_strfreev (e->case_components);
g_free (e->path);
g_free (e);
}
static gboolean
matchlet_match (TreeMatchlet *matchlet,
GFile *root)
{
GFile *file;
GFileInfo *info;
GFileType type;
gboolean result;
const gchar *attrs;
Enumerator *e;
GList *l;
e = enumerator_new (root, matchlet->path, matchlet->ignore_case);
do {
file = enumerator_next (e);
if (!file) {
enumerator_free (e);
return FALSE;
}
if (matchlet->mimetype)
attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
else
attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
info = g_file_query_info (file,
attrs,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (info) {
result = TRUE;
if (matchlet->type != G_FILE_TYPE_UNKNOWN &&
g_file_info_get_file_type (info) != matchlet->type)
result = FALSE;
if (matchlet->executable &&
!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
result = FALSE;
}
else
result = FALSE;
if (result && matchlet->non_empty) {
GFileEnumerator *child_enum;
GFileInfo *child_info;
child_enum = g_file_enumerate_children (file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (child_enum) {
child_info = g_file_enumerator_next_file (child_enum, NULL, NULL);
if (child_info)
g_object_unref (child_info);
else
result = FALSE;
g_object_unref (child_enum);
}
else
result = FALSE;
}
if (result && matchlet->mimetype) {
if (strcmp (matchlet->mimetype, g_file_info_get_content_type (info)) != 0)
result = FALSE;
}
g_object_unref (info);
g_object_unref (file);
}
while (!result);
enumerator_free (e);
if (!matchlet->matches)
return TRUE;
for (l = matchlet->matches; l; l = l->next) {
TreeMatchlet *submatchlet;
submatchlet = l->data;
if (matchlet_match (submatchlet, root))
return TRUE;
}
return FALSE;
}
static void
match_match (TreeMatch *match,
GFile *root,
GPtrArray *types)
{
GList *l;
for (l = match->matches; l; l = l->next) {
TreeMatchlet *matchlet = l->data;
if (matchlet_match (matchlet, root)) {
g_ptr_array_add (types, g_strdup (match->contenttype));
break;
}
}
}
static void
tree_magic_init (void)
{
static gboolean initialized = FALSE;
const gchar *dir;
const gchar * const * dirs;
int i;
if (!initialized) {
initialized = TRUE;
dir = g_get_user_data_dir ();
read_tree_magic_from_directory (dir);
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i]; i++)
read_tree_magic_from_directory (dirs[i]);
}
}
void
sniff_content_type (GFile *root,
GPtrArray *types)
{
GList *l;
gchar **result;
gint i;
/* TODO: monitor and reload */
tree_magic_init ();
types = g_ptr_array_new ();
for (l = tree_matches; l; l = l->next) {
TreeMatch *match = l->data;
match_match (match, root, types);
}
}
_______________________________________________
xdg mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/xdg