This isn't quite worked out, but I want to throw this out to the group
and get some preliminary feedback. Attached is a patch that allows us
to index system-wide and user installed man pages, Tomboy notes, and
some basic Liferea support. The external services all use the
out-of-process mechanism used by the text filter and embded metadata
extractor. However, there are more operations, and therefore, more
applications for each service.
First, the directory structure:
in tracker/src there now resides an "external-services" directory.
In this directory you will find one directory for each service. The
service directories are named after their configuration key in
~/.Tracker/tracker.cfg. This makes it easy to add new services with
out recompiling trackerd (and hopefully encourage other developers to
provide tracker support with their apps!). For example, you'll find
the directory tracker/src/external-services/IndexManPages and a
IndexManPages key under the Services group in tracker.cfg. Each
service has five programs:
1) check-deps
This program is called in the very begining, if the user actives the
service's key. This program may check for any other required programs
that is needed for this service to work. For example, I check for
xsltproc and w3m for the Liferea indexer. If non-zero is returned,
the indexer is disabled.
2) watch-list
This program returns a list of directories to be added to trackerd's
watch list. You must list each directory, it will not automatically
recurse all subdirectorys. If you need all subdirs, I recommend find:
# find $basedir -type d
See IndexManPage/watch-list for an example.
3) service-type
This progam returns the service type of a file being watched by this service.
argv[1] == the full path to the file being watched
argv[2] == the mime type of the file
I provides the file path and mime, if you need it, but I imagine this
should be constant
4) filter-text
This works very similar to the text filters you find in the
tracker/filters directory, except
argv[1] == the full path
argv[2] == the mime type of the file !!
argv[3] == the path to the filtered text !!
5) extract-metadata
Again, behaves like tracker-extract. It takes a file and splits out
Key=Value;\n pairs for each piece of metadata
argv[1] == the full path
argv[2] == the mime type of the file
So, like I said before, I'm including 3 implementations of this:
1) IndexManPages
The new service type is "Man Pages" and it adds a new "Man" metadata
class. The class can tag a man page's title, section, date it was
written, source (app + version), and manual name (eg, Debian Project
for debian specific man pages). It also provides a full text indexer.
Only thing lacking here is the language the man page was written in.
Currently, I reject any non-english directory. It's easy to index
them all, but it's just faster for me if trackerd just ignores those.
2) IndexTomboy
This uses the Notes service type, and adds a Title field to the Note
metadata class. There's obviously more I could grab from the tomboy
files, I just haven't gotten around to it yet. Full text is
supported.
3) IndexLiferea
This adds a service type called "Web Channels" and a metadata class
"RSS". This indexer sucks and I need some help on it. :(
Currently, you only get one entry in the database for each feed. So
all the text in the feed is associated with the entire feed, instead
of an individual item. For example, if I was to search for "tracker"
I'd expect a link to a specific post by Jamie, instead I get a link
planet gnome. I'm not even sure what I need here, I'd like some way
to associate a file with multiple database items. Is this possible?
I'm pretty happy with the man pages indexer, I may look into having
Yelp use some time in the future. But I'm not calling dibs, so anyone
else looking for an project to work on is more than welcome.
The tomboy indexer works as expected also. I belive Tomboy is
dbus-ified, so if any one wants to update tracker-search-tool to
search Notes also and fire up with Tomboy when you click on a note,
that'd be awsome.
The included patch also updates tracker-search and libtracker, so you
can search for the "Man Pages" and "Notes" service types.
Index: configure.in
===================================================================
RCS file: /cvs/gnome/tracker/configure.in,v
retrieving revision 1.44
diff -u -p -r1.44 configure.in
--- configure.in 30 Nov 2006 01:06:56 -0000 1.44
+++ configure.in 7 Dec 2006 14:33:50 -0000
@@ -530,6 +530,10 @@ AC_CONFIG_FILES([
src/trackerd/Makefile
src/libtracker/Makefile
src/tracker-search-tool/Makefile
+ src/external-services/Makefile
+ src/external-services/IndexManPages/Makefile
+ src/external-services/IndexLiferea/Makefile
+ src/external-services/IndexTomboy/Makefile
po/Makefile.in
])
Index: src/Makefile.am
===================================================================
RCS file: /cvs/gnome/tracker/src/Makefile.am,v
retrieving revision 1.13
diff -u -p -r1.13 Makefile.am
--- src/Makefile.am 30 Nov 2006 01:06:58 -0000 1.13
+++ src/Makefile.am 7 Dec 2006 14:37:53 -0000
@@ -9,5 +9,5 @@ sqlite_dir = sqlite3
endif
-SUBDIRS = libstemmer $(sqlite_dir) text-filters trackerd libtracker tracker-extract tracker-thumbnailer $(tracker_gui_dir)
+SUBDIRS = libstemmer $(sqlite_dir) text-filters trackerd libtracker tracker-extract tracker-thumbnailer $(tracker_gui_dir) external-services
Index: src/trackerd/Makefile.am
===================================================================
RCS file: /cvs/gnome/tracker/src/trackerd/Makefile.am,v
retrieving revision 1.24
diff -u -p -r1.24 Makefile.am
--- src/trackerd/Makefile.am 30 Nov 2006 01:47:44 -0000 1.24
+++ src/trackerd/Makefile.am 7 Dec 2006 14:38:09 -0000
@@ -110,6 +110,8 @@ trackerd_SOURCES = \
tracker-mbox-kmail.h \
tracker-mbox.c \
tracker-mbox.h \
+ tracker-external-services.c \
+ tracker-external-services.h \
tracker-metadata.c \
tracker-metadata.h \
tracker-rdf-query.c \
Index: src/trackerd/tracker-utils.h
===================================================================
RCS file: /cvs/gnome/tracker/src/trackerd/tracker-utils.h,v
retrieving revision 1.31
diff -u -p -r1.31 tracker-utils.h
--- src/trackerd/tracker-utils.h 20 Nov 2006 00:53:55 -0000 1.31
+++ src/trackerd/tracker-utils.h 7 Dec 2006 14:38:28 -0000
@@ -130,6 +130,12 @@ typedef struct {
gboolean index_kmail_emails;
GSList *additional_mboxes_to_index;
+ /* external application support */
+ GSList *active_services;
+ GHashTable *app_explicit_dir;
+ GHashTable *app_explicit_file;
+ GSList *app_implicit_dir;
+
/* nfs options */
gboolean use_nfs_safe_locking; /* use safer but much slower external lock file when users home dir is on an nfs systems */
@@ -277,6 +283,9 @@ typedef struct {
+typedef struct {
+ gchar *service_key;
+} ExternalService;
typedef struct {
@@ -293,19 +302,21 @@ typedef struct {
WatchTypes watch_type;
gboolean is_directory;
+ ExternalService *app;
+
/* symlink info - File ID of link might not be in DB so need to store path/filename too */
gboolean is_link;
- gint32 link_id;
+ gint32 link_id;
char *link_path;
char *link_name;
char *mime;
int service_type_id;
- guint32 file_size;
+ guint32 file_size;
char *permissions;
- gint32 mtime;
- gint32 atime;
- gint32 indextime;
+ gint32 mtime;
+ gint32 atime;
+ gint32 indextime;
/* options */
char *move_from_uri;
Index: src/trackerd/tracker-utils.c
===================================================================
RCS file: /cvs/gnome/tracker/src/trackerd/tracker-utils.c,v
retrieving revision 1.45
diff -u -p -r1.45 tracker-utils.c
--- src/trackerd/tracker-utils.c 28 Nov 2006 18:37:07 -0000 1.45
+++ src/trackerd/tracker-utils.c 7 Dec 2006 14:38:54 -0000
@@ -32,6 +32,7 @@
#include "tracker-dbus.h"
#include "tracker-utils.h"
+#include "tracker-external-services.h"
#include "xdgmime.h"
@@ -48,7 +49,8 @@ char *file_service_array[] = {"Files",
char *service_index_array[] = { "Files", "Folders", "Documents", "Images", "Music", "Videos", "Text Files", "Development Files", "Other Files",
"VFS Files", "VFS Folders", "VFS Documents", "VFS Images", "VFS Music", "VFS Videos", "VFS Text Files", "VFS Development Files", "VFS Other Files",
"Conversations", "Playlists", "Applications", "Contacts", "Emails", "EmailAttachments", "Notes", "Appointments",
- "Tasks", "Bookmarks", "History", "Projects", NULL};
+ "Tasks", "Bookmarks", "History", "Projects", "Web Pages", "Web Channels",
+ "Man Pages", NULL};
char *tracker_actions[] = {
@@ -838,6 +840,8 @@ tracker_create_file_info (const char *ur
info->watch_type = watch;
info->is_directory = FALSE;
+ info->app = NULL;
+
info->is_link = FALSE;
info->link_id = 0;
info->link_path = NULL;
@@ -859,7 +863,6 @@ tracker_create_file_info (const char *ur
return info;
}
-
FileInfo *
tracker_free_file_info (FileInfo *info)
{
@@ -1277,7 +1280,7 @@ tracker_get_mime_type (const char* uri)
result = xdg_mime_get_mime_type_for_file (uri, NULL);
- if (result != NULL && result != XDG_MIME_TYPE_UNKNOWN) {
+ if (result != NULL && strcmp (result, XDG_MIME_TYPE_UNKNOWN) != 0 && strlen (result) > 0) {
return g_strdup (result);
} else {
if (is_text_file (uri)) {
@@ -1793,6 +1796,9 @@ tracker_load_config_file ()
"IndexEvolutionEmails=false\n",
"IndexThunderbirdEmails=false\n",
"IndexKmailEmails=false\n\n",
+ "IndexManPages=false\n",
+ "IndexLiferea=false\n",
+ "IndexTomboy=false\n",
"[Emails]\n",
"AdditionalMBoxesToIndex=;\n\n",
"[Performance]\n",
@@ -1978,6 +1984,7 @@ tracker_load_config_file ()
}
+ tracker->active_services = tracker_external_services_get_active (key_file);
g_free (filename);
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/trackerd/tracker-external-services.h 2006-12-07 07:42:54.000000000 -0500
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) 2006, Edward Duffy
+ *
+ * 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,
+ *
+ */
+
+#ifndef TRACKER_EXTERNAL_SERVICES_H
+#define TRACKER_EXTERNAL_SERVICES_H
+
+#include <glib.h>
+#include "tracker-utils.h"
+
+GSList *tracker_external_services_get_active (GKeyFile *key_file);
+
+gchar **tracker_external_services_get_watch_list (gchar *service_key);
+
+gint tracker_external_services_get_part_count (gchar *service_key, gchar *path);
+
+gchar *tracker_external_services_get_service_type (FileInfo *info);
+
+gchar *tracker_external_services_get_text_file (FileInfo *info);
+
+void tracker_external_services_get_metadata (FileInfo *info, GHashTable *table);
+
+#endif /* TRACKER_EXTERNAL_SERVICES_H */
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/trackerd/tracker-external-services.c 2006-12-07 07:42:52.000000000 -0500
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2006, Edward Duffy
+ *
+ * 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,
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include "tracker-external-services.h"
+
+#define SERVICE_PATH LIBDIR "/tracker/external-services"
+
+extern Tracker *tracker;
+
+static void
+setup_child_cb (gpointer user_data)
+{
+ alarm (GPOINTER_TO_INT (user_data));
+ nice (19);
+}
+
+static gboolean
+tracker_external_services_check_deps (gchar *service_key)
+{
+ gchar *args[2];
+ gchar *stderr;
+ gint exit_code;
+ gboolean result = TRUE;
+
+ args[0] = g_build_filename (SERVICE_PATH, service_key, "check-deps", NULL);
+ args[1] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (SERVICE_PATH, args, NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (10),
+ NULL, &stderr, &exit_code, NULL)) {
+
+ if (exit_code != EXIT_SUCCESS) {
+ tracker_log ("Error loading service %s. Reason given: \"%s\"", service_key, stderr);
+ result = FALSE;
+ }
+ g_free (stderr);
+ }
+ }
+ g_free (args[0]);
+ return result;
+}
+
+GSList *
+tracker_external_services_get_active (GKeyFile *key_file)
+{
+ GSList *result;
+ gchar **services;
+ gsize n_services, i;
+
+ result = NULL;
+ services = g_key_file_get_keys (key_file, "Services", &n_services, NULL);
+ for (i = 0; i < n_services; i++) {
+ gchar *path;
+ if (!g_key_file_get_boolean (key_file, "Services", services[i], NULL)) {
+ tracker_log ("%s disabled", services[i]);
+ continue;
+ }
+ path = g_strconcat (SERVICE_PATH, "/", services[i], NULL);
+ if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
+ if (tracker_external_services_check_deps (services[i])) {
+ result = g_slist_append (result, g_strdup (services[i]));
+ }
+ }
+ g_free (path);
+ }
+ g_strfreev (services);
+ return result;
+}
+
+gchar **
+tracker_external_services_get_watch_list (gchar *service_key)
+{
+ gchar *args[2];
+ gchar *watch_list;
+ gint exit_code;
+ gchar **result = NULL;
+
+ args[0] = g_build_filename (SERVICE_PATH, service_key, "watch-list", NULL);
+ args[1] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (10),
+ &watch_list, NULL, &exit_code, NULL)) {
+
+ if (exit_code == EXIT_SUCCESS) {
+ result = g_strsplit (watch_list, "\n", -1);
+ }
+ g_free (watch_list);
+ }
+ }
+ g_free (args[0]);
+ return result;
+}
+
+#if 0
+gint
+tracker_external_services_get_part_count (gchar *service_key, gchar *path)
+{
+ gchar *args[3];
+ gchar *count;
+ gint exit_code;
+ gint result = 1;
+
+ args[0] = g_build_filename (SERVICE_PATH, service_key, "count-parts", NULL);
+ args[1] = g_strdup (path);
+ args[2] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (10),
+ &count, NULL, &exit_code, NULL)) {
+
+ if (exit_code == EXIT_SUCCESS) {
+ result = atoi (count);
+ }
+ g_free (count);
+ }
+ }
+ g_free (args[1]);
+ g_free (args[0]);
+ return result;
+}
+#endif
+
+gchar *
+tracker_external_services_get_service_type (FileInfo *info)
+{
+ gchar *args[4];
+ gchar *service_type;
+ gint exit_code;
+ gchar *result = NULL;
+
+ args[0] = g_build_filename (SERVICE_PATH, info->app->service_key, "service-type", NULL);
+ args[1] = g_strdup (info->uri);
+ args[2] = g_strdup (info->mime);
+ args[3] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (10),
+ &service_type, NULL, &exit_code, NULL)) {
+
+ if (exit_code == EXIT_SUCCESS) {
+ result = g_strstrip (service_type);
+ }
+ }
+ }
+ g_free (args[2]);
+ g_free (args[1]);
+ g_free (args[0]);
+ return result;
+}
+
+gchar *
+tracker_external_services_get_text_file (FileInfo *info)
+{
+ gchar *args[5];
+ gchar *temp_file_name;
+ gint fd, exit_code;
+ gchar *result = NULL;
+
+ temp_file_name = g_build_filename (tracker->sys_tmp_root_dir, "tmp_text_file_XXXXXX", NULL);
+ fd = g_mkstemp (temp_file_name);
+ if (fd == -1) {
+ tracker_log ("Error creating temporary file %s", temp_file_name);
+ }
+ else {
+ close (fd);
+
+ args[0] = g_build_filename (SERVICE_PATH, info->app->service_key, "filter-text", NULL);
+ args[1] = g_strdup (info->uri);
+ args[2] = g_strdup (info->mime);
+ args[3] = temp_file_name;
+ args[4] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (30),
+ NULL, NULL, &exit_code, NULL)) {
+
+ if (exit_code == EXIT_SUCCESS) {
+ result = g_strdup (temp_file_name);
+ }
+ }
+ }
+ g_free (args[2]);
+ g_free (args[1]);
+ g_free (args[0]);
+ }
+ g_free (temp_file_name);
+ return result;
+}
+
+static void
+print_table_cb (gpointer key, gpointer value, gpointer user_data)
+{
+ tracker_log("%s = \"%s\"", (gchar *)key, (gchar *)value);
+}
+
+void
+tracker_external_services_get_metadata (FileInfo *info, GHashTable *table)
+{
+ gchar *args[4];
+ gchar *value;
+ gint exit_code;
+
+ args[0] = g_build_filename (SERVICE_PATH, info->app->service_key, "extract-metadata", NULL);
+ args[1] = g_strdup (info->uri);
+ args[2] = g_strdup (info->mime);
+ args[3] = NULL;
+
+ if (g_file_test (args[0], G_FILE_TEST_IS_EXECUTABLE)) {
+ if (g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ setup_child_cb, GINT_TO_POINTER (30),
+ &value, NULL, &exit_code, NULL)) {
+
+ if (exit_code == EXIT_SUCCESS) {
+ tracker_parse_metadata (value, table);
+ }
+ g_free (value);
+ }
+ }
+ g_free (args[2]);
+ g_free (args[1]);
+ g_free (args[0]);
+ g_hash_table_foreach (table, print_table_cb, NULL);
+}
Index: src/trackerd/trackerd.c
===================================================================
RCS file: /cvs/gnome/tracker/src/trackerd/trackerd.c,v
retrieving revision 1.60
diff -u -p -r1.60 trackerd.c
--- src/trackerd/trackerd.c 30 Nov 2006 13:26:51 -0000 1.60
+++ src/trackerd/trackerd.c 7 Dec 2006 14:40:48 -0000
@@ -63,6 +63,7 @@
#include "tracker-dbus-files.h"
#include "tracker-indexer.h"
+#include "tracker-external-services.h"
Tracker *tracker;
DBConnection *main_thread_db_con;
@@ -116,7 +117,6 @@ static void delete_file (DBConnection *d
static void scan_directory (const char *uri, DBConnection *db_con);
-
#ifdef POLL_ONLY
gboolean tracker_start_watching (void){tracker->watch_limit = 0; return TRUE;}
@@ -950,12 +950,13 @@ index_file (DBConnection *db_con, DBConn
service_name = g_strdup ("Emails");
} else if (is_an_email_attachment) {
service_name = g_strdup ("EmailAttachments");
+ } else if (info->is_directory) {
+ service_name = g_strdup ("Folders");
+ } else if (info->app) {
+ service_name = tracker_external_services_get_service_type (info);
+ // tracker_log ("%s is a %s", info->uri, service_name);
} else {
- if (info->is_directory) {
- service_name = g_strdup ("Folders");
- } else {
- service_name = tracker_get_service_type_for_mime (info->mime);
- }
+ service_name = tracker_get_service_type_for_mime (info->mime);
}
if (!info->mime) {
@@ -1006,7 +1007,7 @@ index_file (DBConnection *db_con, DBConn
//info->file_id = tracker_db_get_file_id (db_con, info->uri);
- //tracker_log ("created new service ID %d for %s with mime %s and service %s and mtime %d", info->file_id, info->uri, info->mime, service_name, info->mtime);
+ tracker_log ("created new service ID %d for %s with mime %s and service %s and mtime %d", info->file_id, info->uri, info->mime, service_name, info->mtime);
g_free (service_name);
@@ -1065,6 +1066,9 @@ index_file (DBConnection *db_con, DBConn
tracker_close_mbox_and_free_memory (mb);
+ } else if (info->app) {
+ tracker_external_services_get_metadata (info, meta_table);
+ tracker_db_save_metadata (db_con, meta_table, info->file_id, info->is_new);
} else {
if (info->is_new) {
@@ -1075,8 +1079,8 @@ index_file (DBConnection *db_con, DBConn
tracker_db_save_metadata (db_con, meta_table, info->file_id, info->is_new);
- g_hash_table_destroy (meta_table);
}
+ g_hash_table_destroy (meta_table);
}
g_free (name);
@@ -1093,7 +1097,7 @@ index_file (DBConnection *db_con, DBConn
is_file_known = (info->file_id != 0 && (strcmp (info->mime, "unknown") != 0) && (strcmp (info->mime, "symlink") != 0));
is_file_indexable = (tracker_file_is_indexable (info->uri) && !is_a_mbox && !info->is_directory);
- service_has_metadata = (((info->service_type_id <= 7) && (info->service_type_id >= 2)) || (info->service_type_id == 18 ) || (info->service_type_id == 23));
+ service_has_metadata = (((info->service_type_id <= 7) && (info->service_type_id >= 2)) || (info->service_type_id == 18 ) || (info->service_type_id == 23) || (info->app != NULL));
if (is_file_known && is_file_indexable && service_has_metadata) {
tracker_db_add_to_extract_queue (db_con, info);
@@ -1142,6 +1146,7 @@ schedule_file_check (const char *uri, DB
if (!tracker->is_running) {
return;
}
+
g_return_if_fail (uri && (uri[0] == '/'));
g_return_if_fail (db_con);
@@ -1177,6 +1182,29 @@ scan_directory (const char *uri, DBConne
schedule_file_check (uri, db_con);
}
+static void
+add_service_watches (gpointer data, gpointer user_data)
+{
+ gchar *service;
+ gchar **watch_list, **p;
+
+ service = (gchar *)data;
+ watch_list = tracker_external_services_get_watch_list (service);
+
+ for (p = watch_list; *p != NULL; ++p) {
+ if (strlen (*p) == 0)
+ continue;
+ if (g_file_test (*p, G_FILE_TEST_IS_DIR)) {
+ g_hash_table_insert (tracker->app_explicit_dir, g_strdup (*p), service);
+ }
+ else {
+ g_hash_table_insert (tracker->app_explicit_file, g_strdup (*p), service);
+ }
+ tracker_add_watch_dir (*p, main_thread_db_con);
+ schedule_file_check (*p, main_thread_db_con);
+ }
+ g_strfreev (watch_list);
+}
static gboolean
@@ -1197,6 +1225,9 @@ start_watching (gpointer data)
tracker_watch_emails (main_thread_db_con);
}
+ /* add application support */
+ g_slist_foreach (tracker->active_services, add_service_watches, NULL);
+
if (data) {
char *watch_folder;
int len;
@@ -1453,7 +1484,6 @@ extract_metadata_thread (void)
/* refresh stat data in case its changed */
info = tracker_get_file_info (info);
-
if (info->file_id == 0) {
info->file_id = tracker_db_get_file_id (db_con, info->uri);
}
@@ -1488,7 +1518,10 @@ extract_metadata_thread (void)
}
- file_as_text = tracker_metadata_get_text_file (info->uri, info->mime);
+ if (info->app)
+ file_as_text = tracker_external_services_get_text_file (info);
+ else
+ file_as_text = tracker_metadata_get_text_file (info->uri, info->mime);
if (tracker->enable_content_indexing && file_as_text) {
const char *tmp_dir;
@@ -1843,6 +1876,22 @@ process_files_thread (void)
info = tracker_get_file_info (info);
}
+ if (info->app == NULL && g_file_test (info->uri, G_FILE_TEST_IS_REGULAR)) {
+ gchar *service = NULL;
+
+ /* should this be handled by an external module, or tracker? */
+ service = g_hash_table_lookup (tracker->app_explicit_file, info->uri);
+ if (!service) {
+ gchar *dir = g_path_get_dirname (info->uri);
+ service = g_hash_table_lookup (tracker->app_explicit_dir, dir);
+ g_free (dir);
+ }
+ if (service) {
+ info->app = g_new(ExternalService, 1);
+ info->app->service_key = service;
+ }
+ }
+
/* check if file needs indexing */
need_index = (info->mtime > info->indextime);
@@ -2568,6 +2617,7 @@ main (int argc, char **argv)
setlocale (LC_ALL, "");
+ g_type_init ();
bindtextdomain (GETTEXT_PACKAGE, TRACKER_LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
@@ -2638,6 +2688,10 @@ main (int argc, char **argv)
tracker->is_running = FALSE;
tracker->first_time_index = FALSE;
+
+ tracker->app_explicit_dir = g_hash_table_new (g_str_hash, g_str_equal);
+ tracker->app_explicit_file = g_hash_table_new (g_str_hash, g_str_equal);
+ tracker->app_implicit_dir = NULL;
tracker->poll_access_mutex = g_mutex_new ();
Index: src/trackerd/tracker-metadata.c
===================================================================
RCS file: /cvs/gnome/tracker/src/trackerd/tracker-metadata.c,v
retrieving revision 1.23
diff -u -p -r1.23 tracker-metadata.c
--- src/trackerd/tracker-metadata.c 30 Nov 2006 01:47:44 -0000 1.23
+++ src/trackerd/tracker-metadata.c 7 Dec 2006 14:42:07 -0000
@@ -371,6 +371,74 @@ tracker_metadata_get_thumbnail (const ch
void
+tracker_parse_metadata (gchar *value, GHashTable *table)
+{
+ if (value && strchr (value, '=') && strchr (value, ';')) {
+ char **values, **values_p;
+
+ values = g_strsplit_set (value, ";", -1);
+
+ for (values_p = values; *values_p; values_p++) {
+ char *meta_data, *sep;
+
+ meta_data = g_strdup (g_strstrip (*values_p));
+
+ sep = strchr (meta_data, '=');
+
+ if (sep) {
+ char *meta_name;
+
+ meta_name = g_strndup (meta_data, sep - meta_data);
+
+ if (meta_name) {
+ char *meta_value;
+
+ meta_value = g_strdup (sep + 1);
+
+ if (meta_value) {
+ char *st;
+
+ //tracker_log ("testing %s = %s", meta_name, meta_value);
+ st = g_hash_table_lookup (table, meta_name);
+
+ if (st == NULL) {
+ char *utf_value;
+
+ if (!g_utf8_validate (meta_value, -1, NULL)) {
+
+ utf_value = g_locale_to_utf8 (meta_value, -1, NULL, NULL, NULL);
+ } else {
+ utf_value = g_strdup (meta_value);
+ }
+
+ if (utf_value) {
+ guint32 length = strlen (utf_value);
+
+ if ((length > 0) && (length >= strlen (meta_value))) {
+
+ g_debug ("%s = %s", meta_name, utf_value);
+ g_hash_table_insert (table, g_strdup (meta_name), g_strdup (utf_value));
+ }
+
+ g_free (utf_value);
+ }
+ }
+
+ g_free (meta_value);
+ }
+
+ g_free (meta_name);
+ }
+ }
+
+ g_free (meta_data);
+ }
+
+ g_strfreev (values);
+ }
+}
+
+void
tracker_metadata_get_embedded (const char *uri, const char *mime, GHashTable *table)
{
MetadataFileType meta_type;
@@ -420,70 +488,7 @@ tracker_metadata_get_embedded (const cha
NULL)) {
/* parse returned stdout (value) and extract keys and associated metadata values */
-
- if (value && strchr (value, '=') && strchr (value, ';')) {
- char **values, **values_p;
-
- values = g_strsplit_set (value, ";", -1);
-
- for (values_p = values; *values_p; values_p++) {
- char *meta_data, *sep;
-
- meta_data = g_strdup (g_strstrip (*values_p));
-
- sep = strchr (meta_data, '=');
-
- if (sep) {
- char *meta_name;
-
- meta_name = g_strndup (meta_data, sep - meta_data);
-
- if (meta_name) {
- char *meta_value;
-
- meta_value = g_strdup (sep + 1);
-
- if (meta_value) {
- char *st;
-
- //tracker_log ("testing %s = %s", meta_name, meta_value);
- st = g_hash_table_lookup (table, meta_name);
-
- if (st == NULL) {
- char *utf_value;
-
- if (!g_utf8_validate (meta_value, -1, NULL)) {
-
- utf_value = g_locale_to_utf8 (meta_value, -1, NULL, NULL, NULL);
- } else {
- utf_value = g_strdup (meta_value);
- }
-
- if (utf_value) {
- guint32 length = strlen (utf_value);
-
- if ((length > 0) && (length >= strlen (meta_value))) {
-
- g_debug ("%s = %s", meta_name, utf_value);
- g_hash_table_insert (table, g_strdup (meta_name), g_strdup (utf_value));
- }
-
- g_free (utf_value);
- }
- }
-
- g_free (meta_value);
- }
-
- g_free (meta_name);
- }
- }
-
- g_free (meta_data);
- }
-
- g_strfreev (values);
- }
+ tracker_parse_metadata (value, table);
if (value) {
g_free (value);
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/Makefile.am 2006-12-07 07:43:11.000000000 -0500
@@ -0,0 +1,8 @@
+SUBDIRS = \
+ IndexManPages \
+ IndexLiferea \
+ IndexTomboy
+
+exservbindir = $(libdir)/tracker/external-services
+exservbin_SCRIPTS = services.rc
+EXTRA_DIST = $(exservbin_SCRIPTS)
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/services.rc 2006-12-07 07:43:12.000000000 -0500
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+function require {
+ which $1 || {
+ echo "Cannot find $1" >/dev/stderr
+ exit 1
+ }
+}
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/Makefile.am 2006-12-07 07:43:07.000000000 -0500
@@ -0,0 +1,10 @@
+servicebindir = $(libdir)/tracker/external-services/IndexManPages
+
+servicebin_SCRIPTS = \
+ check-deps \
+ extract-metadata \
+ filter-text \
+ service-type \
+ watch-list
+
+EXTRA_DIST = $(servicebin_SCRIPTS)
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/check-deps 2006-12-07 07:43:09.000000000 -0500
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+source services.rc
+
+require cat
+require grep
+require zgrep
+require troff
+require gunzip
+require manpath
+require find
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/extract-metadata 2006-12-07 07:43:08.000000000 -0500
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# See "man 7 man" for help on this function
+function print_metadata {
+ [ -n "$2" ] && echo "Man.Title=$2;"
+ [ -n "$3" ] && echo "Man.Section=$3;"
+ [ -n "$4" ] && echo "Man.Date=$4;"
+ [ -n "$5" ] && echo "Man.Source=$5;"
+ [ -n "$6" ] && echo "Man.Manual=$6;"
+ # TODO: Language?
+}
+
+[ $2 == "application/x-gzip" ] && GREP=zgrep || GREP=grep
+
+eval print_metadata $($GREP --max-count=1 '^\.TH' $1)
+exit 0
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/filter-text 2006-12-07 07:43:08.000000000 -0500
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+([ $2 == "application/x-gzip" ] \
+ && gunzip --to-stdout "$1" \
+ || cat $1) | troff -a > $3
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/service-type 2006-12-07 07:43:06.000000000 -0500
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo Man Pages
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexManPages/watch-list 2006-12-07 07:43:07.000000000 -0500
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# This will only give us English man pages. Remove the while loop for all
+# languages.
+
+for dir in $(manpath | tr ':' ' ')
+do
+ find $dir -type d |
+ while read x
+ do
+ parent_dir=$(basename $(dirname $x))
+ dirname=$(basename $x)
+ [ $parent_dir == "man" -a ${dirname:0:3} == "man" ] && echo $x
+ done
+done
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/Makefile.am 2006-12-07 07:43:18.000000000 -0500
@@ -0,0 +1,10 @@
+servicebindir = $(libdir)/tracker/external-services/IndexTomboy
+
+servicebin_SCRIPTS = \
+ check-deps \
+ extract-metadata \
+ filter-text \
+ service-type \
+ watch-list
+
+EXTRA_DIST = $(servicebin_SCRIPTS)
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/check-deps 2006-12-07 07:43:20.000000000 -0500
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source services.rc
+
+require xsltproc
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/extract-metadata 2006-12-07 07:43:18.000000000 -0500
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+xsltproc - "$1" <<EOF
+<?xml version="1.0"?>
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tomboy="http://beatniksoftware.com/tomboy"
+ version="1.0">
+ <xsl:output method="text" />
+ <xsl:strip-space elements="*" />
+ <xsl:output omit-xml-declaration="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="tomboy:note" />
+ </xsl:template>
+
+ <xsl:template match="tomboy:note">
+ <xsl:apply-templates select="tomboy:title" />
+ </xsl:template>
+
+ <xsl:template match="tomboy:title">
+ <xsl:text>Note.Title=</xsl:text>
+ <xsl:value-of select="." />
+ <xsl:text>; </xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
+EOF
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/filter-text 2006-12-07 07:43:18.000000000 -0500
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+xsltproc - "$1" > $3 <<EOF
+<?xml version="1.0"?>
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:tomboy="http://beatniksoftware.com/tomboy"
+ version="1.0">
+
+ <xsl:output method="text" />
+ <xsl:strip-space elements="*" />
+ <xsl:output omit-xml-declaration="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="tomboy:note" />
+ </xsl:template>
+
+ <xsl:template match="tomboy:note">
+ <xsl:apply-templates select="tomboy:text" />
+ </xsl:template>
+
+ <xsl:template match="tomboy:text">
+ <xsl:value-of select="." />
+ </xsl:template>
+
+</xsl:stylesheet>
+EOF
+
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/service-type 2006-12-07 07:43:17.000000000 -0500
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo Notes
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexTomboy/watch-list 2006-12-07 07:43:17.000000000 -0500
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo ~/.tomboy
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/Makefile.am 2006-12-07 10:48:26.000000000 -0500
@@ -0,0 +1,10 @@
+servicebindir = $(libdir)/tracker/external-services/IndexLiferea
+
+servicebin_SCRIPTS = \
+ check-deps \
+ extract-metadata \
+ filter-text \
+ service-type \
+ watch-list
+
+EXTRA_DIST = $(servicebin_SCRIPTS)
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/check-deps 2006-12-07 07:43:13.000000000 -0500
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+source services.rc
+
+require cat
+require xsltproc
+require w3m
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/count-parts 2006-12-07 07:43:15.000000000 -0500
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+xsltproc - "$1" <<EOF
+<?xml version="1.0"?>
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+ <xsl:output method="text"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:output omit-xml-declaration="yes"/>
+
+ <xsl:template match="/feed">
+ <xsl:value-of select="count(//item)"/>
+ <xsl:apply-templates select="item" />
+ </xsl:template>
+
+ <xsl:template match="item">
+ <xsl:value-of select="@title"/>
+ </xsl:template>
+</xsl:stylesheet>
+EOF
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/extract-metadata 2006-12-07 10:46:16.000000000 -0500
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+xsltproc - "$1" <<EOF
+<?xml version="1.0"?>
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+ <xsl:output method="text" />
+ <xsl:strip-space elements="*" />
+ <xsl:output omit-xml-declaration="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="feed" />
+ </xsl:template>
+
+ <xsl:template match="feed">
+ <xsl:apply-templates select="feedTitle" />
+ <xsl:apply-templates select="feedSource" />
+ <xsl:apply-templates select="feedDescription" />
+ </xsl:template>
+
+ <xsl:template match="feedTitle">
+ <xsl:text>RSS.Title=</xsl:text>
+ <xsl:value-of select="." />
+ <xsl:text>; </xsl:text>
+ </xsl:template>
+
+ <xsl:template match="feedSource">
+ <xsl:text>RSS.Source=</xsl:text>
+ <xsl:value-of select="." />
+ <xsl:text>; </xsl:text>
+ </xsl:template>
+
+ <xsl:template match="feedDescription">
+ <xsl:text>RSS.Description=</xsl:text>
+ <xsl:value-of select="." />
+ <xsl:text>; </xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
+EOF
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/filter-text 2006-12-07 10:44:35.000000000 -0500
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+xsltproc - "$1" > $3 <<EOF
+<?xml version="1.0"?>
+<xsl:stylesheet
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+ <xsl:output method="text" />
+ <xsl:strip-space elements="*" />
+ <xsl:output omit-xml-declaration="yes" />
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="feed" />
+ </xsl:template>
+
+ <xsl:template match="feed">
+ <xsl:apply-templates select="item" />
+ </xsl:template>
+
+ <xsl:template match="item">
+ <xsl:apply-templates select="description" />
+ </xsl:template>
+
+ <xsl:template match="description">
+ <xsl:value-of select="." />
+ </xsl:template>
+
+</xsl:stylesheet>
+EOF
+) | w3m -T text/html -dump > $3
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/watch-list 2006-12-07 07:43:13.000000000 -0500
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo ~/.liferea/cache/feeds
Index: data/sqlite-tracker.sql
===================================================================
RCS file: /cvs/gnome/tracker/data/sqlite-tracker.sql,v
retrieving revision 1.11
diff -u -p -r1.11 sqlite-tracker.sql
--- data/sqlite-tracker.sql 10 Nov 2006 22:24:13 -0000 1.11
+++ data/sqlite-tracker.sql 7 Dec 2006 15:50:18 -0000
@@ -51,6 +51,8 @@ insert Into ServiceTypes (TypeID, TypeNa
insert Into ServiceTypes (TypeID, TypeName, MetadataClass, Description, MainService) values (28, 'History', 'History', 'history only', 0);
insert Into ServiceTypes (TypeID, TypeName, MetadataClass, Description, MainService) values (29, 'Projects', 'Project', 'projects only', 0);
insert Into ServiceTypes (TypeID, TypeName, MetadataClass, Description, MainService) values (30, 'Web Pages', 'Web', 'Visited web pages only', 0);
+insert Into ServiceTypes (TypeID, TypeName, MetadataClass, Description, MainService) values (31, 'Web Channels', 'RSS', 'Web channels subscribed too', 0);
+insert Into ServiceTypes (TypeID, TypeName, MetadataClass, Description, MainService) values (32, 'Man Pages', 'Man, File', 'manual page', 0);
/* store volume and HAL info here for files */
CREATE TABLE Volumes
@@ -320,6 +322,18 @@ insert Into MetaDataTypes (MetaName, Dat
insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Email.Sender', 4, 1, 0, 10);
insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Email.SentTo', 4, 1, 0, 10);
insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Email.Subject', 0, 1, 0, 30);
+
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Note.Title', 0, 1, 0, 1);
+
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('RSS.Title', 0, 1, 0, 10);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('RSS.Source', 0, 1, 0, 100);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('RSS.Description', 0, 1, 0, 20);
+
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Man.Title', 0, 1, 0, 1);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Man.Section', 0, 1, 0, 100);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Man.Date', 0, 1, 0, 100);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Man.Source', 0, 1, 0, 100);
+insert Into MetaDataTypes (MetaName, DatatypeID, Embedded, Writeable, Weight) values ('Man.Manual', 0, 1, 0, 50);
end transaction;
Index: src/libtracker/tracker.h
===================================================================
RCS file: /cvs/gnome/tracker/src/libtracker/tracker.h,v
retrieving revision 1.10
diff -u -p -r1.10 tracker.h
--- src/libtracker/tracker.h 19 Oct 2006 01:30:19 -0000 1.10
+++ src/libtracker/tracker.h 7 Dec 2006 15:51:43 -0000
@@ -60,11 +60,15 @@ typedef enum {
SERVICE_CONTACTS,
SERVICE_EMAILS,
SERVICE_EMAILATTACHMENTS,
+ SERVICE_NOTES,
SERVICE_APPOINTMENTS,
SERVICE_TASKS,
SERVICE_BOOKMARKS,
SERVICE_HISTORY,
- SERVICE_PROJECTS
+ SERVICE_PROJECTS,
+ SERVICE_WEB_PAGES,
+ SERVICE_WEB_CHANNELS,
+ SERVICE_MAN_PAGES,
} ServiceType;
Index: src/libtracker/tracker.c
===================================================================
RCS file: /cvs/gnome/tracker/src/libtracker/tracker.c,v
retrieving revision 1.11
diff -u -p -r1.11 tracker.c
--- src/libtracker/tracker.c 5 Nov 2006 23:31:39 -0000 1.11
+++ src/libtracker/tracker.c 7 Dec 2006 15:52:04 -0000
@@ -103,11 +103,15 @@ char *service_types[] = {
"Contacts",
"Emails",
"EmailAttachments",
+"Notes",
"Appointments",
"Tasks",
"Bookmarks",
"History",
-"Projects"
+"Projects",
+"Web Pages",
+"Web Channels",
+"Man Pages"
};
Index: src/libtracker/tracker-search.c
===================================================================
RCS file: /cvs/gnome/tracker/src/libtracker/tracker-search.c,v
retrieving revision 1.20
diff -u -p -r1.20 tracker-search.c
--- src/libtracker/tracker-search.c 21 Nov 2006 08:19:21 -0000 1.20
+++ src/libtracker/tracker-search.c 7 Dec 2006 15:52:11 -0000
@@ -153,6 +153,10 @@ main (int argc, char **argv)
type = SERVICE_TEXT_FILES;
} else if (g_ascii_strcasecmp (service, "Development") == 0) {
type = SERVICE_DEVELOPMENT_FILES;
+ } else if (g_ascii_strcasecmp (service, "Notes") == 0) {
+ type = SERVICE_NOTES;
+ } else if (g_ascii_strcasecmp (service, "Man Pages") == 0) {
+ type = SERVICE_MAN_PAGES;
} else {
g_printerr (_("Service not recognized, searching in Other Files...\n"));
type = SERVICE_OTHER_FILES;
--- /dev/null 2006-10-23 12:36:11.000000000 -0400
+++ src/external-services/IndexLiferea/service-type 2006-12-07 07:43:13.000000000 -0500
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo Web Channels
_______________________________________________
tracker-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/tracker-list