/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ /* vfolder-desktop-method.c Copyright (C) 2001 Red Hat, Inc. Copyright (C) 2001 The Dark Prince The Gnome Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The Gnome 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the Gnome Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* URI scheme for reading the "applications:" vfolder and other * vfolder schemes. Lots of code stolen from the original desktop * reading URI scheme. */ #ifdef HAVE_CONFIG_H # include #endif /* Debugging foo: */ /*#define D(x) x */ #define D(x) ; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DOT_GNOME ".gnome2" typedef struct _VFolderInfo VFolderInfo; typedef struct _Query Query; typedef struct _QueryKeyword QueryKeyword; typedef struct _QueryFilename QueryFilename; typedef struct _Entry Entry; typedef struct _Folder Folder; typedef struct _EntryFile EntryFile; typedef struct _Keyword Keyword; typedef struct _FileMonitorHandle FileMonitorHandle; typedef struct _StatLoc StatLoc; typedef struct _VFolderURI VFolderURI; /* TODO before 2.0: */ /* FIXME: also check/monitor desktop_dirs like we do the vfolder * file and the item dirs */ /* FIXME: check if thread locks are not completely on crack which * is likely given my experience with threads */ /* FIXME: use filename locking, currently we are full of races if * multiple processes write to this filesystem */ /* FIXME: implement monitors */ /* TODO for later (star trek future): */ /* FIXME: Maybe when chaining to file:, we should call the gnome-vfs wrapper * functions, instead of the file: methods directly. */ /* FIXME: related to the above: we should support things being on non * file: filesystems. Such as having the vfolder info file on http * somewhere or some such nonsense :) */ static GnomeVFSMethod *parent_method = NULL; static GHashTable *infos = NULL; /* Note: I have no clue about how to write thread safe code and this * is my first attempt, so it's probably wrong * -George */ G_LOCK_DEFINE_STATIC (vfolder_lock); /* Note: all keywords are quarks */ /* Note: basenames are unique */ #define UNSUPPORTED_INFO_FIELDS (GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS | \ GNOME_VFS_FILE_INFO_FIELDS_DEVICE | \ GNOME_VFS_FILE_INFO_FIELDS_INODE | \ GNOME_VFS_FILE_INFO_FIELDS_LINK_COUNT | \ GNOME_VFS_FILE_INFO_FIELDS_ATIME) enum { QUERY_OR, QUERY_AND, QUERY_KEYWORD, QUERY_FILENAME }; struct _Query { int type; gboolean not; GSList *queries; }; struct _QueryKeyword { int type; gboolean not; GQuark keyword; }; struct _QueryFilename { int type; gboolean not; char *filename; }; enum { ENTRY_FILE, ENTRY_FOLDER }; struct _Entry { int type; int refcount; int alloc; /* not really useful for folders, but oh well, whatever. It's the number of times this is queried in some directory, used for the Unallocated query type */ char *name; GSList *monitors; }; struct _EntryFile { Entry entry; char *filename; gboolean per_user; GSList *keywords; gboolean implicit_keywords; /* the keywords were added by us */ }; struct _Folder { Entry entry; Folder *parent; char *desktop_file; /* the .directory file */ Query *query; /* The following is for per file * access */ /* excluded by filename */ GHashTable *excludes; /* included by filename */ GSList *includes; GHashTable *includes_ht; GSList *subfolders; /* Some flags */ gboolean read_only; gboolean dont_show_if_empty; gboolean only_unallocated; /* include only unallocated items */ /* lazily done, will run query only when it * needs to */ gboolean up_to_date; gboolean sorted; GSList *entries; }; struct _StatLoc { time_t ctime; time_t last_stat; gboolean trigger_next; /* if true, next check will fail */ char name[1]; /* the structure will be long enough to fit name */ }; struct _VFolderInfo { char *scheme; char *filename; char *user_filename; time_t user_filename_last_write; char *desktop_dir; /* directory with .directorys */ char *user_desktop_dir; /* directory with .directorys */ gboolean user_file_active; /* if using user_filename and not filename */ GSList *item_dirs; char *user_item_dir; /* dir where user changes to items are stored */ /* old style dirs to merge in */ GSList *merge_dirs; /* if entries are valid, else * they need to be (re)read */ gboolean entries_valid; GSList *entries; /* entry hash by basename */ GHashTable *entries_ht; /* The root folder */ Folder *root; /* The unallocated folders, the folder which only * include unallocated items */ GSList *unallocated_folders; /* some flags */ gboolean read_only; gboolean dirty; int inhibit_write; /* change monitoring stuff */ GnomeVFSMonitorHandle *filename_monitor; GnomeVFSMonitorHandle *user_filename_monitor; /* stat locations (in case we aren't monitoring) */ StatLoc *filename_statloc; StatLoc *user_filename_statloc; /* for .directory dirs */ /* FIXME: */GnomeVFSMonitorHandle *desktop_dir_monitor; /* FIXME: */GnomeVFSMonitorHandle *user_desktop_dir_monitor; /* stat locations (in case we aren't monitoring) */ /* FIXME: */StatLoc *desktop_dir_statloc; /* FIXME: */StatLoc *user_desktop_dir_statloc; /* FIXME: */GSList *file_monitors; /* FileMonitorHandle */ /* FIXME: */GSList *free_file_monitors; /* FileMonitorHandle */ GSList *folder_monitors; /* FileMonitorHandle */ GSList *free_folder_monitors; /* FileMonitorHandle */ GSList *item_dir_monitors; /* GnomeVFSMonitorHandle */ /* item dirs to stat */ GSList *stat_dirs; /* ctime for folders */ time_t modification_time; guint reread_queue; }; struct _FileMonitorHandle { int refcount; gboolean exists; gboolean dir_monitor; /* TRUE if dir, FALSE if file */ GnomeVFSURI *uri; GnomeVFSMonitorHandle *handle; /* A real handle if we're monitoring an actual file here, or NULL */ char *filename; /* Just the basename, used in the free_file_list */ gboolean is_directory_file; }; struct _VFolderURI { const gchar *scheme; gboolean is_all_scheme; gboolean ends_in_slash; gchar *path; gchar *file; GnomeVFSURI *uri; }; static Entry * entry_ref (Entry *entry); static Entry * entry_ref_alloc (Entry *entry); static void entry_unref (Entry *entry); static void entry_unref_dealloc (Entry *entry); static void query_destroy (Query *query); static void ensure_folder (VFolderInfo *info, Folder *folder, gboolean subfolders, Folder *except, gboolean ignore_unallocated); static void ensure_folder_unlocked (VFolderInfo *info, Folder *folder, gboolean subfolders, Folder *except, gboolean ignore_unallocated); /* An EVIL function for quick reading of .desktop files, * only reads in one or two keys, but that's ALL we need */ static void readitem_entry (const char *filename, const char *key1, char **result1, const char *key2, char **result2); static gboolean vfolder_info_reload (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context, gboolean force_read_items); static gboolean vfolder_info_reload_unlocked (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context, gboolean force_read_items); static void invalidate_folder_subfolders (Folder *folder, gboolean lock_taken); static Folder * resolve_folder (VFolderInfo *info, const char *path, gboolean ignore_basename, GnomeVFSResult *result, GnomeVFSContext *context); static gboolean vfolder_info_read_items (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context); /* assumes vuri->path already set */ static gboolean vfolder_uri_parse_internal (GnomeVFSURI *uri, VFolderURI *vuri) { vuri->scheme = (gchar *) gnome_vfs_uri_get_scheme (uri); vuri->ends_in_slash = FALSE; if (strncmp (vuri->scheme, "all-", strlen ("all-")) == 0) { vuri->scheme += strlen ("all-"); vuri->is_all_scheme = TRUE; } else vuri->is_all_scheme = FALSE; if (vuri->path != NULL) { int last_slash = strlen (vuri->path) - 1; char *first; /* Note: This handling of paths is somewhat evil, may need a * bit of a rework */ /* kill leading slashes, that is make sure there is * only one */ for (first = vuri->path; *first == '/'; first++) ; if (first != vuri->path) { first--; vuri->path = first; } /* kill trailing slashes (leave first if all slashes) */ while (last_slash > 0 && vuri->path [last_slash] == '/') { vuri->path [last_slash--] = '\0'; vuri->ends_in_slash = TRUE; } /* get basename start */ while (last_slash >= 0 && vuri->path [last_slash] != '/') last_slash--; if (last_slash > -1) vuri->file = vuri->path + last_slash + 1; else vuri->file = vuri->path; if (vuri->file[0] == '\0' && strcmp (vuri->path, "/") == 0) { vuri->file = NULL; } } else { vuri->ends_in_slash = TRUE; vuri->path = "/"; vuri->file = NULL; } vuri->uri = uri; return TRUE; } #define VFOLDER_URI_PARSE(_uri, _vuri) { \ gchar *path; \ path = gnome_vfs_unescape_string ((_uri)->text, G_DIR_SEPARATOR_S); \ if (path != NULL) { \ (_vuri)->path = g_alloca (strlen (path) + 1); \ strcpy ((_vuri)->path, path); \ g_free (path); \ } else { \ (_vuri)->path = NULL; \ } \ vfolder_uri_parse_internal ((_uri), (_vuri)); \ } static FileMonitorHandle * file_monitor_handle_ref_unlocked (FileMonitorHandle *h) { h->refcount ++; return h; } static void file_monitor_handle_unref_unlocked (FileMonitorHandle *h) { h->refcount --; if (h->refcount == 0) { gnome_vfs_uri_unref (h->uri); h->uri = NULL; g_free (h->filename); h->filename = NULL; if (h->handle != NULL) { gnome_vfs_monitor_cancel (h->handle); h->handle = NULL; } } } /* This includes the .directory files */ static void emit_monitor (Folder *folder, int type) { GSList *li; for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) handle, handle->uri, type); } } static void emit_file_deleted_monitor (VFolderInfo *info, Entry *entry, Folder *folder) { GSList *li; for (li = entry->monitors; li != NULL; li = li->next) { VFolderURI vuri; Folder *f; GnomeVFSResult result; FileMonitorHandle *handle = li->data; /* Evil! EVIL URI PARSING. this will eat a lot of * stack if we have lots of monitors */ VFOLDER_URI_PARSE (handle->uri, &vuri); f = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (f == folder) gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) handle, handle->uri, GNOME_VFS_MONITOR_EVENT_DELETED); } } static void emit_and_delete_monitor (VFolderInfo *info, Folder *folder) { GSList *li; for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; li->data = NULL; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) handle, handle->uri, GNOME_VFS_MONITOR_EVENT_DELETED); if (handle->dir_monitor) info->free_folder_monitors = g_slist_prepend (info->free_folder_monitors, handle); else info->free_file_monitors = g_slist_prepend (info->free_file_monitors, handle); } g_slist_free (((Entry *)folder)->monitors); ((Entry *)folder)->monitors = NULL; } static gboolean check_ext (const char *name, const char *ext_check) { const char *ext; ext = strrchr (name, '.'); if (ext == NULL || strcmp (ext, ext_check) != 0) return FALSE; else return TRUE; } static StatLoc * bake_statloc (const char *name, time_t curtime) { struct stat s; StatLoc *sl = NULL; if (stat (name, &s) != 0) { if (errno == ENOENT) { sl = g_malloc0 (sizeof (StatLoc) + strlen (name) + 1); sl->last_stat = curtime; sl->ctime = 0; sl->trigger_next = FALSE; strcpy (sl->name, name); } return sl; } sl = g_malloc0 (sizeof (StatLoc) + strlen (name) + 1); sl->last_stat = curtime; sl->ctime = s.st_ctime; sl->trigger_next = FALSE; strcpy (sl->name, name); return sl; } /* returns FALSE if we must reread */ static gboolean check_statloc (StatLoc *sl, time_t curtime) { struct stat s; if (sl->trigger_next) { sl->trigger_next = FALSE; return FALSE; } /* don't stat more then once every 3 seconds */ if (curtime <= sl->last_stat + 3) return TRUE; sl->last_stat = curtime; if (stat (sl->name, &s) != 0) { if (errno == ENOENT && sl->ctime == 0) return TRUE; else return FALSE; } if (sl->ctime == s.st_ctime) return TRUE; else return FALSE; } static gboolean ensure_dir (const char *dirname, gboolean ignore_basename) { char *parsed, *p; if (dirname == NULL) return FALSE; if (ignore_basename) parsed = g_path_get_dirname (dirname); else parsed = g_strdup (dirname); if (g_file_test (parsed, G_FILE_TEST_IS_DIR)) { g_free (parsed); return TRUE; } p = strchr (parsed, '/'); if (p == parsed) p = strchr (p+1, '/'); while (p != NULL) { *p = '\0'; if (mkdir (parsed, 0700) != 0 && errno != EEXIST) { g_free (parsed); return FALSE; } *p = '/'; p = strchr (p+1, '/'); } if (mkdir (parsed, 0700) != 0 && errno != EEXIST) { g_free (parsed); return FALSE; } g_free (parsed); return TRUE; } /* check for any directory name other then root */ static gboolean any_subdir (const char *dirname) { const char *p; if (dirname == NULL) return FALSE; for (p = dirname; *p != '\0'; p++) { if (*p != '/') { return TRUE; } } return FALSE; } static void destroy_entry_file (EntryFile *efile) { if (efile == NULL) return; g_free (efile->filename); efile->filename = NULL; g_slist_free (efile->keywords); efile->keywords = NULL; g_free (efile); } static void destroy_folder (Folder *folder) { GSList *list; if (folder == NULL) return; if (folder->parent != NULL) { folder->parent->subfolders = g_slist_remove (folder->parent->subfolders, folder); folder->parent->up_to_date = FALSE; folder->parent = NULL; } g_free (folder->desktop_file); folder->desktop_file = NULL; query_destroy (folder->query); folder->query = NULL; if (folder->excludes != NULL) { g_hash_table_destroy (folder->excludes); folder->excludes = NULL; } g_slist_foreach (folder->includes, (GFunc)g_free, NULL); g_slist_free (folder->includes); folder->includes = NULL; if (folder->includes_ht != NULL) { g_hash_table_destroy (folder->includes_ht); folder->includes_ht = NULL; } list = folder->subfolders; folder->subfolders = NULL; g_slist_foreach (list, (GFunc)entry_unref, NULL); g_slist_free (list); list = folder->entries; folder->entries = NULL; g_slist_foreach (list, (GFunc)entry_unref, NULL); g_slist_free (list); g_free (folder); } static Entry * entry_ref (Entry *entry) { if (entry != NULL) entry->refcount++; return entry; } static Entry * entry_ref_alloc (Entry *entry) { entry_ref (entry); if (entry != NULL) entry->alloc++; return entry; } static void entry_unref (Entry *entry) { if (entry == NULL) return; entry->refcount--; if (entry->refcount == 0) { g_free (entry->name); entry->name = NULL; g_slist_foreach (entry->monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (entry->monitors); entry->monitors = NULL; if (entry->type == ENTRY_FILE) destroy_entry_file ((EntryFile *)entry); else /* ENTRY_FOLDER */ destroy_folder ((Folder *)entry); } } static void entry_unref_dealloc (Entry *entry) { if (entry != NULL) { entry->alloc --; entry_unref (entry); } } /* Handles ONLY files, not dirs */ /* Also allocates the entries as well as refs them */ static GSList * alloc_entries_from_files (VFolderInfo *info, GSList *filenames) { GSList *li; GSList *files; files = NULL; for (li = filenames; li != NULL; li = li->next) { char *filename = li->data; GSList *entry_list = g_hash_table_lookup (info->entries_ht, filename); if (entry_list != NULL) files = g_slist_prepend (files, entry_ref_alloc (entry_list->data)); } return files; } static gboolean matches_query (VFolderInfo *info, Folder *folder, EntryFile *efile, Query *query) { GSList *li; if (query == NULL) return TRUE; #define INVERT_IF_NEEDED(val) (query->not ? !(val) : (val)) switch (query->type) { case QUERY_OR: for (li = query->queries; li != NULL; li = li->next) { Query *subquery = li->data; if (matches_query (info, folder, efile, subquery)) return INVERT_IF_NEEDED (TRUE); } return INVERT_IF_NEEDED (FALSE); case QUERY_AND: for (li = query->queries; li != NULL; li = li->next) { Query *subquery = li->data; if ( ! matches_query (info, folder, efile, subquery)) return INVERT_IF_NEEDED (FALSE); } return INVERT_IF_NEEDED (TRUE); case QUERY_KEYWORD: { QueryKeyword *qkeyword = (QueryKeyword *)query; for (li = efile->keywords; li != NULL; li = li->next) { GQuark keyword = GPOINTER_TO_INT (li->data); if (keyword == qkeyword->keyword) return INVERT_IF_NEEDED (TRUE); } return INVERT_IF_NEEDED (FALSE); } case QUERY_FILENAME: { QueryFilename *qfilename = (QueryFilename *)query; if (strcmp (qfilename->filename, ((Entry *)efile)->name) == 0) { return INVERT_IF_NEEDED (TRUE); } else { return INVERT_IF_NEEDED (FALSE); } } } #undef INVERT_IF_NEEDED g_assert_not_reached (); /* huh? */ return FALSE; } static void dump_unallocated_folders (Folder *folder) { GSList *li; for (li = folder->subfolders; li != NULL; li = li->next) dump_unallocated_folders (li->data); if (folder->only_unallocated && folder->entries != NULL) { g_slist_foreach (folder->entries, (GFunc)entry_unref_dealloc, NULL); g_slist_free (folder->entries); folder->entries = NULL; } } /* Run query, allocs and refs the entries */ static void append_query (VFolderInfo *info, Folder *folder) { GSList *li; if (folder->query == NULL && ! folder->only_unallocated) return; if (folder->only_unallocated) { /* dump all folders that use unallocated * items only. This sucks if you keep * reading one and then another such * folder, but oh well, life sucks for * you then, but at least you get * consistent results */ dump_unallocated_folders (info->root); /* ensure all other folders, so that * after this we know which ones are * unallocated */ ensure_folder_unlocked (info, info->root, TRUE /* subfolders */, folder /* except */, /* avoid infinite loops */ TRUE /* ignore_unallocated */); } for (li = info->entries; li != NULL; li = li->next) { Entry *entry = li->data; if (/* if not file */ entry->type != ENTRY_FILE || /* if already included */ (folder->includes_ht != NULL && g_hash_table_lookup (folder->includes_ht, entry->name) != NULL)) continue; if (folder->only_unallocated && entry->alloc != 0) continue; if (matches_query (info, folder, (EntryFile *)entry, folder->query)) folder->entries = g_slist_prepend (folder->entries, entry_ref_alloc (entry)); } } /* get entries in folder */ /* FIXME: support cancellation here */ static void ensure_folder_unlocked (VFolderInfo *info, Folder *folder, gboolean subfolders, Folder *except, gboolean ignore_unallocated) { if (subfolders) { GSList *li; for (li = folder->subfolders; li != NULL; li = li->next) ensure_folder_unlocked (info, li->data, subfolders, except, ignore_unallocated); } if (except == folder) return; if (ignore_unallocated && folder->only_unallocated) return; if (folder->up_to_date) return; if (folder->entries != NULL) { g_slist_foreach (folder->entries, (GFunc)entry_unref_dealloc, NULL); g_slist_free (folder->entries); folder->entries = NULL; } /* Include includes */ folder->entries = alloc_entries_from_files (info, folder->includes); /* Run query */ append_query (info, folder); /* We were prepending all this time */ folder->entries = g_slist_reverse (folder->entries); /* Include subfolders */ /* we always whack them onto the beginning */ if (folder->subfolders != NULL) { GSList *li; GSList *subfolders; subfolders = NULL; li = folder->subfolders; for (li = folder->subfolders; li != NULL; li = li->next) { Folder *f = li->data; /* always dont_show_if_empty */ if (f->entries != NULL) { entry_ref_alloc (&f->entry); subfolders = g_slist_prepend (subfolders, f); } } subfolders = g_slist_reverse (subfolders); folder->entries = g_slist_concat (subfolders, folder->entries); } /* Exclude excludes */ if (folder->excludes != NULL) { GSList *li; GSList *entries = folder->entries; folder->entries = NULL; for (li = entries; li != NULL; li = li->next) { Entry *entry = li->data; if (g_hash_table_lookup (folder->excludes, entry->name) == NULL) folder->entries = g_slist_prepend (folder->entries, entry); else entry_unref_dealloc (entry); } g_slist_free (entries); /* to preserve the Folders then everything else order */ folder->entries = g_slist_reverse (folder->entries); } folder->up_to_date = TRUE; /* not yet sorted */ folder->sorted = FALSE; } static void ensure_folder (VFolderInfo *info, Folder *folder, gboolean subfolders, Folder *except, gboolean ignore_unallocated) { G_LOCK (vfolder_lock); ensure_folder_unlocked (info, folder, subfolders, except, ignore_unallocated); G_UNLOCK (vfolder_lock); } static char * get_directory_file_unlocked (VFolderInfo *info, Folder *folder) { char *filename; /* FIXME: cache dir_files */ if (folder->desktop_file == NULL) return NULL; if (folder->desktop_file[0] == G_DIR_SEPARATOR) return g_strdup (folder->desktop_file); /* Now try the user directory */ if (info->user_desktop_dir != NULL) { filename = g_build_filename (info->user_desktop_dir, folder->desktop_file, NULL); if (access (filename, F_OK) == 0) { return filename; } g_free (filename); } filename = g_build_filename (info->desktop_dir, folder->desktop_file, NULL); if (access (filename, F_OK) == 0) { return filename; } g_free (filename); return NULL; } static char * get_directory_file (VFolderInfo *info, Folder *folder) { char *ret; G_LOCK (vfolder_lock); ret = get_directory_file_unlocked (info, folder); G_UNLOCK (vfolder_lock); return ret; } static GSList * get_sort_order (VFolderInfo *info, Folder *folder) { GSList *list; char **parsed; char *order; int i; char *filename; filename = get_directory_file_unlocked (info, folder); if (filename == NULL) return NULL; order = NULL; readitem_entry (filename, "SortOrder", &order, NULL, NULL); g_free (filename); if (order == NULL) return NULL; parsed = g_strsplit (order, ":", -1); g_free (order); list = NULL; for (i = 0; parsed[i] != NULL; i++) { char *word = parsed[i]; /* steal */ parsed[i] = NULL; /* ignore empty */ if (word[0] == '\0') { g_free (word); continue; } list = g_slist_prepend (list, word); } /* we've stolen all strings from it */ g_free (parsed); return g_slist_reverse (list); } /* get entries in folder */ static void ensure_folder_sort (VFolderInfo *info, Folder *folder) { GSList *li, *sort_order; GSList *entries; GHashTable *entry_hash; ensure_folder (info, folder, TRUE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); if (folder->sorted) return; G_LOCK (vfolder_lock); sort_order = get_sort_order (info, folder); if (sort_order == NULL) { folder->sorted = TRUE; G_UNLOCK (vfolder_lock); return; } entries = folder->entries; folder->entries = NULL; entry_hash = g_hash_table_new (g_str_hash, g_str_equal); for (li = entries; li != NULL; li = li->next) { Entry *entry = li->data; g_hash_table_insert (entry_hash, entry->name, li); } for (li = sort_order; li != NULL; li = li->next) { char *word = li->data; GSList *entry_list; Entry *entry; /* we kill the words here */ li->data = NULL; entry_list = g_hash_table_lookup (entry_hash, word); g_free (word); if (entry_list == NULL) continue; entry = entry_list->data; entries = g_slist_delete_link (entries, entry_list); folder->entries = g_slist_prepend (folder->entries, entry); } /* put on those that weren't mentioned in the sort */ for (li = entries; li != NULL; li = li->next) { Entry *entry = li->data; folder->entries = g_slist_prepend (folder->entries, entry); } g_hash_table_destroy (entry_hash); g_slist_free (entries); g_slist_free (sort_order); folder->sorted = TRUE; G_UNLOCK (vfolder_lock); } static EntryFile * file_new (const char *name) { EntryFile *efile = g_new0 (EntryFile, 1); efile->entry.type = ENTRY_FILE; efile->entry.name = g_strdup (name); efile->entry.refcount = 1; return efile; } static Folder * folder_new (const char *name) { Folder *folder = g_new0 (Folder, 1); folder->entry.type = ENTRY_FOLDER; folder->entry.name = g_strdup (name); folder->entry.refcount = 1; folder->read_only = TRUE; folder->dont_show_if_empty = TRUE; return folder; } static Query * query_new (int type) { Query *query; if (type == QUERY_KEYWORD) query = (Query *)g_new0 (QueryKeyword, 1); else if (type == QUERY_FILENAME) query = (Query *)g_new0 (QueryFilename, 1); else query = g_new0 (Query, 1); query->type = type; return query; } static void query_destroy (Query *query) { if (query == NULL) return; if (query->type == QUERY_FILENAME) { QueryFilename *qfile = (QueryFilename *)query; g_free (qfile->filename); qfile->filename = NULL; } else if (query->type == QUERY_OR || query->type == QUERY_AND) { g_slist_foreach (query->queries, (GFunc)query_destroy, NULL); g_slist_free (query->queries); query->queries = NULL; } g_free (query); } static void add_folder_monitor_unlocked (VFolderInfo *info, FileMonitorHandle *handle) { VFolderURI vuri; GnomeVFSResult result; Folder *folder; VFOLDER_URI_PARSE (handle->uri, &vuri); file_monitor_handle_ref_unlocked (handle); info->folder_monitors = g_slist_prepend (info->folder_monitors, handle); folder = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, NULL); if (folder == NULL) { file_monitor_handle_ref_unlocked (handle); info->free_folder_monitors = g_slist_prepend (info->free_folder_monitors, handle); if (handle->exists) { handle->exists = FALSE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, handle->uri, GNOME_VFS_MONITOR_EVENT_DELETED); } } else { file_monitor_handle_ref_unlocked (handle); ((Entry *)folder)->monitors = g_slist_prepend (((Entry *)folder)->monitors, handle); if ( ! handle->exists) { handle->exists = TRUE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, handle->uri, GNOME_VFS_MONITOR_EVENT_CREATED); } } } static inline void invalidate_folder_T (Folder *folder) { folder->up_to_date = FALSE; invalidate_folder_subfolders (folder, TRUE); } static inline void invalidate_folder (Folder *folder) { G_LOCK (vfolder_lock); folder->up_to_date = FALSE; G_UNLOCK (vfolder_lock); invalidate_folder_subfolders (folder, FALSE); } static void invalidate_folder_subfolders (Folder *folder, gboolean lock_taken) { GSList *li; for (li = folder->subfolders; li != NULL; li = li->next) { Folder *subfolder = li->data; if (!lock_taken) invalidate_folder (subfolder); else invalidate_folder_T (subfolder); } emit_monitor (folder, GNOME_VFS_MONITOR_EVENT_CHANGED); } /* FIXME: this is UGLY!, we need to figure out when the file * got finished changing! */ static gboolean reread_timeout (gpointer data) { VFolderInfo *info = data; gboolean force_read_items = info->file_monitors != NULL; vfolder_info_reload (info, NULL, NULL, force_read_items); return FALSE; } static void queue_reread_in (VFolderInfo *info, int msec) { G_LOCK (vfolder_lock); if (info->reread_queue != 0) g_source_remove (info->reread_queue); info->reread_queue = g_timeout_add (msec, reread_timeout, info); G_UNLOCK (vfolder_lock); } static void vfolder_desktop_dir_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { /* FIXME: implement */ } static void vfolder_user_desktop_dir_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { /* FIXME: implement */ } static void vfolder_filename_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { VFolderInfo *info = user_data; if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && ! info->user_file_active) { queue_reread_in (info, 200); } else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED && ! info->user_file_active) { /* FIXME: is this correct? I mean now * there probably isn't ANY vfolder file, so we * init to default values really. I have no clue what's * right here */ vfolder_info_reload (info, NULL, NULL, TRUE /* force read items */); } } static void vfolder_user_filename_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { VFolderInfo *info = user_data; if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && info->user_file_active) { struct stat s; /* see if this was really our own change */ if (info->user_filename_last_write == time (NULL)) return; /* anal retentive */ if (stat (info->user_filename, &s) == 0 && info->user_filename_last_write == s.st_ctime) return; queue_reread_in (info, 200); } else if ((event_type == GNOME_VFS_MONITOR_EVENT_CREATED || event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) && ! info->user_file_active) { queue_reread_in (info, 200); } else if (event_type == GNOME_VFS_MONITOR_EVENT_DELETED && info->user_file_active) { gboolean force_read_items = info->file_monitors != NULL; vfolder_info_reload (info, NULL, NULL, force_read_items); } } static void item_dir_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { VFolderInfo *info = user_data; if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED || event_type == GNOME_VFS_MONITOR_EVENT_CHANGED) { /* first invalidate all folders */ invalidate_folder (info->root); /* second invalidate all entries */ info->entries_valid = FALSE; if (info->file_monitors != NULL) { GnomeVFSResult result; GSList *li; /* Whack all monitors here! */ for (li = info->file_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h->handle != NULL) gnome_vfs_monitor_cancel (h->handle); h->handle = NULL; } if (vfolder_info_read_items (info, &result, NULL)) { info->entries_valid = TRUE; } } } } static gboolean setup_dir_monitor (VFolderInfo *info, const char *dir, gboolean subdirs, GnomeVFSResult *result, GnomeVFSContext *context) { GnomeVFSMonitorHandle *handle; DIR *dh; struct dirent *de; char *uri; uri = gnome_vfs_get_uri_from_local_path (dir); if (gnome_vfs_monitor_add (&handle, uri, GNOME_VFS_MONITOR_DIRECTORY, item_dir_monitor, info) != GNOME_VFS_OK) { StatLoc *sl = bake_statloc (dir, time (NULL)); if (sl != NULL) info->stat_dirs = g_slist_prepend (info->stat_dirs, sl); g_free (uri); return TRUE; } g_free (uri); if (gnome_vfs_context_check_cancellation (context)) { gnome_vfs_monitor_cancel (handle); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } info->item_dir_monitors = g_slist_prepend (info->item_dir_monitors, handle); if ( ! subdirs) return TRUE; dh = opendir (dir); if (dh == NULL) return TRUE; while ((de = readdir (dh)) != NULL) { char *full_path; if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; closedir (dh); return FALSE; } if (de->d_name[0] == '.') continue; full_path = g_build_filename (dir, de->d_name, NULL); if (g_file_test (full_path, G_FILE_TEST_IS_DIR)) { if ( ! setup_dir_monitor (info, full_path, TRUE /* subdirs */, result, context)) { closedir (dh); return FALSE; } } g_free (full_path); } closedir (dh); return TRUE; } static gboolean monitor_setup (VFolderInfo *info, gboolean setup_filenames, gboolean setup_itemdirs, gboolean setup_desktop_dirs, GnomeVFSResult *result, GnomeVFSContext *context) { char *uri; GSList *li; if (setup_filenames) { uri = gnome_vfs_get_uri_from_local_path (info->filename); if (gnome_vfs_monitor_add (&info->filename_monitor, uri, GNOME_VFS_MONITOR_FILE, vfolder_filename_monitor, info) != GNOME_VFS_OK) { info->filename_monitor = NULL; info->filename_statloc = bake_statloc (info->filename, time (NULL)); } g_free (uri); } if (setup_filenames && info->user_filename != NULL) { uri = gnome_vfs_get_uri_from_local_path (info->user_filename); if (gnome_vfs_monitor_add (&info->user_filename_monitor, uri, GNOME_VFS_MONITOR_FILE, vfolder_user_filename_monitor, info) != GNOME_VFS_OK) { info->user_filename_monitor = NULL; info->user_filename_statloc = bake_statloc (info->user_filename, time (NULL)); } g_free (uri); } if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } if (setup_itemdirs) { for (li = info->item_dirs; li != NULL; li = li->next) { const char *dir = li->data; if ( ! setup_dir_monitor (info, dir, FALSE /* subdirs */, result, context)) return FALSE; } if (info->user_item_dir != NULL) { if ( ! setup_dir_monitor (info, info->user_item_dir, FALSE /* subdirs */, result, context)) return FALSE; } for (li = info->merge_dirs; li != NULL; li = li->next) { const char *dir = li->data; if ( ! setup_dir_monitor (info, dir, TRUE /* subdirs */, result, context)) return FALSE; } } if (setup_desktop_dirs) { uri = gnome_vfs_get_uri_from_local_path (info->desktop_dir); if (gnome_vfs_monitor_add (&info->desktop_dir_monitor, uri, GNOME_VFS_MONITOR_FILE, vfolder_desktop_dir_monitor, info) != GNOME_VFS_OK) { info->desktop_dir_monitor = NULL; info->desktop_dir_statloc = bake_statloc (info->desktop_dir, time (NULL)); } g_free (uri); } if (setup_desktop_dirs && info->user_desktop_dir != NULL) { uri = gnome_vfs_get_uri_from_local_path (info->user_desktop_dir); if (gnome_vfs_monitor_add (&info->user_desktop_dir_monitor, uri, GNOME_VFS_MONITOR_DIRECTORY, vfolder_user_desktop_dir_monitor, info) != GNOME_VFS_OK) { info->user_desktop_dir_monitor = NULL; info->user_desktop_dir_statloc = bake_statloc (info->user_desktop_dir, time (NULL)); } g_free (uri); } return TRUE; } static void vfolder_info_init (VFolderInfo *info, const char *scheme) { const char *path; GSList *list; info->scheme = g_strdup (scheme); info->filename = g_strconcat (SYSCONFDIR, "/X11/desktop-menus/", scheme, ".menu", NULL); info->user_filename = g_strconcat (g_get_home_dir (), "/" DOT_GNOME "/vfolders/", scheme, ".vfolder-info", NULL); info->desktop_dir = g_strconcat (SYSCONFDIR, "/gnome-vfs-2.0/vfolders/", NULL); info->user_desktop_dir = g_strconcat (g_get_home_dir (), "/" DOT_GNOME "/vfolders/", NULL); /* Init the desktop paths */ list = NULL; list = g_slist_prepend (list, g_strdup ("/usr/share/applications/")); if (strcmp ("/usr/share/applications/", DATADIR "/applications/") != 0) list = g_slist_prepend (list, g_strdup (DATADIR "/applications/")); path = g_getenv ("DESKTOP_FILE_PATH"); if (path != NULL) { int i; char **ppath = g_strsplit (path, ":", -1); for (i = 0; ppath[i] != NULL; i++) { const char *dir = ppath[i]; list = g_slist_prepend (list, g_strdup (dir)); } g_strfreev (ppath); } info->item_dirs = g_slist_reverse (list); info->user_item_dir = g_strconcat (g_get_home_dir (), "/" DOT_GNOME "/vfolders/", scheme, NULL); info->entries_ht = g_hash_table_new (g_str_hash, g_str_equal); info->root = folder_new ("Root"); info->modification_time = time (NULL); } static void vfolder_info_free_internals_unlocked (VFolderInfo *info) { if (info == NULL) return; if (info->filename_monitor != NULL) { gnome_vfs_monitor_cancel (info->filename_monitor); info->filename_monitor = NULL; } if (info->user_filename_monitor != NULL) { gnome_vfs_monitor_cancel (info->user_filename_monitor); info->user_filename_monitor = NULL; } g_free (info->filename_statloc); info->filename_statloc = NULL; g_free (info->user_filename_statloc); info->user_filename_statloc = NULL; if (info->desktop_dir_monitor != NULL) { gnome_vfs_monitor_cancel (info->desktop_dir_monitor); info->desktop_dir_monitor = NULL; } if (info->user_desktop_dir_monitor != NULL) { gnome_vfs_monitor_cancel (info->user_desktop_dir_monitor); info->user_desktop_dir_monitor = NULL; } g_free (info->desktop_dir_statloc); info->desktop_dir_statloc = NULL; g_free (info->user_desktop_dir_statloc); info->user_desktop_dir_statloc = NULL; g_slist_foreach (info->item_dir_monitors, (GFunc)gnome_vfs_monitor_cancel, NULL); g_slist_free (info->item_dir_monitors); info->item_dir_monitors = NULL; g_free (info->scheme); info->scheme = NULL; g_free (info->filename); info->filename = NULL; g_free (info->user_filename); info->user_filename = NULL; g_free (info->desktop_dir); info->desktop_dir = NULL; g_free (info->user_desktop_dir); info->user_desktop_dir = NULL; g_slist_foreach (info->item_dirs, (GFunc)g_free, NULL); g_slist_free (info->item_dirs); info->item_dirs = NULL; g_free (info->user_item_dir); info->user_item_dir = NULL; g_slist_foreach (info->merge_dirs, (GFunc)g_free, NULL); g_slist_free (info->merge_dirs); info->merge_dirs = NULL; g_slist_foreach (info->entries, (GFunc)entry_unref, NULL); g_slist_free (info->entries); info->entries = NULL; if (info->entries_ht != NULL) g_hash_table_destroy (info->entries_ht); info->entries_ht = NULL; g_slist_foreach (info->unallocated_folders, (GFunc)entry_unref, NULL); g_slist_free (info->unallocated_folders); info->unallocated_folders = NULL; entry_unref ((Entry *)info->root); info->root = NULL; g_slist_foreach (info->stat_dirs, (GFunc)g_free, NULL); g_slist_free (info->stat_dirs); info->stat_dirs = NULL; g_slist_foreach (info->folder_monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (info->folder_monitors); info->folder_monitors = NULL; g_slist_foreach (info->free_folder_monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (info->free_folder_monitors); info->free_folder_monitors = NULL; g_slist_foreach (info->file_monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (info->file_monitors); info->file_monitors = NULL; g_slist_foreach (info->free_file_monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (info->free_file_monitors); info->free_file_monitors = NULL; if (info->reread_queue != 0) g_source_remove (info->reread_queue); info->reread_queue = 0; } static void vfolder_info_free_internals (VFolderInfo *info) { G_LOCK (vfolder_lock); vfolder_info_free_internals_unlocked (info); G_UNLOCK (vfolder_lock); } static void vfolder_info_destroy (VFolderInfo *info) { vfolder_info_free_internals (info); g_free (info); } static Query * single_query_read (xmlNode *qnode) { Query *query; xmlNode *node; if (qnode->type != XML_ELEMENT_NODE || qnode->name == NULL) return NULL; query = NULL; if (g_ascii_strcasecmp (qnode->name, "Not") == 0 && qnode->xmlChildrenNode != NULL) { xmlNode *iter; query = NULL; for (iter = qnode->xmlChildrenNode; iter != NULL && query == NULL; iter = iter->next) query = single_query_read (iter); if (query != NULL) { query->not = ! query->not; } return query; } else if (g_ascii_strcasecmp (qnode->name, "Keyword") == 0) { xmlChar *word = xmlNodeGetContent (qnode); if (word != NULL) { query = query_new (QUERY_KEYWORD); ((QueryKeyword *)query)->keyword = g_quark_from_string (word); xmlFree (word); } return query; } else if (g_ascii_strcasecmp (qnode->name, "Filename") == 0) { xmlChar *file = xmlNodeGetContent (qnode); if (file != NULL) { query = query_new (QUERY_FILENAME); ((QueryFilename *)query)->filename = g_strdup (file); xmlFree (file); } return query; } else if (g_ascii_strcasecmp (qnode->name, "And") == 0) { query = query_new (QUERY_AND); } else if (g_ascii_strcasecmp (qnode->name, "Or") == 0) { query = query_new (QUERY_OR); } else { /* We don't understand */ return NULL; } /* This must be OR or AND */ g_assert (query != NULL); for (node = qnode->xmlChildrenNode; node != NULL; node = node->next) { Query *new_query = single_query_read (node); if (new_query != NULL) query->queries = g_slist_prepend (query->queries, new_query); } query->queries = g_slist_reverse (query->queries); return query; } static void add_or_set_query (Query **query, Query *new_query) { if (*query == NULL) { *query = new_query; } else { Query *old_query = *query; *query = query_new (QUERY_OR); (*query)->queries = g_slist_append ((*query)->queries, old_query); (*query)->queries = g_slist_append ((*query)->queries, new_query); } } static Query * query_read (xmlNode *qnode) { Query *query; xmlNode *node; query = NULL; for (node = qnode->xmlChildrenNode; node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE || node->name == NULL) continue; if (g_ascii_strcasecmp (node->name, "Not") == 0 && node->xmlChildrenNode != NULL) { xmlNode *iter; Query *new_query = NULL; for (iter = node->xmlChildrenNode; iter != NULL && new_query == NULL; iter = iter->next) new_query = single_query_read (iter); if (new_query != NULL) { new_query->not = ! new_query->not; add_or_set_query (&query, new_query); } } else { Query *new_query = single_query_read (node); if (new_query != NULL) add_or_set_query (&query, new_query); } } return query; } static Folder * folder_read (VFolderInfo *info, xmlNode *fnode) { Folder *folder; xmlNode *node; folder = folder_new (NULL); for (node = fnode->xmlChildrenNode; node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE || node->name == NULL) continue; if (g_ascii_strcasecmp (node->name, "Name") == 0) { xmlChar *name = xmlNodeGetContent (node); if (name != NULL) { g_free (folder->entry.name); folder->entry.name = g_strdup (name); xmlFree (name); } } else if (g_ascii_strcasecmp (node->name, "Desktop") == 0) { xmlChar *desktop = xmlNodeGetContent (node); if (desktop != NULL) { g_free (folder->desktop_file); folder->desktop_file = g_strdup (desktop); xmlFree (desktop); } } else if (g_ascii_strcasecmp (node->name, "Include") == 0) { xmlChar *file = xmlNodeGetContent (node); if (file != NULL) { GSList *li; char *str = g_strdup (file); folder->includes = g_slist_prepend (folder->includes, str); if (folder->includes_ht == NULL) { folder->includes_ht = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); } li = g_hash_table_lookup (folder->includes_ht, file); if (li != NULL) { g_free (li->data); /* Note: this will NOT change folder->includes * pointer! */ folder->includes = g_slist_delete_link (folder->includes, li); } g_hash_table_replace (folder->includes_ht, file, folder->includes); xmlFree (file); } } else if (g_ascii_strcasecmp (node->name, "Exclude") == 0) { xmlChar *file = xmlNodeGetContent (node); if (file != NULL) { char *s; if (folder->excludes == NULL) { folder->excludes = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); } s = g_strdup (file); g_hash_table_replace (folder->excludes, s, s); xmlFree (file); } } else if (g_ascii_strcasecmp (node->name, "Query") == 0) { Query *query; query = query_read (node); if (query != NULL) { if (folder->query != NULL) query_destroy (folder->query); folder->query = query; } } else if (g_ascii_strcasecmp (node->name, "OnlyUnallocated") == 0) { info->unallocated_folders = g_slist_prepend (info->unallocated_folders, (Folder *)entry_ref ((Entry *)folder)); folder->only_unallocated = TRUE; } else if (g_ascii_strcasecmp (node->name, "Folder") == 0) { Folder *new_folder = folder_read (info, node); if (new_folder != NULL) { folder->subfolders = g_slist_append (folder->subfolders, new_folder); new_folder->parent = folder; } } else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) { folder->read_only = TRUE; } else if (g_ascii_strcasecmp (node->name, "DontShowIfEmpty") == 0) { folder->dont_show_if_empty = TRUE; } } /* Name is required */ if (folder->entry.name == NULL) { entry_unref ((Entry *)folder); folder = NULL; } folder->includes = g_slist_reverse (folder->includes); return folder; } static char * subst_home (const char *dir) { if (dir[0] == '~') return g_strconcat (g_get_home_dir (), &dir[1], NULL); else return g_strdup (dir); } /* FORMAT looks like: * * * /etc/X11/applnk * * /usr/share/applications * * /etc/X11/gnome/vfolders * * * Root * * important.desktop * * * * SomeFolder * * * Test_Folder * * Test_Folder.directory * * * * Application * Game * * Clock * * * somefile.desktop * someotherfile.desktop * yetanother.desktop * * * */ static gboolean vfolder_info_read_info (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context) { xmlDoc *doc; xmlNode *node; gboolean got_a_vfolder_dir = FALSE; doc = NULL; if (info->user_filename != NULL && access (info->user_filename, F_OK) == 0) { doc = xmlParseFile (info->user_filename); if (doc != NULL) info->user_file_active = TRUE; } if (doc == NULL && access (info->filename, F_OK) == 0) doc = xmlParseFile (info->filename); if (gnome_vfs_context_check_cancellation (context)) { xmlFreeDoc(doc); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } if (doc == NULL || doc->xmlRootNode == NULL || doc->xmlRootNode->name == NULL || g_ascii_strcasecmp (doc->xmlRootNode->name, "VFolderInfo") != 0) { xmlFreeDoc(doc); return TRUE; /* FIXME: really, shouldn't we error out? */ } for (node = doc->xmlRootNode->xmlChildrenNode; node != NULL; node = node->next) { if (node->type != XML_ELEMENT_NODE || node->name == NULL) continue; if (gnome_vfs_context_check_cancellation (context)) { xmlFreeDoc(doc); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } if (g_ascii_strcasecmp (node->name, "MergeDir") == 0) { xmlChar *dir = xmlNodeGetContent (node); if (dir != NULL) { info->merge_dirs = g_slist_append (info->merge_dirs, g_strdup (dir)); xmlFree (dir); } } else if (g_ascii_strcasecmp (node->name, "ItemDir") == 0) { xmlChar *dir = xmlNodeGetContent (node); if (dir != NULL) { if ( ! got_a_vfolder_dir) { g_slist_foreach (info->item_dirs, (GFunc)g_free, NULL); g_slist_free (info->item_dirs); info->item_dirs = NULL; } got_a_vfolder_dir = TRUE; info->item_dirs = g_slist_append (info->item_dirs, g_strdup (dir)); xmlFree (dir); } } else if (g_ascii_strcasecmp (node->name, "UserItemDir") == 0) { xmlChar *dir = xmlNodeGetContent (node); if (dir != NULL) { g_free (info->user_item_dir); info->user_item_dir = subst_home (dir); xmlFree (dir); } } else if (g_ascii_strcasecmp (node->name, "DesktopDir") == 0) { xmlChar *dir = xmlNodeGetContent (node); if (dir != NULL) { g_free (info->desktop_dir); info->desktop_dir = g_strdup (dir); xmlFree (dir); } } else if (g_ascii_strcasecmp (node->name, "UserDesktopDir") == 0) { xmlChar *dir = xmlNodeGetContent (node); if (dir != NULL) { g_free (info->user_desktop_dir); info->user_desktop_dir = subst_home (dir); xmlFree (dir); } } else if (g_ascii_strcasecmp (node->name, "Folder") == 0) { Folder *folder = folder_read (info, node); if (folder != NULL) { if (info->root != NULL) entry_unref ((Entry *)info->root); info->root = folder; } } else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) { info->read_only = TRUE; } } xmlFreeDoc(doc); return TRUE; } static void add_xml_tree_from_query (xmlNode *parent, Query *query) { xmlNode *real_parent; if (query->not) real_parent = xmlNewChild (parent /* parent */, NULL /* ns */, "Not" /* name */, NULL /* content */); else real_parent = parent; if (query->type == QUERY_KEYWORD) { QueryKeyword *qkeyword = (QueryKeyword *)query; const char *string = g_quark_to_string (qkeyword->keyword); xmlNewChild (real_parent /* parent */, NULL /* ns */, "Keyword" /* name */, string /* content */); } else if (query->type == QUERY_FILENAME) { QueryFilename *qfilename = (QueryFilename *)query; xmlNewChild (real_parent /* parent */, NULL /* ns */, "Filename" /* name */, qfilename->filename /* content */); } else if (query->type == QUERY_OR || query->type == QUERY_AND) { xmlNode *node; const char *name; GSList *li; if (query->type == QUERY_OR) name = "Or"; else /* QUERY_AND */ name = "And"; node = xmlNewChild (real_parent /* parent */, NULL /* ns */, name /* name */, NULL /* content */); for (li = query->queries; li != NULL; li = li->next) { Query *subquery = li->data; add_xml_tree_from_query (node, subquery); } } else { g_assert_not_reached (); } } static void add_excludes_to_xml (gpointer key, gpointer value, gpointer user_data) { const char *filename = key; xmlNode *folder_node = user_data; xmlNewChild (folder_node /* parent */, NULL /* ns */, "Exclude" /* name */, filename /* content */); } static void add_xml_tree_from_folder (xmlNode *parent, Folder *folder) { GSList *li; xmlNode *folder_node; folder_node = xmlNewChild (parent /* parent */, NULL /* ns */, "Folder" /* name */, NULL /* content */); xmlNewChild (folder_node /* parent */, NULL /* ns */, "Name" /* name */, folder->entry.name /* content */); if (folder->desktop_file != NULL) { xmlNewChild (folder_node /* parent */, NULL /* ns */, "Desktop" /* name */, folder->desktop_file /* content */); } if (folder->read_only) xmlNewChild (folder_node /* parent */, NULL /* ns */, "ReadOnly" /* name */, NULL /* content */); if (folder->dont_show_if_empty) xmlNewChild (folder_node /* parent */, NULL /* ns */, "DontShowIfEmpty" /* name */, NULL /* content */); if (folder->only_unallocated) xmlNewChild (folder_node /* parent */, NULL /* ns */, "OnlyUnallocated" /* name */, NULL /* content */); for (li = folder->subfolders; li != NULL; li = li->next) { Folder *subfolder = li->data; add_xml_tree_from_folder (folder_node, subfolder); } for (li = folder->includes; li != NULL; li = li->next) { const char *include = li->data; xmlNewChild (folder_node /* parent */, NULL /* ns */, "Include" /* name */, include /* content */); } if (folder->excludes) { g_hash_table_foreach (folder->excludes, add_excludes_to_xml, folder_node); } if (folder->query != NULL) { xmlNode *query_node; query_node = xmlNewChild (folder_node /* parent */, NULL /* ns */, "Query" /* name */, NULL /* content */); add_xml_tree_from_query (query_node, folder->query); } } static xmlDoc * xml_tree_from_vfolder (VFolderInfo *info) { xmlDoc *doc; xmlNode *topnode; GSList *li; doc = xmlNewDoc ("1.0"); topnode = xmlNewDocNode (doc /* doc */, NULL /* ns */, "VFolderInfo" /* name */, NULL /* content */); doc->xmlRootNode = topnode; for (li = info->merge_dirs; li != NULL; li = li->next) { const char *merge_dir = li->data; xmlNewChild (topnode /* parent */, NULL /* ns */, "MergeDir" /* name */, merge_dir /* content */); } for (li = info->item_dirs; li != NULL; li = li->next) { const char *item_dir = li->data; xmlNewChild (topnode /* parent */, NULL /* ns */, "ItemDir" /* name */, item_dir /* content */); } if (info->user_item_dir != NULL) { xmlNewChild (topnode /* parent */, NULL /* ns */, "UserItemDir" /* name */, info->user_item_dir /* content */); } if (info->desktop_dir != NULL) { xmlNewChild (topnode /* parent */, NULL /* ns */, "DesktopDir" /* name */, info->desktop_dir /* content */); } if (info->user_desktop_dir != NULL) { xmlNewChild (topnode /* parent */, NULL /* ns */, "UserDesktopDir" /* name */, info->user_desktop_dir /* content */); } if (info->root != NULL) add_xml_tree_from_folder (topnode, info->root); return doc; } /* FIXME: what to do about errors */ static void vfolder_info_write_user (VFolderInfo *info) { xmlDoc *doc; if (info->inhibit_write > 0) return; if (info->user_filename == NULL) return; doc = xml_tree_from_vfolder (info); if (doc == NULL) return; /* FIXME: errors, anyone? */ ensure_dir (info->user_filename, TRUE /* ignore_basename */); xmlSaveFormatFile (info->user_filename, doc, TRUE /* format */); /* not as good as a stat, but cheaper ... hmmm what is * the likelyhood of this not being the same as ctime */ info->user_filename_last_write = time (NULL); xmlFreeDoc(doc); info->user_file_active = TRUE; info->dirty = FALSE; info->modification_time = time (NULL); } /* An EVIL function for quick reading of .desktop files, * only reads in one or two keys, but that's ALL we need */ static void readitem_entry (const char *filename, const char *key1, char **result1, const char *key2, char **result2) { FILE *fp; char buf[1024]; int keylen1, keylen2; *result1 = NULL; if (result2 != NULL) *result2 = NULL; fp = fopen (filename, "r"); if (fp == NULL) return; keylen1 = strlen (key1); if (key2 != NULL) keylen2 = strlen (key2); else keylen2 = -1; /* This is slightly wrong, it should only look * at the correct section */ while (fgets (buf, sizeof (buf), fp) != NULL) { char *p; int len; int keylen; char **result = NULL; /* check if it's one of the keys */ if (strncmp (buf, key1, keylen1) == 0) { result = result1; keylen = keylen1; } else if (keylen2 >= 0 && strncmp (buf, key2, keylen2) == 0) { result = result2; keylen = keylen2; } else { continue; } p = &buf[keylen]; /* still not our key */ if (!(*p == '=' || *p == ' ')) { continue; } do p++; while (*p == ' ' || *p == '='); /* get rid of trailing \n */ len = strlen (p); if (p[len-1] == '\n' || p[len-1] == '\r') p[len-1] = '\0'; *result = g_strdup (p); if (*result1 != NULL && (result2 == NULL || *result2 != NULL)) break; } fclose (fp); } static void vfolder_info_insert_entry (VFolderInfo *info, EntryFile *efile) { GSList *entry_list; entry_ref ((Entry *)efile); entry_list = g_hash_table_lookup (info->entries_ht, efile->entry.name); info->entries = g_slist_prepend (info->entries, efile); /* The hash table contains the GSList pointer */ g_hash_table_replace (info->entries_ht, efile->entry.name, info->entries); if (entry_list != NULL) { Entry *entry = entry_list->data; info->entries = g_slist_delete_link (info->entries, entry_list); entry_unref (entry); } } static void set_keywords (EntryFile *efile, const char *keywords) { if (keywords != NULL) { int i; char **parsed = g_strsplit (keywords, ";", -1); for (i = 0; parsed[i] != NULL; i++) { GQuark quark; const char *word = parsed[i]; /* ignore empties (including end of list) */ if (word[0] == '\0') continue; quark = g_quark_from_string (word); efile->keywords = g_slist_prepend (efile->keywords, GINT_TO_POINTER (quark)); } g_strfreev (parsed); } } static EntryFile * make_entry_file (const char *dir, const char *name) { EntryFile *efile; char *categories; char *only_show_in; char *filename; int i; filename = g_build_filename (dir, name, NULL); readitem_entry (filename, "Categories", &categories, "OnlyShowIn", &only_show_in); if (only_show_in != NULL) { gboolean show = FALSE; char **parsed = g_strsplit (only_show_in, ";", -1); for (i = 0; parsed[i] != NULL; i++) { if (strcmp (parsed[i], "GNOME") == 0) { show = TRUE; break; } } g_strfreev (parsed); if ( ! show) { g_free (filename); g_free (only_show_in); g_free (categories); return NULL; } } efile = file_new (name); efile->filename = filename; set_keywords (efile, categories); g_free (only_show_in); g_free (categories); return efile; } static gboolean vfolder_info_read_items_from (VFolderInfo *info, const char *item_dir, gboolean per_user, GnomeVFSResult *result, GnomeVFSContext *context) { DIR *dir; struct dirent *de; dir = opendir (item_dir); if (dir == NULL) return TRUE; while ((de = readdir (dir)) != NULL) { EntryFile *efile; if (gnome_vfs_context_check_cancellation (context)) { closedir (dir); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } /* files MUST be called .desktop */ if (de->d_name[0] == '.' || ! check_ext (de->d_name, ".desktop")) continue; efile = make_entry_file (item_dir, de->d_name); if (efile == NULL) continue; efile->per_user = per_user; vfolder_info_insert_entry (info, efile); entry_unref ((Entry *)efile); } closedir (dir); return TRUE; } static gboolean vfolder_info_read_items_merge (VFolderInfo *info, const char *merge_dir, const char *subdir, GQuark inherited_keyword, GnomeVFSResult *result, GnomeVFSContext *context) { DIR *dir; struct dirent *de; GQuark extra_keyword; GQuark Application; GQuark Merged; GQuark inheritance; gboolean pass_down_extra_keyword = TRUE; dir = opendir (merge_dir); if (dir == NULL) return TRUE; Application = g_quark_from_static_string ("Application"); Merged = g_quark_from_static_string ("Merged"); /* FIXME: this should be a hash or something */ extra_keyword = 0; if (subdir == NULL) { extra_keyword = g_quark_from_static_string ("Core"); pass_down_extra_keyword = FALSE; } else if (g_ascii_strcasecmp (subdir, "Development") == 0) extra_keyword = g_quark_from_static_string ("Development"); else if (g_ascii_strcasecmp (subdir, "Editors") == 0) extra_keyword = g_quark_from_static_string ("TextEditor"); else if (g_ascii_strcasecmp (subdir, "Games") == 0) extra_keyword = g_quark_from_static_string ("Game"); else if (g_ascii_strcasecmp (subdir, "Graphics") == 0) extra_keyword = g_quark_from_static_string ("Graphics"); else if (g_ascii_strcasecmp (subdir, "Internet") == 0) extra_keyword = g_quark_from_static_string ("Network"); else if (g_ascii_strcasecmp (subdir, "Multimedia") == 0) extra_keyword = g_quark_from_static_string ("AudioVideo"); else if (g_ascii_strcasecmp (subdir, "Office") == 0) extra_keyword = g_quark_from_static_string ("Office"); else if (g_ascii_strcasecmp (subdir, "Settings") == 0) extra_keyword = g_quark_from_static_string ("Settings"); else if (g_ascii_strcasecmp (subdir, "System") == 0) extra_keyword = g_quark_from_static_string ("System"); else if (g_ascii_strcasecmp (subdir, "Utilities") == 0) extra_keyword = g_quark_from_static_string ("Utility"); while ((de = readdir (dir)) != NULL) { EntryFile *efile; if (gnome_vfs_context_check_cancellation (context)) { closedir (dir); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } /* ignore hidden */ if (de->d_name[0] == '.') continue; /* files MUST be called .desktop, so * treat all others as dirs. If we're wrong, * the open will fail, which is ok */ if ( ! check_ext (de->d_name, ".desktop")) { /* if this is a directory recurse */ char *fullname = g_build_filename (merge_dir, de->d_name, NULL); if ((pass_down_extra_keyword == TRUE) && (extra_keyword != 0)) { inheritance = extra_keyword; } else { inheritance = inherited_keyword; } if ( ! vfolder_info_read_items_merge (info, fullname, de->d_name, inheritance, result, context)) { g_free (fullname); return FALSE; } g_free (fullname); continue; } /* FIXME: add some keywords about some known apps * like gimp and whatnot, perhaps take these from the vfolder * file or some such */ efile = make_entry_file (merge_dir, de->d_name); if (efile == NULL) continue; /* If no keywords set, then add the standard ones */ if (efile->keywords == NULL) { efile->keywords = g_slist_prepend (efile->keywords, GINT_TO_POINTER (Application)); efile->keywords = g_slist_prepend (efile->keywords, GINT_TO_POINTER (Merged)); if (inherited_keyword != 0) { efile->keywords = g_slist_prepend (efile->keywords, GINT_TO_POINTER (inherited_keyword)); } if (extra_keyword != 0) { efile->keywords = g_slist_prepend (efile->keywords, GINT_TO_POINTER (extra_keyword)); } efile->implicit_keywords = TRUE; } vfolder_info_insert_entry (info, efile); entry_unref ((Entry *)efile); } closedir (dir); return TRUE; } static Entry * find_entry (GSList *list, const char *name) { GSList *li; for (li = list; li != NULL; li = li->next) { Entry *entry = li->data; if (strcmp (name, entry->name) == 0) return entry; } return NULL; } static void file_monitor (GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType event_type, gpointer user_data) { FileMonitorHandle *h = user_data; /* proxy the event through if it is a changed event * only */ if (event_type == GNOME_VFS_MONITOR_EVENT_CHANGED && h->handle != NULL) gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *) h, h->uri, event_type); } static void try_free_file_monitors_create_files_unlocked (VFolderInfo *info) { GSList *li, *list; list = g_slist_copy (info->free_file_monitors); for (li = list; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; Entry *entry; GnomeVFSResult result; char *dirfile = NULL; if (handle->is_directory_file) { VFolderURI vuri; Folder *folder; /* Evil! EVIL URI PARSING. this will eat a lot of * stack if we have lots of free monitors */ VFOLDER_URI_PARSE (handle->uri, &vuri); folder = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (folder == NULL) continue; dirfile = get_directory_file_unlocked (info, folder); if (dirfile == NULL) continue; entry = (Entry *)folder; } else { VFolderURI vuri; Folder *f; GnomeVFSResult result; entry = NULL; /* Evil! EVIL URI PARSING. this will eat a lot of * stack if we have lots of monitors */ VFOLDER_URI_PARSE (handle->uri, &vuri); f = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (f != NULL) { ensure_folder_unlocked ( info, f, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); entry = find_entry (f->entries, vuri.file); } if (entry == NULL) continue; } info->free_file_monitors = g_slist_remove (info->free_file_monitors, handle); entry->monitors = g_slist_prepend (entry->monitors, handle); handle->exists = TRUE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, handle->uri, GNOME_VFS_MONITOR_EVENT_CREATED); /* recreate a handle */ if (handle->handle == NULL && entry->type == ENTRY_FILE) { EntryFile *efile = (EntryFile *)entry; char *uri = gnome_vfs_get_uri_from_local_path (efile->filename); gnome_vfs_monitor_add (&(handle->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, handle); g_free (uri); } else if (handle->handle == NULL && dirfile != NULL) { char *uri = gnome_vfs_get_uri_from_local_path (dirfile); gnome_vfs_monitor_add (&(handle->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, handle); g_free (uri); } g_free (dirfile); } g_slist_free (list); } static void /* unlocked */ rescan_monitors (VFolderInfo *info) { GSList *li; if (info->file_monitors == NULL) return; for (li = info->file_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; GnomeVFSResult result; Entry *entry; char *dirfile = NULL; /* these are handled below */ if ( ! h->exists) continue; if (h->is_directory_file) { VFolderURI vuri; Folder *folder; /* Evil! EVIL URI PARSING. this will eat a lot of * stack if we have lots of monitors */ VFOLDER_URI_PARSE (h->uri, &vuri); folder = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (folder != NULL) dirfile = get_directory_file_unlocked (info, folder); if (dirfile == NULL) { h->exists = FALSE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)h, h->uri, GNOME_VFS_MONITOR_EVENT_DELETED); info->free_file_monitors = g_slist_prepend (info->free_file_monitors, h); file_monitor_handle_ref_unlocked (h); /* it has been unreffed when the entry was * whacked */ continue; } entry = (Entry *)folder; } else { VFolderURI vuri; Folder *f; GnomeVFSResult result; entry = NULL; /* Evil! EVIL URI PARSING. this will eat a lot of * stack if we have lots of monitors */ VFOLDER_URI_PARSE (h->uri, &vuri); f = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (f != NULL) { ensure_folder_unlocked ( info, f, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); entry = find_entry (f->entries, vuri.file); } if (entry == NULL) { h->exists = FALSE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)h, h->uri, GNOME_VFS_MONITOR_EVENT_DELETED); info->free_file_monitors = g_slist_prepend (info->free_file_monitors, h); file_monitor_handle_ref_unlocked (h); /* it has been unreffed when the entry was * whacked */ continue; } } /* recreate a handle */ if (h->handle == NULL && entry->type == ENTRY_FILE) { EntryFile *efile = (EntryFile *)entry; char *uri = gnome_vfs_get_uri_from_local_path (efile->filename); gnome_vfs_monitor_add (&(h->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, h); g_free (uri); } else if (h->handle == NULL && dirfile != NULL) { char *uri = gnome_vfs_get_uri_from_local_path (dirfile); gnome_vfs_monitor_add (&(h->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, h); g_free (uri); } g_free (dirfile); } try_free_file_monitors_create_files_unlocked (info); } static gboolean /* unlocked */ vfolder_info_read_items (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context) { GSList *li; /* First merge */ for (li = info->merge_dirs; li != NULL; li = li->next) { const char *merge_dir = li->data; if ( ! vfolder_info_read_items_merge (info, merge_dir, NULL, FALSE, result, context)) return FALSE; } /* Then read the real thing (later overrides) */ for (li = info->item_dirs; li != NULL; li = li->next) { const char *item_dir = li->data; if ( ! vfolder_info_read_items_from (info, item_dir, FALSE /* per_user */, result, context)) return FALSE; } if (info->user_item_dir != NULL) { if ( ! vfolder_info_read_items_from (info, info->user_item_dir, TRUE /* per_user */, result, context)) return FALSE; } rescan_monitors (info); return TRUE; } static gboolean string_slist_equal (GSList *list1, GSList *list2) { GSList *li1, *li2; for (li1 = list1, li2 = list2; li1 != NULL && li2 != NULL; li1 = li1->next, li2 = li2->next) { const char *s1 = li1->data; const char *s2 = li2->data; if (strcmp (s1, s2) != 0) return FALSE; } /* if both are not NULL, then lengths are * different */ if (li1 != li2) return FALSE; return TRUE; } static gboolean safe_string_same (const char *string1, const char *string2) { if (string1 == string2 && string1 == NULL) return TRUE; if (string1 != NULL && string2 != NULL && strcmp (string1, string2) == 0) return TRUE; return FALSE; } static gboolean vfolder_info_item_dirs_same (VFolderInfo *info1, VFolderInfo *info2) { if ( ! string_slist_equal (info1->item_dirs, info2->item_dirs)) return FALSE; if ( ! string_slist_equal (info1->merge_dirs, info2->merge_dirs)) return FALSE; if ( ! safe_string_same (info1->user_item_dir, info2->user_item_dir)) return FALSE; return TRUE; } static gboolean vfolder_info_reload_unlocked (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context, gboolean force_read_items) { VFolderInfo *newinfo; gboolean setup_filenames; gboolean setup_itemdirs; GSList *li; /* FIXME: Hmmm, race, there is no locking YAIKES, * we need filename locking for changes. eek, eek, eek */ if (info->dirty) { return TRUE; } newinfo = g_new0 (VFolderInfo, 1); vfolder_info_init (newinfo, info->scheme); g_free (newinfo->filename); g_free (newinfo->user_filename); newinfo->filename = g_strdup (info->filename); newinfo->user_filename = g_strdup (info->user_filename); if (gnome_vfs_context_check_cancellation (context)) { vfolder_info_destroy (newinfo); *result = GNOME_VFS_ERROR_CANCELLED; return FALSE; } if ( ! vfolder_info_read_info (newinfo, result, context)) { vfolder_info_destroy (newinfo); return FALSE; } /* FIXME: reload logic for 'desktop_dir' and * 'user_desktop_dir' */ setup_itemdirs = TRUE; /* Validity of entries and item dirs and all that is unchanged */ if (vfolder_info_item_dirs_same (info, newinfo)) { newinfo->entries = info->entries; info->entries = NULL; newinfo->entries_ht = info->entries_ht; info->entries_ht = NULL /* some places assume this non-null, but we're only going to destroy this */; newinfo->entries_valid = info->entries_valid; /* move over the monitors/statlocs since those are valid */ newinfo->item_dir_monitors = info->item_dir_monitors; info->item_dir_monitors = NULL; newinfo->stat_dirs = info->stat_dirs; info->stat_dirs = NULL; /* No need to resetup dir monitors */ setup_itemdirs = FALSE; /* No need to do anything with file monitors */ } else { /* Whack all monitors here! */ for (li = info->file_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h->handle != NULL) gnome_vfs_monitor_cancel (h->handle); h->handle = NULL; } } setup_filenames = TRUE; if (safe_string_same (info->filename, newinfo->filename) && safe_string_same (info->user_filename, newinfo->user_filename)) { newinfo->user_filename_last_write = info->user_filename_last_write; /* move over the monitors/statlocs since those are valid */ newinfo->filename_monitor = info->filename_monitor; info->filename_monitor = NULL; newinfo->user_filename_monitor = info->user_filename_monitor; info->user_filename_monitor = NULL; if (info->filename_statloc != NULL && info->filename != NULL) newinfo->filename_statloc = bake_statloc (info->filename, time (NULL)); if (info->user_filename_statloc != NULL && info->user_filename != NULL) newinfo->user_filename_statloc = bake_statloc (info->user_filename, time (NULL)); /* No need to resetup filename monitors */ setup_filenames = FALSE; } /* Note: not cancellable anymore, since we've * already started nibbling on the info structure, * so we'd need to back things out or some such, * too complex, so screw that */ monitor_setup (info, setup_filenames, setup_itemdirs, /* FIXME: setup_desktop_dirs */ TRUE, NULL, NULL); for (li = info->folder_monitors; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; li->data = NULL; add_folder_monitor_unlocked (newinfo, handle); file_monitor_handle_unref_unlocked (handle); } g_slist_free (info->folder_monitors); info->folder_monitors = NULL; g_slist_foreach (info->free_folder_monitors, (GFunc)file_monitor_handle_unref_unlocked, NULL); g_slist_free (info->free_folder_monitors); info->folder_monitors = NULL; /* we can just copy these for now, they will be readded * and all the fun stuff will be done with them later */ newinfo->file_monitors = info->file_monitors; info->file_monitors = NULL; newinfo->free_file_monitors = info->free_file_monitors; info->free_file_monitors = NULL; /* emit changed on all folders, a bit drastic, but oh well, * we also invalidate all folders at the same time, but that is * irrelevant since they should all just be invalid to begin with */ invalidate_folder_T (info->root); /* FIXME: make sure if this was enough, I think it was */ vfolder_info_free_internals_unlocked (info); memcpy (info, newinfo, sizeof (VFolderInfo)); g_free (newinfo); /* must rescan the monitors here */ if (info->entries_valid) { rescan_monitors (info); } if ( ! info->entries_valid && force_read_items) { GnomeVFSResult res; /* FIXME: I bet cancelation plays havoc with monitors, * I'm not sure however */ if (info->file_monitors != NULL) { vfolder_info_read_items (info, &res, NULL); } else { if ( ! vfolder_info_read_items (info, result, context)) return FALSE; } info->entries_valid = TRUE; } return TRUE; } static gboolean vfolder_info_reload (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context, gboolean force_read_items) { G_LOCK (vfolder_lock); if (vfolder_info_reload_unlocked (info, result, context, force_read_items)) { G_UNLOCK (vfolder_lock); return TRUE; } else { G_UNLOCK (vfolder_lock); return FALSE; } } static gboolean vfolder_info_recheck (VFolderInfo *info, GnomeVFSResult *result, GnomeVFSContext *context) { GSList *li; time_t curtime = time (NULL); gboolean reread = FALSE; if (info->filename_statloc != NULL && ! check_statloc (info->filename_statloc, curtime)) { if ( ! vfolder_info_reload_unlocked (info, result, context, FALSE /* force read items */)) { /* we have failed, make sure we fail * next time too */ info->filename_statloc->trigger_next = TRUE; return FALSE; } reread = TRUE; } if ( ! reread && info->user_filename_statloc != NULL && ! check_statloc (info->user_filename_statloc, curtime)) { if ( ! vfolder_info_reload_unlocked (info, result, context, FALSE /* force read items */)) { /* we have failed, make sure we fail * next time too */ info->user_filename_statloc->trigger_next = TRUE; return FALSE; } reread = TRUE; } if (info->entries_valid) { for (li = info->stat_dirs; li != NULL; li = li->next) { StatLoc *sl = li->data; if ( ! check_statloc (sl, curtime)) { info->entries_valid = FALSE; break; } } } return TRUE; } static VFolderInfo * get_vfolder_info_unlocked (const char *scheme, GnomeVFSResult *result, GnomeVFSContext *context) { VFolderInfo *info; if (infos != NULL && (info = g_hash_table_lookup (infos, scheme)) != NULL) { if ( ! vfolder_info_recheck (info, result, context)) { return NULL; } if ( ! info->entries_valid) { g_slist_foreach (info->entries, (GFunc)entry_unref, NULL); g_slist_free (info->entries); info->entries = NULL; if (info->entries_ht != NULL) g_hash_table_destroy (info->entries_ht); info->entries_ht = g_hash_table_new (g_str_hash, g_str_equal); if ( ! vfolder_info_read_items (info, result, context)) { info->entries_valid = FALSE; return NULL; } invalidate_folder_T (info->root); info->entries_valid = TRUE; /* Update modification time of all folders, * kind of evil, but it will make adding new items work * I hope. This is because rereading usually means * something changed */ info->modification_time = time (NULL); } return info; } if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if (infos == NULL) infos = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)vfolder_info_destroy); info = g_new0 (VFolderInfo, 1); vfolder_info_init (info, scheme); if (gnome_vfs_context_check_cancellation (context)) { vfolder_info_destroy (info); *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if ( ! vfolder_info_read_info (info, result, context)) { vfolder_info_destroy (info); return NULL; } if ( ! monitor_setup (info, TRUE /* setup_filenames */, TRUE /* setup_itemdirs */, TRUE /* setup_desktop_dirs */, result, context)) { vfolder_info_destroy (info); return NULL; } g_hash_table_insert (infos, g_strdup (scheme), info); if ( ! vfolder_info_read_items (info, result, context)) { info->entries_valid = FALSE; return NULL; } info->entries_valid = TRUE; return info; } static VFolderInfo * get_vfolder_info (const char *scheme, GnomeVFSResult *result, GnomeVFSContext *context) { VFolderInfo *info; G_LOCK (vfolder_lock); info = get_vfolder_info_unlocked (scheme, result, context); G_UNLOCK (vfolder_lock); return info; } static char * keywords_to_string (GSList *keywords) { GSList *li; GString *str = g_string_new (NULL); for (li = keywords; li != NULL; li = li->next) { GQuark word = GPOINTER_TO_INT (li->data); g_string_append (str, g_quark_to_string (word)); g_string_append_c (str, ';'); } return g_string_free (str, FALSE); } /* copy file and add keywords line */ static gboolean copy_file_with_keywords (const char *from, const char *to, GSList *keywords) { FILE *fp; FILE *wfp; int wfd; char buf[BUFSIZ]; char *keyword_string; if ( ! ensure_dir (to, TRUE /* ignore_basename */)) return FALSE; wfd = open (to, O_CREAT | O_WRONLY | O_TRUNC, 0600); if (wfd < 0) { return FALSE; } keyword_string = keywords_to_string (keywords); wfp = fdopen (wfd, "w"); fp = fopen (from, "r"); if (fp != NULL) { gboolean wrote_keywords = FALSE; while (fgets (buf, sizeof (buf), fp) != NULL) { fprintf (wfp, "%s", buf); if ( ! wrote_keywords && (strncmp (buf, "[Desktop Entry]", strlen ("[Desktop Entry]")) == 0 || strncmp (buf, "[KDE Desktop Entry]", strlen ("[KDE Desktop Entry]")) == 0)) { fprintf (wfp, "Categories=%s\n", keyword_string); wrote_keywords = TRUE; } } fclose (fp); } else { fprintf (wfp, "[Desktop Entry]\nCategories=%s\n", keyword_string); } /* FIXME: does this close wfd???? */ fclose (wfp); close (wfd); g_free (keyword_string); return TRUE; } static gboolean copy_file (const char *from, const char *to) { int fd; int wfd; if ( ! ensure_dir (to, TRUE /* ignore_basename */)) return FALSE; wfd = open (to, O_CREAT | O_WRONLY | O_TRUNC, 0600); if (wfd < 0) { return FALSE; } fd = open (from, O_RDONLY); if (fd >= 0) { char buf[1024]; ssize_t n; while ((n = read (fd, buf, sizeof(buf))) > 0) { write (wfd, buf, n); } close (fd); } close (wfd); return TRUE; } static gboolean make_file_private (VFolderInfo *info, EntryFile *efile) { char *newfname; Entry *entry = (Entry *)efile; if (efile->per_user) return TRUE; /* this file already exists so whack its monitors */ if (efile->filename != NULL) { GSList *li; for (li = entry->monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h->handle != NULL) gnome_vfs_monitor_cancel (h->handle); h->handle = NULL; } } newfname = g_build_filename (g_get_home_dir (), DOT_GNOME, "vfolders", info->scheme, efile->entry.name, NULL); if (efile->implicit_keywords) { if (efile->filename != NULL && ! copy_file_with_keywords (efile->filename, newfname, efile->keywords)) { /* FIXME: what to do with monitors here, they * have already been whacked, a corner case * not handled! */ g_free (newfname); return FALSE; } } else { if (efile->filename != NULL && ! copy_file (efile->filename, newfname)) { /* FIXME: what to do with monitors here, they * have already been whacked, a corner case * not handled! */ g_free (newfname); return FALSE; } } /* we didn't copy but ensure path anyway */ if (efile->filename == NULL && ! ensure_dir (newfname, TRUE /* ignore_basename */)) { g_free (newfname); return FALSE; } /* this file already exists so re-add monitors at the new location */ if (efile->filename != NULL) { GSList *li; char *uri = gnome_vfs_get_uri_from_local_path (newfname); for (li = entry->monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; gnome_vfs_monitor_add (&(h->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, h); } g_free (uri); } g_free (efile->filename); efile->filename = newfname; efile->per_user = TRUE; return TRUE; } static void try_free_file_monitors_create_dirfile_unlocked (VFolderInfo *info, Folder *folder) { GSList *li, *list; list = g_slist_copy (info->free_file_monitors); for (li = list; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; Folder *f; VFolderURI vuri; GnomeVFSResult result; if ( ! handle->is_directory_file) continue; /* Evil! EVIL URI PARSING. this will eat a lot of stack if we * have lots of free monitors */ VFOLDER_URI_PARSE (handle->uri, &vuri); f = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, NULL); if (folder != f) continue; info->free_file_monitors = g_slist_remove (info->free_file_monitors, handle); ((Entry *)folder)->monitors = g_slist_prepend (((Entry *)folder)->monitors, handle); handle->exists = TRUE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, handle->uri, GNOME_VFS_MONITOR_EVENT_CREATED); } g_slist_free (list); } static void make_new_dirfile (VFolderInfo *info, Folder *folder) { char *name = g_strdup (folder->entry.name); char *fname; char *p; int i; int fd; for (p = name; *p != '\0'; p++) { if ( ! ( (*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) { *p = '_'; } } i = 0; fname = NULL; do { char *fullname; g_free (fname); if (i > 0) { fname = g_strdup_printf ("%s-%d.directory", name, i); } else { fname = g_strdup_printf ("%s.directory", name); } fullname = g_build_filename (info->user_desktop_dir, fname, NULL); fd = open (fullname, O_CREAT | O_WRONLY | O_EXCL, 0600); g_free (fullname); } while (fd < 0); close (fd); folder->desktop_file = fname; info->dirty = TRUE; try_free_file_monitors_create_dirfile_unlocked (info, folder); } static gboolean make_dirfile_private (VFolderInfo *info, Folder *folder) { char *fname; char *desktop_file; GSList *li; char *uri; gboolean ret; if (info->user_desktop_dir == NULL) return FALSE; if ( ! ensure_dir (info->user_desktop_dir, FALSE /* ignore_basename */)) return FALSE; if (folder->desktop_file == NULL) { make_new_dirfile (info, folder); return TRUE; } /* FIXME: this is broken! What if the desktop file exists * in the local but there is a different (but with a same name) * .directory in the system. */ fname = g_build_filename (info->user_desktop_dir, folder->desktop_file, NULL); if (access (fname, F_OK) == 0) { g_free (fname); return TRUE; } desktop_file = get_directory_file (info, folder); if (desktop_file == NULL) { int fd = open (fname, O_CREAT | O_EXCL | O_WRONLY, 0600); g_free (fname); if (fd >= 0) { close (fd); return TRUE; } return FALSE; } for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h->is_directory_file) { if (h->handle != NULL) gnome_vfs_monitor_cancel (h->handle); h->handle = NULL; } } ret = TRUE; if ( ! copy_file (desktop_file, fname)) { ret = FALSE; g_free (fname); fname = desktop_file; desktop_file = NULL; } uri = gnome_vfs_get_uri_from_local_path (fname); for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h->is_directory_file) { gnome_vfs_monitor_add (&(h->handle), uri, GNOME_VFS_MONITOR_FILE, file_monitor, h); } } g_free (uri); g_free (desktop_file); g_free (fname); return ret; } static Folder * resolve_folder (VFolderInfo *info, const char *path, gboolean ignore_basename, GnomeVFSResult *result, GnomeVFSContext *context) { char **ppath; int i; Folder *folder = info->root; ppath = g_strsplit (path, "/", -1); if (ppath == NULL || ppath[0] == NULL) { g_strfreev (ppath); *result = GNOME_VFS_ERROR_INVALID_URI; return NULL; } for (i = 0; ppath [i] != NULL; i++) { const char *segment = ppath[i]; if (*segment == '\0') continue; if (ignore_basename && ppath [i + 1] == NULL) break; else { folder = (Folder *) find_entry (folder->subfolders, segment); if (folder == NULL) break; } } g_strfreev (ppath); if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if (folder == NULL) *result = GNOME_VFS_ERROR_NOT_FOUND; return folder; } static Entry * resolve_path (VFolderInfo *info, const char *path, const char *basename, Folder **return_folder, GnomeVFSResult *result, GnomeVFSContext *context) { Entry *entry; Folder *folder; if (strcmp (path, "/") == 0) return (Entry *)info->root; folder = resolve_folder (info, path, TRUE /* ignore_basename */, result, context); if (return_folder != NULL) *return_folder = folder; if (folder == NULL) { return NULL; } /* Make sure we have the entries here */ ensure_folder_unlocked (info, folder, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); entry = find_entry (folder->entries, basename); if (entry == NULL) *result = GNOME_VFS_ERROR_NOT_FOUND; return entry; } static Entry * get_entry_unlocked (VFolderURI *vuri, Folder **parent, gboolean *is_directory_file, GnomeVFSResult *result, GnomeVFSContext *context) { VFolderInfo *info; Entry *entry; if (is_directory_file != NULL) *is_directory_file = FALSE; if (parent != NULL) *parent = NULL; info = get_vfolder_info_unlocked (vuri->scheme, result, context); if (info == NULL) return NULL; if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if (vuri->is_all_scheme) { GSList *efile_list; if (vuri->file == NULL) { entry = resolve_path (info, vuri->path, vuri->file, parent, result, context); return entry; } efile_list = g_hash_table_lookup (info->entries_ht, vuri->file); if (efile_list == NULL) { *result = GNOME_VFS_ERROR_NOT_FOUND; return NULL; } else { return efile_list->data; } } if (vuri->file != NULL && check_ext (vuri->file, ".directory") == TRUE) { Folder *folder; folder = resolve_folder (info, vuri->path, TRUE /* ignore_basename */, result, context); if (folder == NULL) { return NULL; } if (is_directory_file != NULL) *is_directory_file = TRUE; if (parent != NULL) *parent = folder; return (Entry *)folder; } else { entry = resolve_path (info, vuri->path, vuri->file, parent, result, context); return entry; } } static Entry * get_entry (VFolderURI *vuri, Folder **parent, gboolean *is_directory_file, GnomeVFSResult *result, GnomeVFSContext *context) { Entry *entry; G_LOCK (vfolder_lock); entry = get_entry_unlocked (vuri, parent, is_directory_file, result, context); G_UNLOCK (vfolder_lock); return entry; } /* only works for files and only those that exist */ /* unlocked function */ static GnomeVFSURI * desktop_uri_to_file_uri (VFolderInfo *info, VFolderURI *desktop_vuri, Entry **the_entry, gboolean *the_is_directory_file, Folder **the_folder, gboolean privatize, GnomeVFSResult *result, GnomeVFSContext *context) { gboolean is_directory_file; GnomeVFSURI *ret_uri; Folder *folder = NULL; Entry *entry; entry = get_entry_unlocked (desktop_vuri, &folder, &is_directory_file, result, context); if (entry == NULL) return NULL; if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if (the_folder != NULL) *the_folder = folder; if (the_entry != NULL) *the_entry = entry; if (the_is_directory_file != NULL) *the_is_directory_file = is_directory_file; if (is_directory_file && entry->type == ENTRY_FOLDER) { char *desktop_file; folder = (Folder *)entry; if (the_folder != NULL) *the_folder = folder; /* we'll be doing something write like */ if (folder->read_only && privatize) { *result = GNOME_VFS_ERROR_READ_ONLY; return NULL; } if (privatize) { char *fname; if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if ( ! make_dirfile_private (info, folder)) { *result = GNOME_VFS_ERROR_GENERIC; return NULL; } fname = g_build_filename (g_get_home_dir (), folder->desktop_file, NULL); ret_uri = gnome_vfs_uri_new (fname); g_free (fname); return ret_uri; } desktop_file = get_directory_file_unlocked (info, folder); if (desktop_file != NULL) { char *s = gnome_vfs_get_uri_from_local_path (desktop_file); g_free (desktop_file); ret_uri = gnome_vfs_uri_new (s); g_free (s); return ret_uri; } else { *result = GNOME_VFS_ERROR_NOT_FOUND; return NULL; } } else if (entry->type == ENTRY_FILE) { EntryFile *efile = (EntryFile *)entry; char *s; /* we'll be doing something write like */ if (folder != NULL && folder->read_only && privatize) { *result = GNOME_VFS_ERROR_READ_ONLY; return NULL; } if (gnome_vfs_context_check_cancellation (context)) { *result = GNOME_VFS_ERROR_CANCELLED; return NULL; } if (privatize && ! make_file_private (info, efile)) { *result = GNOME_VFS_ERROR_GENERIC; return NULL; } s = gnome_vfs_get_uri_from_local_path (efile->filename); ret_uri = gnome_vfs_uri_new (s); g_free (s); return ret_uri; } else { if (the_folder != NULL) *the_folder = (Folder *)entry; *result = GNOME_VFS_ERROR_IS_DIRECTORY; return NULL; } } static void remove_file (Folder *folder, const char *basename) { GSList *li; char *s; if (folder->includes_ht != NULL) { li = g_hash_table_lookup (folder->includes_ht, basename); if (li != NULL) { char *name = li->data; folder->includes = g_slist_delete_link (folder->includes, li); g_hash_table_remove (folder->includes_ht, basename); g_free (name); } } if (folder->excludes == NULL) { folder->excludes = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); } s = g_strdup (basename); g_hash_table_replace (folder->excludes, s, s); } static void add_file (Folder *folder, const char *basename) { GSList *li = NULL; if (folder->includes_ht != NULL) { li = g_hash_table_lookup (folder->includes_ht, basename); } /* if not found */ if (li == NULL) { char *str = g_strdup (basename); folder->includes = g_slist_prepend (folder->includes, str); if (folder->includes_ht == NULL) { folder->includes_ht = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); } g_hash_table_replace (folder->includes_ht, str, folder->includes); } if (folder->excludes != NULL) g_hash_table_remove (folder->excludes, basename); } typedef struct _FileHandle FileHandle; struct _FileHandle { VFolderInfo *info; GnomeVFSMethodHandle *handle; Entry *entry; gboolean write; gboolean is_directory_file; }; static void make_handle (GnomeVFSMethodHandle **method_handle, GnomeVFSMethodHandle *file_handle, VFolderInfo *info, Entry *entry, gboolean is_directory_file, gboolean write) { if (file_handle != NULL) { FileHandle *handle = g_new0 (FileHandle, 1); handle->info = info; handle->handle = file_handle; handle->entry = entry_ref (entry); handle->is_directory_file = is_directory_file; handle->write = write; *method_handle = (GnomeVFSMethodHandle *) handle; } else { *method_handle = NULL; } } static void whack_handle (FileHandle *handle) { entry_unref (handle->entry); handle->entry = NULL; handle->handle = NULL; handle->info = NULL; g_free (handle); } static GnomeVFSResult do_open (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, GnomeVFSContext *context) { GnomeVFSURI *file_uri; GnomeVFSResult result = GNOME_VFS_OK; VFolderInfo *info; Entry *entry; gboolean is_directory_file; GnomeVFSMethodHandle *file_handle = NULL; VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); /* These can't be very nice FILE names */ if (vuri.file == NULL || vuri.ends_in_slash) return GNOME_VFS_ERROR_INVALID_URI; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; if (mode & GNOME_VFS_OPEN_WRITE && (info->read_only || vuri.is_all_scheme)) return GNOME_VFS_ERROR_READ_ONLY; G_LOCK (vfolder_lock); file_uri = desktop_uri_to_file_uri (info, &vuri, &entry, &is_directory_file, NULL /* the_folder */, mode & GNOME_VFS_OPEN_WRITE, &result, context); if (file_uri == NULL) { G_UNLOCK (vfolder_lock); return result; } result = (* parent_method->open) (parent_method, &file_handle, file_uri, mode, context); if (result == GNOME_VFS_ERROR_CANCELLED) { G_UNLOCK (vfolder_lock); gnome_vfs_uri_unref (file_uri); return result; } make_handle (method_handle, file_handle, info, entry, is_directory_file, mode & GNOME_VFS_OPEN_WRITE); gnome_vfs_uri_unref (file_uri); if (info->dirty) { vfolder_info_write_user (info); } G_UNLOCK (vfolder_lock); return result; } static void remove_from_all_except (Folder *root, const char *name, Folder *except) { GSList *li; if (root != except) { remove_file (root, name); if (root->up_to_date) { for (li = root->entries; li != NULL; li = li->next) { Entry *entry = li->data; if (strcmp (name, entry->name) == 0) { root->entries = g_slist_delete_link (root->entries, li); break; } } } } for (li = root->subfolders; li != NULL; li = li->next) { Folder *subfolder = li->data; remove_from_all_except (subfolder, name, except); } } static GnomeVFSResult do_create (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSOpenMode mode, gboolean exclusive, guint perm, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; GnomeVFSMethodHandle *file_handle; GnomeVFSURI *file_uri; VFolderURI vuri; VFolderInfo *info; Folder *parent; Entry *entry; EntryFile *efile; char *s; GSList *li; VFOLDER_URI_PARSE (uri, &vuri); /* These can't be very nice FILE names */ if (vuri.file == NULL || vuri.ends_in_slash) return GNOME_VFS_ERROR_INVALID_URI; if ( ! check_ext (vuri.file, ".desktop") && ! strcmp (vuri.file, ".directory") == 0) { return GNOME_VFS_ERROR_INVALID_URI; } /* all scheme is read only */ if (vuri.is_all_scheme) return GNOME_VFS_ERROR_READ_ONLY; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; if (info->user_filename == NULL || info->read_only) return GNOME_VFS_ERROR_READ_ONLY; parent = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, context); if (parent == NULL) return result; if (parent->read_only) return GNOME_VFS_ERROR_READ_ONLY; if (strcmp (vuri.file, ".directory") == 0) { char *fname; G_LOCK (vfolder_lock); if (exclusive) { char *desktop_file; desktop_file = get_directory_file_unlocked (info, parent); if (desktop_file != NULL) { g_free (desktop_file); G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_FILE_EXISTS; } } if ( ! make_dirfile_private (info, parent)) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_GENERIC; } fname = g_build_filename (g_get_home_dir (), parent->desktop_file, NULL); s = gnome_vfs_get_uri_from_local_path (fname); file_uri = gnome_vfs_uri_new (s); g_free (fname); g_free (s); if (file_uri == NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_GENERIC; } result = (* parent_method->create) (parent_method, &file_handle, file_uri, mode, exclusive, perm, context); gnome_vfs_uri_unref (file_uri); make_handle (method_handle, file_handle, info, (Entry *)parent, TRUE /* is_directory_file */, TRUE /* write */); if (info->dirty) vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return result; } ensure_folder (info, parent, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); entry = find_entry (parent->entries, vuri.file); if (entry != NULL && entry->type == ENTRY_FOLDER) return GNOME_VFS_ERROR_IS_DIRECTORY; efile = (EntryFile *)entry; if (efile != NULL) { if (exclusive) return GNOME_VFS_ERROR_FILE_EXISTS; G_LOCK (vfolder_lock); if ( ! make_file_private (info, efile)) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_GENERIC; } s = gnome_vfs_get_uri_from_local_path (efile->filename); file_uri = gnome_vfs_uri_new (s); g_free (s); if (file_uri == NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_GENERIC; } result = (* parent_method->create) (parent_method, &file_handle, file_uri, mode, exclusive, perm, context); gnome_vfs_uri_unref (file_uri); make_handle (method_handle, file_handle, info, (Entry *)efile, FALSE /* is_directory_file */, TRUE /* write */); G_UNLOCK (vfolder_lock); return result; } G_LOCK (vfolder_lock); li = g_hash_table_lookup (info->entries_ht, vuri.file); if (exclusive && li != NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_FILE_EXISTS; } if (li == NULL) { efile = file_new (vuri.file); vfolder_info_insert_entry (info, efile); entry_unref ((Entry *)efile); } else { efile = li->data; } /* this will make a private name for this */ if ( ! make_file_private (info, efile)) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_GENERIC; } add_file (parent, vuri.file); parent->sorted = FALSE; if (parent->up_to_date) parent->entries = g_slist_prepend (parent->entries, efile); /* if we created a brand new name, then we exclude it * from everywhere else to ensure overall sanity */ if (li == NULL) remove_from_all_except (info->root, vuri.file, parent); s = gnome_vfs_get_uri_from_local_path (efile->filename); file_uri = gnome_vfs_uri_new (s); g_free (s); result = (* parent_method->create) (parent_method, &file_handle, file_uri, mode, exclusive, perm, context); gnome_vfs_uri_unref (file_uri); make_handle (method_handle, file_handle, info, (Entry *)efile, FALSE /* is_directory_file */, TRUE /* write */); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return result; } static GnomeVFSResult do_close (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) return GNOME_VFS_OK; G_LOCK (vfolder_lock); result = (* parent_method->close) (parent_method, handle->handle, context); handle->handle = NULL; /* we reread the Categories keyword */ if (handle->write && handle->entry != NULL && handle->entry->type == ENTRY_FILE) { EntryFile *efile = (EntryFile *)handle->entry; char *categories; readitem_entry (efile->filename, "Categories", &categories, NULL, NULL); set_keywords (efile, categories); g_free (categories); /* FIXME: what about OnlyShowIn */ /* FIXME: check if the keywords changed, if not, do * nothing */ /* Perhaps a bit drastic */ /* also this emits the CHANGED monitor signal */ invalidate_folder_T (handle->info->root); /* the file changed monitor will happen by itself * as the underlying file is changed */ } else if (handle->write && handle->entry != NULL && handle->entry->type == ENTRY_FOLDER && handle->is_directory_file) { /* if we're monitoring this directory, emit the CHANGED * monitor thing, it will also emit a changed on * the file itself. It is better to emit changed * just in case. */ emit_monitor ((Folder *)(handle->entry), GNOME_VFS_MONITOR_EVENT_CHANGED); } whack_handle (handle); G_UNLOCK (vfolder_lock); return result; } static void fill_buffer (gpointer buffer, GnomeVFSFileSize num_bytes, GnomeVFSFileSize *bytes_read) { char *buf = buffer; GnomeVFSFileSize i; for (i = 0; i < num_bytes; i++) { if (rand () % 32 == 0 || i == num_bytes-1) buf[i] = '\n'; else buf[i] = ((rand()>>4) % 94) + 32; } if (bytes_read != 0) *bytes_read = i; } static GnomeVFSResult do_read (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, gpointer buffer, GnomeVFSFileSize num_bytes, GnomeVFSFileSize *bytes_read, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) { if ((rand () >> 4) & 0x3) { fill_buffer (buffer, num_bytes, bytes_read); return GNOME_VFS_OK; } else { return GNOME_VFS_ERROR_EOF; } } result = (* parent_method->read) (parent_method, handle->handle, buffer, num_bytes, bytes_read, context); return result; } static GnomeVFSResult do_write (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, gconstpointer buffer, GnomeVFSFileSize num_bytes, GnomeVFSFileSize *bytes_written, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) return GNOME_VFS_OK; result = (* parent_method->write) (parent_method, handle->handle, buffer, num_bytes, bytes_written, context); return result; } static GnomeVFSResult do_seek (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSSeekPosition whence, GnomeVFSFileOffset offset, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) return GNOME_VFS_OK; result = (* parent_method->seek) (parent_method, handle->handle, whence, offset, context); return result; } static GnomeVFSResult do_tell (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileOffset *offset_return) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; result = (* parent_method->tell) (parent_method, handle->handle, offset_return); return result; } static GnomeVFSResult do_truncate_handle (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileSize where, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) return GNOME_VFS_OK; result = (* parent_method->truncate_handle) (parent_method, handle->handle, where, context); return result; } static GnomeVFSResult do_truncate (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSFileSize where, GnomeVFSContext *context) { GnomeVFSURI *file_uri; GnomeVFSResult result = GNOME_VFS_OK; VFolderInfo *info; Entry *entry; VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); /* These can't be very nice FILE names */ if (vuri.file == NULL || vuri.ends_in_slash) return GNOME_VFS_ERROR_INVALID_URI; if (vuri.is_all_scheme) return GNOME_VFS_ERROR_READ_ONLY; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; if (info->read_only) return GNOME_VFS_ERROR_READ_ONLY; G_LOCK (vfolder_lock); file_uri = desktop_uri_to_file_uri (info, &vuri, &entry, NULL /* the_is_directory_file */, NULL /* the_folder */, TRUE /* privatize */, &result, context); G_UNLOCK (vfolder_lock); if (file_uri == NULL) return result; result = (* parent_method->truncate) (parent_method, file_uri, where, context); gnome_vfs_uri_unref (file_uri); if (info->dirty) { G_LOCK (vfolder_lock); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); } if (entry->type == ENTRY_FILE) { EntryFile *efile = (EntryFile *)entry; G_LOCK (vfolder_lock); g_slist_free (efile->keywords); efile->keywords = NULL; G_UNLOCK (vfolder_lock); } /* Perhaps a bit drastic, but oh well */ invalidate_folder (info->root); return result; } typedef struct _DirHandle DirHandle; struct _DirHandle { VFolderInfo *info; Folder *folder; GnomeVFSFileInfoOptions options; /* List of Entries */ GSList *list; GSList *current; }; static GnomeVFSResult do_open_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle, GnomeVFSURI *uri, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; VFolderURI vuri; DirHandle *dh; Folder *folder; VFolderInfo *info; char *desktop_file; VFOLDER_URI_PARSE (uri, &vuri); info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; /* In the all- scheme just list all filenames */ if (vuri.is_all_scheme) { if (any_subdir (vuri.path)) return GNOME_VFS_ERROR_NOT_FOUND; dh = g_new0 (DirHandle, 1); dh->info = info; dh->options = options; dh->folder = NULL; G_LOCK (vfolder_lock); dh->list = g_slist_copy (info->entries); g_slist_foreach (dh->list, (GFunc)entry_ref, NULL); dh->current = dh->list; G_UNLOCK (vfolder_lock); *method_handle = (GnomeVFSMethodHandle*) dh; return GNOME_VFS_OK; } folder = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, context); if (folder == NULL) return result; /* Make sure we have the entries and sorted here */ ensure_folder_sort (info, folder); dh = g_new0 (DirHandle, 1); dh->info = info; dh->options = options; G_LOCK (vfolder_lock); dh->folder = (Folder *)entry_ref ((Entry *)folder); dh->list = g_slist_copy (folder->entries); g_slist_foreach (folder->entries, (GFunc)entry_ref, NULL); G_UNLOCK (vfolder_lock); desktop_file = get_directory_file (info, folder); if (desktop_file != NULL) { EntryFile *efile = file_new (".directory"); dh->list = g_slist_prepend (dh->list, efile); g_free (desktop_file); } dh->current = dh->list; *method_handle = (GnomeVFSMethodHandle*) dh; return GNOME_VFS_OK; } static GnomeVFSResult do_close_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSContext *context) { DirHandle *dh; dh = (DirHandle*) method_handle; G_LOCK (vfolder_lock); g_slist_foreach (dh->list, (GFunc)entry_unref, NULL); g_slist_free (dh->list); dh->list = NULL; dh->current = NULL; if (dh->folder != NULL) entry_unref ((Entry *)dh->folder); dh->folder = NULL; dh->info = NULL; g_free (dh); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } static GnomeVFSResult do_read_directory (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSContext *context) { DirHandle *dh; Entry *entry; GnomeVFSFileInfoOptions options; dh = (DirHandle*) method_handle; read_directory_again: if (dh->current == NULL) { return GNOME_VFS_ERROR_EOF; } entry = dh->current->data; dh->current = dh->current->next; options = dh->options; if (entry->type == ENTRY_FILE && ((EntryFile *)entry)->filename != NULL) { EntryFile *efile = (EntryFile *)entry; char *furi = gnome_vfs_get_uri_from_local_path (efile->filename); GnomeVFSURI *uri = gnome_vfs_uri_new (furi); /* we always get mime-type by forcing it below */ if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; /* Get the file info for this */ (* parent_method->get_file_info) (parent_method, uri, file_info, options, context); /* we ignore errors from this since the file_info just * won't be filled completely if there's an error, that's all */ g_free (file_info->mime_type); file_info->mime_type = g_strdup ("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; /* Now we wipe those fields we don't support */ file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); gnome_vfs_uri_unref (uri); g_free (furi); } else if (entry->type == ENTRY_FILE) { file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; file_info->name = g_strdup (entry->name); GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); file_info->type = GNOME_VFS_FILE_TYPE_REGULAR; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; /* FIXME: Is this correct? isn't there an xdg mime type? */ file_info->mime_type = g_strdup ("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; /* FIXME: get some ctime/mtime */ } else /* ENTRY_FOLDER */ { Folder *folder = (Folder *)entry; /* Skip empty folders if they have * the flag set */ if (folder->dont_show_if_empty) { /* Make sure we have the entries */ ensure_folder (dh->info, folder, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); if (folder->entries == NULL) { /* start this function over on the * next item */ goto read_directory_again; } } file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; file_info->name = g_strdup (entry->name); GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; file_info->mime_type = g_strdup ("x-directory/vfolder-desktop"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; file_info->ctime = dh->info->modification_time; file_info->mtime = dh->info->modification_time; file_info->valid_fields |= (GNOME_VFS_FILE_INFO_FIELDS_CTIME | GNOME_VFS_FILE_INFO_FIELDS_MTIME); } return GNOME_VFS_OK; } static GnomeVFSResult do_get_file_info (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { GnomeVFSURI *file_uri; GnomeVFSResult result = GNOME_VFS_OK; Folder *folder; VFolderInfo *info; VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; G_LOCK (vfolder_lock); file_uri = desktop_uri_to_file_uri (info, &vuri, NULL /* the_entry */, NULL /* the_is_directory_file */, &folder, FALSE /* privatize */, &result, context); G_UNLOCK (vfolder_lock); if (file_uri == NULL && result != GNOME_VFS_ERROR_IS_DIRECTORY) return result; if (file_uri != NULL) { /* we always get mime-type by forcing it below */ if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; result = (* parent_method->get_file_info) (parent_method, file_uri, file_info, options, context); g_free (file_info->mime_type); file_info->mime_type = g_strdup ("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; /* Now we wipe those fields we don't support */ file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); gnome_vfs_uri_unref (file_uri); return result; } else if (folder != NULL) { file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE; file_info->name = g_strdup (folder->entry.name); GNOME_VFS_FILE_INFO_SET_LOCAL (file_info, TRUE); file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY; file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_TYPE; file_info->mime_type = g_strdup ("x-directory/vfolder-desktop"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; file_info->ctime = info->modification_time; file_info->mtime = info->modification_time; file_info->valid_fields |= (GNOME_VFS_FILE_INFO_FIELDS_CTIME | GNOME_VFS_FILE_INFO_FIELDS_MTIME); return GNOME_VFS_OK; } else { return GNOME_VFS_ERROR_NOT_FOUND; } } static GnomeVFSResult do_get_file_info_from_handle (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle, GnomeVFSFileInfo *file_info, GnomeVFSFileInfoOptions options, GnomeVFSContext *context) { GnomeVFSResult result; FileHandle *handle = (FileHandle *)method_handle; if (method_handle == (GnomeVFSMethodHandle *)method) { g_free (file_info->mime_type); file_info->mime_type = g_strdup ("text/plain"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; return GNOME_VFS_OK; } /* we always get mime-type by forcing it below */ if (options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) options &= ~GNOME_VFS_FILE_INFO_GET_MIME_TYPE; result = (* parent_method->get_file_info_from_handle) (parent_method, handle->handle, file_info, options, context); /* any file is of the .desktop type */ g_free (file_info->mime_type); file_info->mime_type = g_strdup ("application/x-gnome-app-info"); file_info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE; /* Now we wipe those fields we don't support */ file_info->valid_fields &= ~(UNSUPPORTED_INFO_FIELDS); return result; } static gboolean do_is_local (GnomeVFSMethod *method, const GnomeVFSURI *uri) { return TRUE; } static void try_free_folder_monitors_create_unlocked (VFolderInfo *info, Folder *folder) { GSList *li, *list; list = g_slist_copy (info->free_folder_monitors); for (li = list; li != NULL; li = li->next) { FileMonitorHandle *handle = li->data; Folder *f; VFolderURI vuri; GnomeVFSResult result; /* Evil! EVIL URI PARSING. this will eat a lot of stack if we * have lots of free monitors */ VFOLDER_URI_PARSE (handle->uri, &vuri); f = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, NULL); if (folder != f) continue; info->free_folder_monitors = g_slist_remove (info->free_folder_monitors, handle); ((Entry *)folder)->monitors = g_slist_prepend (((Entry *)folder)->monitors, handle); handle->exists = TRUE; gnome_vfs_monitor_callback ((GnomeVFSMethodHandle *)handle, handle->uri, GNOME_VFS_MONITOR_EVENT_CREATED); } } static GnomeVFSResult do_make_directory (GnomeVFSMethod *method, GnomeVFSURI *uri, guint perm, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; VFolderInfo *info; Folder *parent, *folder; VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); if (vuri.is_all_scheme) return GNOME_VFS_ERROR_READ_ONLY; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; if (info->user_filename == NULL || info->read_only) return GNOME_VFS_ERROR_READ_ONLY; parent = resolve_folder (info, vuri.path, TRUE /* ignore_basename */, &result, context); if (parent == NULL) return result; else if (parent->read_only) return GNOME_VFS_ERROR_READ_ONLY; G_LOCK (vfolder_lock); folder = (Folder *)find_entry (parent->subfolders, vuri.file); if (folder != NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_FILE_EXISTS; } folder = folder_new (vuri.file); parent->subfolders = g_slist_append (parent->subfolders, folder); folder->parent = parent; parent->up_to_date = FALSE; try_free_folder_monitors_create_unlocked (info, folder); /* parent changed */ emit_monitor (parent, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } static GnomeVFSResult do_remove_directory (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; Folder *folder; VFolderInfo *info; VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); if (vuri.is_all_scheme) return GNOME_VFS_ERROR_READ_ONLY; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; if (info->user_filename == NULL || info->read_only) return GNOME_VFS_ERROR_READ_ONLY; G_LOCK (vfolder_lock); folder = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, context); if (folder == NULL) { G_UNLOCK (vfolder_lock); return result; } if (folder->read_only || (folder->parent != NULL && folder->parent->read_only)) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_READ_ONLY; } /* don't make removing directories easy */ if (folder->desktop_file != NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY; } /* Make sure we have the entries */ ensure_folder_unlocked (info, folder, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); /* don't make removing directories easy */ if (folder->entries != NULL) { G_UNLOCK (vfolder_lock); return GNOME_VFS_ERROR_DIRECTORY_NOT_EMPTY; } emit_and_delete_monitor (info, folder); if (folder->only_unallocated) { GSList *li = g_slist_find (info->unallocated_folders, folder); if (li != NULL) { info->unallocated_folders = g_slist_delete_link (info->unallocated_folders, li); entry_unref ((Entry *)folder); } } if (folder == info->root) { info->root = NULL; entry_unref ((Entry *)folder); info->root = folder_new ("Root"); } else { Folder *parent = folder->parent; g_assert (parent != NULL); parent->subfolders = g_slist_remove (parent->subfolders, folder); parent->up_to_date = FALSE; entry_unref ((Entry *)folder); /* parent changed */ emit_monitor (parent, GNOME_VFS_MONITOR_EVENT_CHANGED); } vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } /* a fairly evil function that does the whole move bit by copy and * remove */ static GnomeVFSResult long_move (GnomeVFSMethod *method, VFolderURI *old_vuri, VFolderURI *new_vuri, gboolean force_replace, GnomeVFSContext *context) { GnomeVFSResult result; GnomeVFSMethodHandle *handle; GnomeVFSURI *file_uri; const char *path; int fd; char buf[BUFSIZ]; int bytes; VFolderInfo *info; info = get_vfolder_info (old_vuri->scheme, &result, context); if (info == NULL) return result; G_LOCK (vfolder_lock); file_uri = desktop_uri_to_file_uri (info, old_vuri, NULL /* the_entry */, NULL /* the_is_directory_file */, NULL /* the_folder */, FALSE /* privatize */, &result, context); G_UNLOCK (vfolder_lock); if (file_uri == NULL) return result; path = gnome_vfs_uri_get_path (file_uri); if (path == NULL) { gnome_vfs_uri_unref (file_uri); return GNOME_VFS_ERROR_INVALID_URI; } fd = open (path, O_RDONLY); if (fd < 0) { gnome_vfs_uri_unref (file_uri); return gnome_vfs_result_from_errno (); } gnome_vfs_uri_unref (file_uri); info->inhibit_write++; result = method->create (method, &handle, new_vuri->uri, GNOME_VFS_OPEN_WRITE, force_replace /* exclusive */, 0600 /* perm */, context); if (result != GNOME_VFS_OK) { close (fd); info->inhibit_write--; return result; } while ((bytes = read (fd, buf, BUFSIZ)) > 0) { GnomeVFSFileSize bytes_written = 0; result = method->write (method, handle, buf, bytes, &bytes_written, context); if (result == GNOME_VFS_OK && bytes_written != bytes) result = GNOME_VFS_ERROR_NO_SPACE; if (result != GNOME_VFS_OK) { close (fd); method->close (method, handle, context); /* FIXME: is this completely correct ? */ method->unlink (method, new_vuri->uri, context); G_LOCK (vfolder_lock); info->inhibit_write--; vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return result; } } close (fd); result = method->close (method, handle, context); if (result != GNOME_VFS_OK) { G_LOCK (vfolder_lock); info->inhibit_write--; vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return result; } result = method->unlink (method, old_vuri->uri, context); G_LOCK (vfolder_lock); info->inhibit_write--; vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return result; } static GnomeVFSResult move_directory_file (VFolderInfo *info, Folder *old_folder, Folder *new_folder) { if (old_folder->desktop_file == NULL) return GNOME_VFS_ERROR_NOT_FOUND; /* "move" the desktop file */ g_free (new_folder->desktop_file); new_folder->desktop_file = old_folder->desktop_file; old_folder->desktop_file = NULL; /* is this too drastic, it will requery the folder? */ new_folder->up_to_date = FALSE; old_folder->up_to_date = FALSE; emit_monitor (new_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); return GNOME_VFS_OK; } static gboolean is_sub (Folder *master, Folder *sub) { GSList *li; for (li = master->subfolders; li != NULL; li = li->next) { Folder *subfolder = li->data; if (subfolder == sub || is_sub (subfolder, sub)) return TRUE; } return FALSE; } static GnomeVFSResult move_folder (VFolderInfo *info, Folder *old_folder, Entry *old_entry, Folder *new_folder, Entry *new_entry) { Folder *source = (Folder *)old_entry; Folder *target; if (new_entry != NULL && new_entry->type != ENTRY_FOLDER) return GNOME_VFS_ERROR_NOT_A_DIRECTORY; if (new_entry != NULL) { target = (Folder *)new_entry; } else { target = new_folder; } /* move to where we are, yay, we're done :) */ if (source->parent == target) return GNOME_VFS_OK; if (source == target || is_sub (source, target)) return GNOME_VFS_ERROR_LOOP; /* this will never happen, but we're paranoid */ if (source->parent == NULL) return GNOME_VFS_ERROR_LOOP; source->parent->subfolders = g_slist_remove (source->parent->subfolders, source); target->subfolders = g_slist_append (target->subfolders, source); source->parent = target; source->up_to_date = FALSE; target->up_to_date = FALSE; emit_monitor (source, GNOME_VFS_MONITOR_EVENT_CHANGED); emit_monitor (target, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); return GNOME_VFS_OK; } static GnomeVFSResult do_move (GnomeVFSMethod *method, GnomeVFSURI *old_uri, GnomeVFSURI *new_uri, gboolean force_replace, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; VFolderInfo *info; Folder *old_folder, *new_folder; Entry *old_entry, *new_entry; gboolean old_is_directory_file, new_is_directory_file; VFolderURI old_vuri, new_vuri; VFOLDER_URI_PARSE (old_uri, &old_vuri); VFOLDER_URI_PARSE (new_uri, &new_vuri); if (old_vuri.file == NULL) return GNOME_VFS_ERROR_INVALID_URI; if (old_vuri.is_all_scheme) return GNOME_VFS_ERROR_READ_ONLY; if (strcmp (old_vuri.scheme, new_vuri.scheme) != 0) return GNOME_VFS_ERROR_NOT_SAME_FILE_SYSTEM; info = get_vfolder_info (old_vuri.scheme, &result, context); if (info == NULL) return result; if (info->read_only) return GNOME_VFS_ERROR_READ_ONLY; old_entry = get_entry (&old_vuri, &old_folder, &old_is_directory_file, &result, context); if (old_entry == NULL) return result; if (old_folder != NULL && old_folder->read_only) return GNOME_VFS_ERROR_READ_ONLY; new_entry = get_entry (&new_vuri, &new_folder, &new_is_directory_file, &result, context); if (new_entry == NULL && new_folder == NULL) return result; if (new_folder != NULL && new_folder->read_only) return GNOME_VFS_ERROR_READ_ONLY; if (new_is_directory_file != old_is_directory_file) { /* this will do another set of lookups * perhaps this can be done in a nicer way, * but is this the common case? I don't think so */ return long_move (method, &old_vuri, &new_vuri, force_replace, context); } if (new_is_directory_file) { g_assert (old_entry != NULL); g_assert (new_entry != NULL); G_LOCK (vfolder_lock); result = move_directory_file (info, (Folder *)old_entry, (Folder *)new_entry); G_UNLOCK (vfolder_lock); return result; } if (old_entry->type == ENTRY_FOLDER) { G_LOCK (vfolder_lock); result = move_folder (info, old_folder, old_entry, new_folder, new_entry); G_UNLOCK (vfolder_lock); return result; } /* move into self, just whack the old one */ if (old_entry == new_entry) { /* same folder */ if (new_folder == old_folder) return GNOME_VFS_OK; if ( ! force_replace) return GNOME_VFS_ERROR_FILE_EXISTS; G_LOCK (vfolder_lock); remove_file (old_folder, old_vuri.file); old_folder->entries = g_slist_remove (old_folder->entries, old_entry); entry_unref (old_entry); emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } /* this is a simple move */ if (new_entry == NULL || new_entry->type == ENTRY_FOLDER) { if (new_entry != NULL) { new_folder = (Folder *)new_entry; } else { /* a file and a totally different one */ if (strcmp (new_vuri.file, old_entry->name) != 0) { /* yay, a long move */ /* this will do another set of lookups * perhaps this can be done in a nicer way, * but is this the common case? I don't think * so */ return long_move (method, &old_vuri, &new_vuri, force_replace, context); } } /* same folder */ if (new_folder == old_folder) return GNOME_VFS_OK; G_LOCK (vfolder_lock); remove_file (old_folder, old_entry->name); add_file (new_folder, old_entry->name); new_folder->entries = g_slist_prepend (new_folder->entries, old_entry); entry_ref (old_entry); new_folder->sorted = FALSE; old_folder->entries = g_slist_remove (old_folder->entries, old_entry); entry_unref (old_entry); emit_monitor (new_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); emit_monitor (old_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } /* do we EVER get here? */ /* this will do another set of lookups * perhaps this can be done in a nicer way, * but is this the common case? I don't think so */ return long_move (method, &old_vuri, &new_vuri, force_replace, context); } static GnomeVFSResult do_unlink (GnomeVFSMethod *method, GnomeVFSURI *uri, GnomeVFSContext *context) { GnomeVFSResult result = GNOME_VFS_OK; Entry *entry; Folder *the_folder; gboolean is_directory_file; VFolderInfo *info; VFolderURI vuri; GSList *li; VFOLDER_URI_PARSE (uri, &vuri); if (vuri.file == NULL) return GNOME_VFS_ERROR_INVALID_URI; if (vuri.is_all_scheme == TRUE) return GNOME_VFS_ERROR_READ_ONLY; info = get_vfolder_info (vuri.scheme, &result, context); if (info == NULL) return result; else if (info->read_only) return GNOME_VFS_ERROR_READ_ONLY; entry = get_entry (&vuri, &the_folder, &is_directory_file, &result, context); if (entry == NULL) return result; else if (the_folder != NULL && the_folder->read_only) return GNOME_VFS_ERROR_READ_ONLY; if (entry->type == ENTRY_FOLDER && is_directory_file) { Folder *folder = (Folder *)entry; if (folder->desktop_file == NULL) return GNOME_VFS_ERROR_NOT_FOUND; G_LOCK (vfolder_lock); g_free (folder->desktop_file); folder->desktop_file = NULL; emit_monitor (folder, GNOME_VFS_MONITOR_EVENT_CHANGED); vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } else if (entry->type == ENTRY_FOLDER) { return GNOME_VFS_ERROR_IS_DIRECTORY; } else if (the_folder == NULL) { return GNOME_VFS_ERROR_NOT_FOUND; } G_LOCK (vfolder_lock); the_folder->entries = g_slist_remove (the_folder->entries, entry); entry_unref (entry); remove_file (the_folder, vuri.file); emit_monitor (the_folder, GNOME_VFS_MONITOR_EVENT_CHANGED); /* evil, we must remove this from the unallocated folders as well * so that it magically doesn't appear there. But it's not so simple. * We only want to remove it if it isn't in that folder already. */ for (li = info->unallocated_folders; li != NULL; li = li->next) { Folder *folder = li->data; GSList *l; /* This is actually really evil since ensuring * an unallocated folder clears all other unallocated * folders in it's wake. I'm not sure it's worth * optimizing however */ ensure_folder_unlocked (info, folder, FALSE /* subfolders */, NULL /* except */, FALSE /* ignore_unallocated */); l = g_slist_find (folder->entries, entry); if (l == NULL) { remove_file (folder, vuri.file); } } emit_file_deleted_monitor (info, entry, the_folder); /* FIXME: if this was a user file and this is the only * reference to it, unlink it. */ vfolder_info_write_user (info); G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } static GnomeVFSResult do_check_same_fs (GnomeVFSMethod *method, GnomeVFSURI *source_uri, GnomeVFSURI *target_uri, gboolean *same_fs_return, GnomeVFSContext *context) { VFolderURI source_vuri, target_vuri; *same_fs_return = FALSE; VFOLDER_URI_PARSE (source_uri, &source_vuri); VFOLDER_URI_PARSE (target_uri, &target_vuri); if (strcmp (source_vuri.scheme, target_vuri.scheme) != 0 || source_vuri.is_all_scheme != target_vuri.is_all_scheme) *same_fs_return = FALSE; else *same_fs_return = TRUE; return GNOME_VFS_OK; } static GnomeVFSResult do_set_file_info (GnomeVFSMethod *method, GnomeVFSURI *uri, const GnomeVFSFileInfo *info, GnomeVFSSetFileInfoMask mask, GnomeVFSContext *context) { VFolderURI vuri; VFOLDER_URI_PARSE (uri, &vuri); if (vuri.file == NULL) return GNOME_VFS_ERROR_INVALID_URI; if (mask & GNOME_VFS_SET_FILE_INFO_NAME) { GnomeVFSResult result = GNOME_VFS_OK; char *dirname = gnome_vfs_uri_extract_dirname (uri); GnomeVFSURI *new_uri = gnome_vfs_uri_dup (uri); G_LOCK (vfolder_lock); g_free (new_uri->text); new_uri->text = g_build_path ("/", dirname, info->name, NULL); G_UNLOCK (vfolder_lock); result = do_move (method, uri, new_uri, FALSE /* force_replace */, context); g_free (dirname); gnome_vfs_uri_unref (new_uri); return result; } else { /* We don't support setting any of this other permission, * times and all that voodoo */ return GNOME_VFS_ERROR_NOT_SUPPORTED; } } static GnomeVFSResult do_monitor_add (GnomeVFSMethod *method, GnomeVFSMethodHandle **method_handle_return, GnomeVFSURI *uri, GnomeVFSMonitorType monitor_type) { VFolderInfo *info; VFolderURI vuri; GnomeVFSResult result; Folder *folder; Entry *entry; GnomeVFSURI *file_uri; FileMonitorHandle *handle; gboolean is_directory_file; VFOLDER_URI_PARSE (uri, &vuri); info = get_vfolder_info (vuri.scheme, &result, NULL); if (info == NULL) return result; if (monitor_type == GNOME_VFS_MONITOR_DIRECTORY) { G_LOCK (vfolder_lock); folder = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, NULL); handle = g_new0 (FileMonitorHandle, 1); handle->refcount = 2; handle->uri = gnome_vfs_uri_dup (uri); handle->dir_monitor = TRUE; handle->handle = NULL; handle->filename = NULL; if (folder == NULL) { handle->exists = FALSE; info->free_folder_monitors = g_slist_prepend (info->free_folder_monitors, handle); } else { handle->exists = TRUE; ((Entry *)folder)->monitors = g_slist_prepend (((Entry *)folder)->monitors, handle); } info->folder_monitors = g_slist_prepend (info->folder_monitors, handle); G_UNLOCK (vfolder_lock); *method_handle_return = (GnomeVFSMethodHandle *) handle; return GNOME_VFS_OK; } else { /* These can't be very nice FILE names */ if (vuri.file == NULL || vuri.ends_in_slash) return GNOME_VFS_ERROR_INVALID_URI; G_LOCK (vfolder_lock); file_uri = desktop_uri_to_file_uri (info, &vuri, &entry, &is_directory_file, NULL /* the_folder */, FALSE, &result, NULL); handle = g_new0 (FileMonitorHandle, 1); handle->refcount = 2; handle->uri = gnome_vfs_uri_dup (uri); handle->dir_monitor = FALSE; handle->handle = NULL; handle->filename = g_strdup (vuri.file); handle->is_directory_file = is_directory_file; info->file_monitors = g_slist_prepend (info->file_monitors, handle); if (file_uri == NULL) { handle->exists = FALSE; info->free_file_monitors = g_slist_prepend (info->free_file_monitors, handle); } else { char *uri_string = gnome_vfs_uri_to_string (file_uri, 0); handle->exists = TRUE; gnome_vfs_monitor_add (&(handle->handle), uri_string, GNOME_VFS_MONITOR_FILE, file_monitor, handle); g_free (uri_string); entry->monitors = g_slist_prepend (entry->monitors, handle); gnome_vfs_uri_unref (file_uri); } *method_handle_return = (GnomeVFSMethodHandle *) handle; G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } } static GnomeVFSResult do_monitor_cancel (GnomeVFSMethod *method, GnomeVFSMethodHandle *method_handle) { FileMonitorHandle *handle; VFolderInfo *info; VFolderURI vuri; GnomeVFSResult result; Folder *folder; GSList *li; handle = (FileMonitorHandle *)method_handle; /* FIXME: is this correct? */ if (method_handle == NULL) return GNOME_VFS_OK; VFOLDER_URI_PARSE (handle->uri, &vuri); info = get_vfolder_info (vuri.scheme, &result, NULL); if (info == NULL) return result; if (handle->dir_monitor) { G_LOCK (vfolder_lock); folder = resolve_folder (info, vuri.path, FALSE /* ignore_basename */, &result, NULL); for (li = info->folder_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h != handle) continue; info->folder_monitors = g_slist_delete_link (info->folder_monitors, li); file_monitor_handle_unref_unlocked (h); break; } if (folder == NULL) { for (li = info->free_folder_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h != handle) continue; info->free_folder_monitors = g_slist_delete_link (info->free_folder_monitors, li); file_monitor_handle_unref_unlocked (h); break; } } else { for (li = ((Entry *)folder)->monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h != handle) continue; ((Entry *)folder)->monitors = g_slist_delete_link (((Entry *)folder)->monitors, li); file_monitor_handle_unref_unlocked (h); break; } } G_UNLOCK (vfolder_lock); return GNOME_VFS_OK; } else { G_LOCK (vfolder_lock); for (li = info->file_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h != handle) continue; info->file_monitors = g_slist_delete_link (info->file_monitors, li); file_monitor_handle_unref_unlocked (h); break; } for (li = info->free_file_monitors; li != NULL; li = li->next) { FileMonitorHandle *h = li->data; if (h != handle) continue; info->free_file_monitors = g_slist_delete_link (info->free_file_monitors, li); file_monitor_handle_unref_unlocked (h); break; } for (li = info->entries; li != NULL; li = li->next) { Entry *e = li->data; GSList *link = g_slist_find (e->monitors, handle); if (link == NULL) continue; link->data = NULL; e->monitors = g_slist_delete_link (e->monitors, link); file_monitor_handle_unref_unlocked (handle); break; } G_UNLOCK (vfolder_lock); /* Note: last unref of our monitor will cancel the * underlying handle */ return GNOME_VFS_OK; } } /* gnome-vfs bureaucracy */ static GnomeVFSMethod method = { sizeof (GnomeVFSMethod), do_open, NULL, /* do_create, */ do_close, do_read, NULL, /* do_write, */ do_seek, do_tell, NULL, /* do_truncate_handle, */ do_open_directory, do_close_directory, do_read_directory, do_get_file_info, do_get_file_info_from_handle, do_is_local, NULL, /* do_make_directory, */ NULL, /* do_remove_directory, */ NULL, /* do_move, */ NULL, /* do_unlink, */ do_check_same_fs, NULL, /* do_set_file_info, */ NULL, /* do_truncate, */ NULL /* find_directory */, NULL /* create_symbolic_link */, do_monitor_add, do_monitor_cancel }; GnomeVFSMethod * vfs_module_init (const char *method_name, const char *args) { parent_method = gnome_vfs_method_get ("file"); if (parent_method == NULL) { g_error ("Could not find 'file' method for gnome-vfs"); return NULL; } return &method; } void vfs_module_shutdown (GnomeVFSMethod *method) { if (infos == NULL) return; g_hash_table_destroy (infos); infos = NULL; }