nautilus/libnautilus/nautilus-directory.c
John Sullivan 10e2b0004b Finished this round of sorting architecture improvements; list view now handles
sorting itself instead of delegating to superclass. Superclass now knows nothing
about sorting.
2000-01-27 03:51:49 +00:00

1295 lines
37 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-directory.c: Mautilus directory model.
Copyright (C) 1999, 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Author: Darin Adler <darin@eazel.com>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "nautilus-directory.h"
#include <stdlib.h>
#include <gtk/gtkmain.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
#include <libgnomevfs/gnome-vfs-types.h>
#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-async-ops.h>
#include <gnome-xml/parser.h>
#include <gnome-xml/tree.h>
#include <gnome-xml/xmlmemory.h>
#include "nautilus-alloc.h"
#include "nautilus-glib-extensions.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-lib-self-check-functions.h"
#include "nautilus-string.h"
#define METAFILE_NAME ".nautilus-metafile.xml"
#define METAFILE_PERMISSIONS (GNOME_VFS_PERM_USER_READ | GNOME_VFS_PERM_USER_WRITE \
| GNOME_VFS_PERM_GROUP_READ | GNOME_VFS_PERM_GROUP_WRITE \
| GNOME_VFS_PERM_OTHER_READ | GNOME_VFS_PERM_OTHER_WRITE)
#define NAUTILUS_DIRECTORY_NAME ".nautilus"
#define METAFILES_DIRECTORY_NAME "metafiles"
#define METAFILES_DIRECTORY_PERMISSIONS (GNOME_VFS_PERM_USER_ALL \
| GNOME_VFS_PERM_GROUP_ALL \
| GNOME_VFS_PERM_OTHER_ALL)
#define METAFILE_SUFFIX ".xml"
#define METAFILE_XML_VERSION "1.0"
static void nautilus_directory_initialize_class (gpointer klass);
static void nautilus_directory_initialize (gpointer object, gpointer klass);
static void nautilus_directory_finalize (GtkObject *object);
static NautilusDirectory *nautilus_directory_new (const char* uri);
static void nautilus_directory_read_metafile (NautilusDirectory *directory);
static void nautilus_directory_write_metafile (NautilusDirectory *directory);
static void nautilus_directory_request_write_metafile (NautilusDirectory *directory);
static void nautilus_directory_remove_write_metafile_idle (NautilusDirectory *directory);
static void nautilus_file_detach (NautilusFile *file);
static int nautilus_file_compare_for_sort_internal (NautilusFile *file_1,
NautilusFile *file_2,
NautilusFileSortType sort_type,
gboolean reversed);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusDirectory, nautilus_directory, GTK_TYPE_OBJECT)
struct _NautilusDirectoryDetails
{
char *uri_text;
GnomeVFSURI *uri;
GnomeVFSURI *metafile_uri;
GnomeVFSURI *alternate_metafile_uri;
gboolean use_alternate_metafile;
xmlDoc *metafile_tree;
int write_metafile_idle_id;
NautilusFileList *files;
};
struct _NautilusFile
{
guint ref_count;
NautilusDirectory *directory;
GnomeVFSFileInfo *info;
};
static GHashTable* directory_objects;
static void
nautilus_directory_initialize_class (gpointer klass)
{
GtkObjectClass *object_class;
object_class = GTK_OBJECT_CLASS (klass);
object_class->finalize = nautilus_directory_finalize;
}
static void
nautilus_directory_initialize (gpointer object, gpointer klass)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY(object);
directory->details = g_new0 (NautilusDirectoryDetails, 1);
}
static void
nautilus_directory_finalize (GtkObject *object)
{
NautilusDirectory *directory;
directory = NAUTILUS_DIRECTORY (object);
if (directory->details->write_metafile_idle_id != 0)
nautilus_directory_write_metafile (directory);
g_hash_table_remove (directory_objects, directory->details->uri_text);
/* Let go of all the files. */
while (directory->details->files != NULL) {
nautilus_file_detach (directory->details->files->data);
directory->details->files = g_list_remove_link
(directory->details->files, directory->details->files);
}
g_free (directory->details->uri_text);
if (directory->details->uri != NULL)
gnome_vfs_uri_unref (directory->details->uri);
if (directory->details->metafile_uri != NULL)
gnome_vfs_uri_unref (directory->details->metafile_uri);
if (directory->details->alternate_metafile_uri != NULL)
gnome_vfs_uri_unref (directory->details->alternate_metafile_uri);
xmlFreeDoc (directory->details->metafile_tree);
g_free (directory->details);
NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, finalize, (object));
}
/**
* nautilus_directory_get:
* @uri: URI of directory to get.
*
* Get a directory given a uri.
* Creates the appropriate subclass given the uri mappings.
* Returns a referenced object, not a floating one. Unref when finished.
* If two windows are viewing the same uri, the directory object is shared.
*/
NautilusDirectory *
nautilus_directory_get (const char *uri)
{
NautilusDirectory *directory;
g_return_val_if_fail (uri != NULL, NULL);
/* FIXME: This currently ignores the issue of two uris that are not identical but point
to the same data.
*/
/* Create the hash table first time through. */
if (!directory_objects)
directory_objects = g_hash_table_new (g_str_hash, g_str_equal);
/* If the object is already in the hash table, look it up. */
directory = g_hash_table_lookup (directory_objects, uri);
if (directory != NULL) {
g_assert (NAUTILUS_IS_DIRECTORY (directory));
gtk_object_ref (GTK_OBJECT (directory));
} else {
/* Create a new directory object instead. */
directory = NAUTILUS_DIRECTORY (nautilus_directory_new (uri));
if(!directory)
return NULL;
g_assert (strcmp (directory->details->uri_text, uri) == 0);
/* Put it in the hash table. */
gtk_object_ref (GTK_OBJECT (directory));
gtk_object_sink (GTK_OBJECT (directory));
g_hash_table_insert (directory_objects, directory->details->uri_text, directory);
}
return directory;
}
char *
nautilus_directory_get_uri (NautilusDirectory *directory)
{
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
return g_strdup (directory->details->uri_text);
}
/* This reads the metafile synchronously. This must go eventually.
To do this asynchronously we'd need a way to read an entire file
with async. calls; currently you can only get the file length with
a synchronous call.
*/
static GnomeVFSResult
nautilus_directory_try_to_read_metafile (NautilusDirectory *directory, GnomeVFSURI *metafile_uri)
{
GnomeVFSResult result;
GnomeVFSFileInfo metafile_info;
GnomeVFSHandle *metafile_handle;
GnomeVFSFileSize size, actual_size;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), GNOME_VFS_ERROR_GENERIC);
g_return_val_if_fail (directory->details->metafile_tree == NULL, GNOME_VFS_ERROR_GENERIC);
result = gnome_vfs_get_file_info_uri (metafile_uri,
&metafile_info,
GNOME_VFS_FILE_INFO_DEFAULT,
NULL);
metafile_handle = NULL;
if (result == GNOME_VFS_OK)
result = gnome_vfs_open_uri (&metafile_handle,
metafile_uri,
GNOME_VFS_OPEN_READ);
if (result == GNOME_VFS_OK) {
size = metafile_info.size;
if (size != metafile_info.size)
result = GNOME_VFS_ERROR_TOOBIG;
}
if (result == GNOME_VFS_OK) {
char *buffer;
buffer = g_alloca(size);
result = gnome_vfs_read (metafile_handle, buffer, size, &actual_size);
buffer[size] = '\0';
directory->details->metafile_tree = xmlParseMemory (buffer, actual_size);
}
if (metafile_handle != NULL)
gnome_vfs_close (metafile_handle);
return result;
}
static void
nautilus_directory_read_metafile (NautilusDirectory *directory)
{
GnomeVFSResult result;
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
/* Check for the alternate metafile first.
If we read from it, then write to it later.
*/
directory->details->use_alternate_metafile = FALSE;
result = nautilus_directory_try_to_read_metafile (directory, directory->details->alternate_metafile_uri);
if (result == GNOME_VFS_OK)
directory->details->use_alternate_metafile = TRUE;
else
result = nautilus_directory_try_to_read_metafile (directory, directory->details->metafile_uri);
/* Check for errors. Later this must be reported to the user, not spit out as a warning. */
if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_NOTFOUND)
g_warning ("nautilus_directory_read_metafile failed to read metafile - we should report this to the user");
}
static void
nautilus_directory_remove_write_metafile_idle (NautilusDirectory *directory)
{
if (directory->details->write_metafile_idle_id != 0) {
gtk_idle_remove (directory->details->write_metafile_idle_id);
directory->details->write_metafile_idle_id = 0;
}
}
/* This writes the metafile synchronously. This must go eventually. */
static GnomeVFSResult
nautilus_directory_try_to_write_metafile (NautilusDirectory *directory, GnomeVFSURI *metafile_uri)
{
xmlChar *buffer;
int buffer_size;
GnomeVFSResult result;
GnomeVFSHandle *metafile_handle;
GnomeVFSFileSize actual_size;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), GNOME_VFS_ERROR_GENERIC);
g_return_val_if_fail (directory->details != NULL, GNOME_VFS_ERROR_GENERIC);
g_return_val_if_fail (directory->details->metafile_tree != NULL, GNOME_VFS_ERROR_GENERIC);
metafile_handle = NULL;
result = gnome_vfs_create_uri (&metafile_handle,
metafile_uri,
GNOME_VFS_OPEN_WRITE,
FALSE,
METAFILE_PERMISSIONS);
buffer = NULL;
if (result == GNOME_VFS_OK) {
xmlDocDumpMemory (directory->details->metafile_tree, &buffer, &buffer_size);
result = gnome_vfs_write (metafile_handle, buffer, buffer_size, &actual_size);
if (buffer_size != actual_size)
result = GNOME_VFS_ERROR_GENERIC;
}
if (metafile_handle != NULL)
gnome_vfs_close (metafile_handle);
xmlFree (buffer);
return result;
}
static void
nautilus_directory_write_metafile (NautilusDirectory *directory)
{
GnomeVFSResult result;
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
/* We are about the write the metafile, so we can cancel the pending
request to do so.
*/
nautilus_directory_remove_write_metafile_idle (directory);
/* Don't write anything if there's nothing to write.
At some point, we might want to change this to actually delete
the metafile in this case.
*/
if (directory->details->metafile_tree == NULL)
return;
/* Try the main URI, unless we have already been instructed to use the alternate URI. */
if (directory->details->use_alternate_metafile)
result = GNOME_VFS_ERROR_ACCESSDENIED;
else
result = nautilus_directory_try_to_write_metafile (directory, directory->details->metafile_uri);
/* Try the alternate URI if the main one failed. */
if (result != GNOME_VFS_OK)
result = nautilus_directory_try_to_write_metafile (directory, directory->details->alternate_metafile_uri);
/* Check for errors. Later this must be reported to the user, not spit out as a warning. */
if (result != GNOME_VFS_OK)
g_warning ("nautilus_directory_write_metafile failed to write metafile - we should report this to the user");
}
static gboolean
nautilus_directory_write_metafile_on_idle (gpointer data)
{
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (data), FALSE);
nautilus_directory_write_metafile (data);
return FALSE;
}
static void
nautilus_directory_request_write_metafile (NautilusDirectory *directory)
{
/* Set up an idle task that will write the metafile. */
if (directory->details->write_metafile_idle_id == 0)
directory->details->write_metafile_idle_id =
gtk_idle_add (nautilus_directory_write_metafile_on_idle,
directory);
}
/* To use a directory name as a file name, we need to escape any slashes.
This means that "/" is replaced by "%2F" and "%" is replaced by "%25".
Later we might share the escaping code with some more generic escaping
function, but this should do for now.
*/
static char *
nautilus_directory_escape_slashes (const char *path)
{
char c;
const char *in;
guint length;
char *result;
char *out;
/* Figure out how long the result needs to be. */
in = path;
length = 0;
while ((c = *in++) != '\0')
switch (c) {
case '/':
case '%':
length += 3;
break;
default:
length += 1;
}
/* Create the result string. */
result = g_malloc (length + 1);
in = path;
out = result;
while ((c = *in++) != '\0')
switch (c) {
case '/':
*out++ = '%';
*out++ = '2';
*out++ = 'F';
break;
case '%':
*out++ = '%';
*out++ = '2';
*out++ = '5';
break;
default:
*out++ = c;
}
g_assert (out == result + length);
*out = '\0';
return result;
}
static GnomeVFSResult
nautilus_make_directory_and_parents (GnomeVFSURI *uri, guint permissions)
{
GnomeVFSResult result;
GnomeVFSURI *parent_uri;
/* Make the directory, and return right away unless there's
a possible problem with the parent.
*/
result = gnome_vfs_make_directory_for_uri (uri, permissions);
if (result != GNOME_VFS_ERROR_NOTFOUND)
return result;
/* If we can't get a parent, we are done. */
parent_uri = gnome_vfs_uri_get_parent (uri);
if (parent_uri == NULL)
return result;
/* If we can get a parent, use a recursive call to create
the parent and its parents.
*/
result = nautilus_make_directory_and_parents (parent_uri, permissions);
gnome_vfs_uri_unref (parent_uri);
if (result != GNOME_VFS_OK)
return result;
/* A second try at making the directory after the parents
have all been created.
*/
result = gnome_vfs_make_directory_for_uri (uri, permissions);
return result;
}
static GnomeVFSURI *
nautilus_directory_construct_alternate_metafile_uri (GnomeVFSURI *metafile_uri)
{
GnomeVFSResult result;
GnomeVFSURI *home_uri, *nautilus_directory_uri, *metafiles_directory_uri, *alternate_uri;
char *uri_as_string, *escaped_uri, *file_name;
/* Ensure that the metafiles directory exists. */
home_uri = gnome_vfs_uri_new (g_get_home_dir ());
nautilus_directory_uri = gnome_vfs_uri_append_path (home_uri, NAUTILUS_DIRECTORY_NAME);
gnome_vfs_uri_unref (home_uri);
metafiles_directory_uri = gnome_vfs_uri_append_path (nautilus_directory_uri, METAFILES_DIRECTORY_NAME);
gnome_vfs_uri_unref (nautilus_directory_uri);
result = nautilus_make_directory_and_parents (metafiles_directory_uri, METAFILES_DIRECTORY_PERMISSIONS);
if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_FILEEXISTS) {
gnome_vfs_uri_unref (metafiles_directory_uri);
return NULL;
}
/* Construct a file name from the URI. */
uri_as_string = gnome_vfs_uri_to_string (metafile_uri,
GNOME_VFS_URI_HIDE_NONE);
escaped_uri = nautilus_directory_escape_slashes (uri_as_string);
g_free (uri_as_string);
file_name = g_strconcat (escaped_uri, ".xml", NULL);
g_free (escaped_uri);
/* Construct a URI for something in the "metafiles" directory. */
alternate_uri = gnome_vfs_uri_append_path (metafiles_directory_uri, file_name);
gnome_vfs_uri_unref (metafiles_directory_uri);
g_free (file_name);
return alternate_uri;
}
#if NAUTILUS_DIRECTORY_ASYNC
static void
nautilus_directory_opened_metafile (GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data)
{
}
result = gnome_vfs_async_open_uri (&metafile_handle, metafile_uri, GNOME_VFS_OPEN_READ,
nautilus_directory_opened_metafile, directory);
#endif
static NautilusDirectory *
nautilus_directory_new (const char* uri)
{
NautilusDirectory *directory;
GnomeVFSURI *vfs_uri;
GnomeVFSURI *metafile_uri;
GnomeVFSURI *alternate_metafile_uri;
vfs_uri = gnome_vfs_uri_new (uri);
if (vfs_uri == NULL)
return NULL;
metafile_uri = gnome_vfs_uri_append_path (vfs_uri, METAFILE_NAME);
alternate_metafile_uri = nautilus_directory_construct_alternate_metafile_uri (metafile_uri);
directory = gtk_type_new (NAUTILUS_TYPE_DIRECTORY);
directory->details->uri_text = g_strdup(uri);
directory->details->uri = vfs_uri;
directory->details->metafile_uri = metafile_uri;
directory->details->alternate_metafile_uri = alternate_metafile_uri;
nautilus_directory_read_metafile (directory);
return directory;
}
void
nautilus_directory_get_files (NautilusDirectory *directory,
NautilusFileListCallback callback,
gpointer callback_data)
{
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
g_return_if_fail (callback != NULL);
if (directory->details->files != NULL)
(* callback) (directory,
directory->details->files,
callback_data);
}
static char *
nautilus_directory_get_metadata_from_node (xmlNode *node,
const char *tag,
const char *default_metadata)
{
xmlChar *property;
char *result;
g_return_val_if_fail (tag, NULL);
g_return_val_if_fail (tag[0], NULL);
property = xmlGetProp (node, tag);
if (property == NULL)
result = g_strdup (default_metadata);
else
result = g_strdup (property);
xmlFree (property);
return result;
}
static xmlNode *
nautilus_directory_create_metafile_tree_root (NautilusDirectory *directory)
{
xmlNode *root;
if (directory->details->metafile_tree == NULL)
directory->details->metafile_tree = xmlNewDoc (METAFILE_XML_VERSION);
root = xmlDocGetRootElement (directory->details->metafile_tree);
if (root == NULL) {
root = xmlNewDocNode (directory->details->metafile_tree, NULL, "DIRECTORY", NULL);
xmlDocSetRootElement (directory->details->metafile_tree, root);
}
return root;
}
char *
nautilus_directory_get_metadata (NautilusDirectory *directory,
const char *tag,
const char *default_metadata)
{
/* It's legal to call this on a NULL directory. */
if (directory == NULL)
return NULL;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
/* The root itself represents the directory. */
return nautilus_directory_get_metadata_from_node
(xmlDocGetRootElement (directory->details->metafile_tree),
tag, default_metadata);
}
void
nautilus_directory_set_metadata (NautilusDirectory *directory,
const char *tag,
const char *default_metadata,
const char *metadata)
{
char *old_metadata;
gboolean old_metadata_matches;
xmlNode *root;
const char *value;
xmlAttr *property_node;
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
g_return_if_fail (tag);
g_return_if_fail (tag[0]);
/* If the data in the metafile is already correct, do nothing. */
old_metadata = nautilus_directory_get_metadata (directory, tag, default_metadata);
old_metadata_matches = nautilus_strcmp (old_metadata, metadata) == 0;
g_free (old_metadata);
if (old_metadata_matches)
return;
/* Data that matches the default is represented in the tree by
the lack of an attribute.
*/
if (nautilus_strcmp (default_metadata, metadata) == 0)
value = NULL;
else
value = metadata;
/* Get at the tree. */
root = nautilus_directory_create_metafile_tree_root (directory);
/* Add or remove an attribute node. */
property_node = xmlSetProp (root, tag, value);
if (value == NULL)
xmlRemoveProp (property_node);
/* Since we changed the tree, arrange for it to be written. */
nautilus_directory_request_write_metafile (directory);
}
static char *
nautilus_directory_get_file_metadata (NautilusDirectory *directory,
const char *file_name,
const char *tag,
const char *default_metadata)
{
xmlNode *root, *child;
xmlChar *property;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
/* The root itself represents the directory. */
root = xmlDocGetRootElement (directory->details->metafile_tree);
if (root == NULL)
return g_strdup (default_metadata);
/* The children represent the files.
This linear search is temporary.
Eventually, we'll have a pointer from the NautilusFile right to
the corresponding XML node, or we won't have the XML tree
in memory at all.
*/
for (child = root->childs; child != NULL; child = child->next)
if (strcmp (child->name, "FILE") == 0) {
property = xmlGetProp (child, "NAME");
if (nautilus_eat_strcmp (property, file_name) == 0)
break;
}
/* If we found a child, we can get the data from it. */
return nautilus_directory_get_metadata_from_node
(child, tag, default_metadata);
}
static void
nautilus_directory_set_file_metadata (NautilusDirectory *directory,
const char *file_name,
const char *tag,
const char *default_metadata,
const char *metadata)
{
char *old_metadata;
gboolean old_metadata_matches;
xmlNode *root, *child;
const char *value;
xmlChar *property;
xmlAttr *property_node;
g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory));
g_return_if_fail (tag);
g_return_if_fail (tag[0]);
/* If the data in the metafile is already correct, do nothing. */
old_metadata = nautilus_directory_get_file_metadata
(directory, file_name, tag, default_metadata);
old_metadata_matches = nautilus_strcmp (old_metadata, metadata) == 0;
g_free (old_metadata);
if (old_metadata_matches)
return;
/* Data that matches the default is represented in the tree by
the lack of an attribute.
*/
if (nautilus_strcmp (default_metadata, metadata) == 0)
value = NULL;
else
value = metadata;
/* The root itself represents the directory. */
root = nautilus_directory_create_metafile_tree_root (directory);
/* The children represent the files.
This linear search is temporary.
Eventually, we'll have a pointer from the NautilusFile right to
the corresponding XML node, or we won't have the XML tree
in memory at all.
*/
for (child = root->childs; child != NULL; child = child->next)
if (strcmp (child->name, "FILE") == 0) {
property = xmlGetProp (child, "NAME");
if (nautilus_eat_strcmp (property, file_name) == 0)
break;
}
if (child == NULL) {
g_assert (value != NULL);
child = xmlNewChild (root, NULL, "FILE", NULL);
xmlSetProp (child, "NAME", file_name);
}
/* Add or remove an attribute node. */
property_node = xmlSetProp (child, tag, value);
if (value == NULL)
xmlRemoveProp (property_node);
/* Since we changed the tree, arrange for it to be written. */
nautilus_directory_request_write_metafile (directory);
}
NautilusFile *
nautilus_directory_new_file (NautilusDirectory *directory, GnomeVFSFileInfo *info)
{
NautilusFile *file;
g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
g_return_val_if_fail (info != NULL, NULL);
gnome_vfs_file_info_ref (info);
file = g_new (NautilusFile, 1);
file->ref_count = 0;
file->directory = directory;
file->info = info;
directory->details->files = g_list_prepend (directory->details->files, file);
return file;
}
/**
* nautilus_file_get:
* @uri: URI of file to get.
*
* Get a file given a uri.
* Returns a referenced object. Unref when finished.
* If two windows are viewing the same uri, the file object is shared.
*/
NautilusFile *
nautilus_file_get (const char *uri)
{
GnomeVFSResult result;
GnomeVFSFileInfo *file_info;
GnomeVFSURI *vfs_uri, *directory_vfs_uri;
char *directory_uri;
NautilusDirectory *directory;
NautilusFile *file;
/* Get info on the file. */
file_info = gnome_vfs_file_info_new ();
result = gnome_vfs_get_file_info (uri, file_info,
GNOME_VFS_FILE_INFO_FASTMIMETYPE, NULL);
if (result != GNOME_VFS_OK)
return NULL;
/* Make VFS version of URI. */
vfs_uri = gnome_vfs_uri_new (uri);
if (vfs_uri == NULL)
return NULL;
/* Make VFS version of directory URI. */
directory_vfs_uri = gnome_vfs_uri_get_parent (vfs_uri);
gnome_vfs_uri_unref (vfs_uri);
if (directory_vfs_uri == NULL)
return NULL;
/* Make text version of directory URI. */
directory_uri = gnome_vfs_uri_to_string (directory_vfs_uri,
GNOME_VFS_URI_HIDE_NONE);
gnome_vfs_uri_unref (directory_vfs_uri);
/* Get object that represents the directory. */
directory = nautilus_directory_get (directory_uri);
g_free (directory_uri);
if (directory == NULL)
return NULL;
file = nautilus_directory_new_file (directory, file_info);
gnome_vfs_file_info_unref (file_info);
nautilus_file_ref (file);
gtk_object_unref (GTK_OBJECT (directory));
return file;
}
void
nautilus_file_ref (NautilusFile *file)
{
g_return_if_fail (file != NULL);
g_assert (file->ref_count < UINT_MAX);
g_assert (file->directory != NULL);
/* Increment the ref count. */
if (file->ref_count++ != 0)
return;
/* As soon as someone other than the directory holds a ref,
* we need to hold the directory too. */
gtk_object_ref (GTK_OBJECT (file->directory));
}
void
nautilus_file_unref (NautilusFile *file)
{
g_return_if_fail (file != NULL);
g_assert (file->ref_count != 0);
g_assert (file->directory != NULL);
/* Decrement the ref count. */
if (--file->ref_count != 0)
return;
/* No references left, so it's time to release our hold on the directory. */
gtk_object_unref (GTK_OBJECT (file->directory));
}
static void
nautilus_file_detach (NautilusFile *file)
{
g_assert (file->ref_count == 0);
/* Destroy the file object. */
gnome_vfs_file_info_unref (file->info);
}
static int
nautilus_file_compare_for_sort_internal (NautilusFile *file_1,
NautilusFile *file_2,
NautilusFileSortType sort_type,
gboolean reversed)
{
GnomeVFSDirectorySortRule *rules;
g_return_val_if_fail (file_1 != NULL, 0);
g_return_val_if_fail (file_2 != NULL, 0);
g_return_val_if_fail (sort_type != NAUTILUS_FILE_SORT_NONE, 0);
#define ALLOC_RULES(n) alloca ((n) * sizeof (GnomeVFSDirectorySortRule))
switch (sort_type) {
case NAUTILUS_FILE_SORT_BY_NAME:
rules = ALLOC_RULES (2);
/* Note: This used to put directories first. I
* thought that was counterintuitive and removed it,
* but I can imagine discussing this further.
* John Sullivan <sullivan@eazel.com>
*/
rules[0] = GNOME_VFS_DIRECTORY_SORT_BYNAME_IGNORECASE;
rules[1] = GNOME_VFS_DIRECTORY_SORT_NONE;
break;
case NAUTILUS_FILE_SORT_BY_SIZE:
rules = ALLOC_RULES (4);
rules[0] = GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST;
rules[1] = GNOME_VFS_DIRECTORY_SORT_BYSIZE;
rules[2] = GNOME_VFS_DIRECTORY_SORT_BYNAME_IGNORECASE;
rules[3] = GNOME_VFS_DIRECTORY_SORT_NONE;
break;
case NAUTILUS_FILE_SORT_BY_TYPE:
rules = ALLOC_RULES (4);
rules[0] = GNOME_VFS_DIRECTORY_SORT_DIRECTORYFIRST;
rules[1] = GNOME_VFS_DIRECTORY_SORT_BYMIMETYPE;
rules[2] = GNOME_VFS_DIRECTORY_SORT_BYNAME_IGNORECASE;
rules[3] = GNOME_VFS_DIRECTORY_SORT_NONE;
break;
case NAUTILUS_FILE_SORT_BY_MTIME:
rules = ALLOC_RULES (3);
rules[0] = GNOME_VFS_DIRECTORY_SORT_BYMTIME;
rules[1] = GNOME_VFS_DIRECTORY_SORT_BYNAME_IGNORECASE;
rules[2] = GNOME_VFS_DIRECTORY_SORT_NONE;
break;
default:
g_assert_not_reached ();
return 0;
}
if (reversed)
return gnome_vfs_file_info_compare_for_sort_reversed (file_1->info,
file_2->info,
rules);
else
return gnome_vfs_file_info_compare_for_sort (file_1->info,
file_2->info,
rules);
#undef ALLOC_RULES
}
/**
* nautilus_file_compare_for_sort:
* @file_1: A file object
* @file_2: Another file object
* @sort_type: Sort criterion
*
* Return value: int < 0 if @file_1 should come before file_2 in a smallest-to-largest
* sorted list; int > 0 if @file_2 should come before file_1 in a smallest-to-largest
* sorted list; 0 if @file_1 and @file_2 are equal for this sort criterion. Note
* that each named sort type may actually break ties several ways, with the name
* of the sort criterion being the primary but not only differentiator.
**/
int
nautilus_file_compare_for_sort (NautilusFile *file_1,
NautilusFile *file_2,
NautilusFileSortType sort_type)
{
return nautilus_file_compare_for_sort_internal (file_1, file_2, sort_type, FALSE);
}
/**
* nautilus_file_compare_for_sort_reversed:
* @file_1: A file object
* @file_2: Another file object
* @sort_type: Sort criterion
*
* Return value: The opposite of nautilus_file_compare_for_sort: int > 0 if @file_1
* should come before file_2 in a smallest-to-largest sorted list; int < 0 if @file_2
* should come before file_1 in a smallest-to-largest sorted list; 0 if @file_1
* and @file_2 are equal for this sort criterion. Note that each named sort type
* may actually break ties several ways, with the name of the sort criterion
* being the primary but not only differentiator.
**/
int
nautilus_file_compare_for_sort_reversed (NautilusFile *file_1,
NautilusFile *file_2,
NautilusFileSortType sort_type)
{
return nautilus_file_compare_for_sort_internal (file_1, file_2, sort_type, TRUE);
}
char *
nautilus_file_get_metadata (NautilusFile *file,
const char *tag,
const char *default_metadata)
{
g_return_val_if_fail (file != NULL, NULL);
return nautilus_directory_get_file_metadata (file->directory, file->info->name, tag, default_metadata);
}
void
nautilus_file_set_metadata (NautilusFile *file,
const char *tag,
const char *default_metadata,
const char *metadata)
{
g_return_if_fail (file != NULL);
nautilus_directory_set_file_metadata (file->directory, file->info->name, tag, default_metadata, metadata);
}
char *
nautilus_file_get_name (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
g_assert (file->directory == NULL || NAUTILUS_IS_DIRECTORY (file->directory));
g_assert (file->info->name != NULL);
g_assert (file->info->name[0] != '\0');
return g_strdup (file->info->name);
}
char *
nautilus_file_get_uri (NautilusFile *file)
{
GnomeVFSURI *uri;
char *uri_text;
g_return_val_if_fail (file != NULL, NULL);
g_return_val_if_fail (file->directory != NULL, NULL);
uri = gnome_vfs_uri_append_path (file->directory->details->uri, file->info->name);
uri_text = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
gnome_vfs_uri_unref (uri);
return uri_text;
}
/**
* nautilus_file_get_date_as_string:
*
* Get a user-displayable string representing a file modification date.
* The caller is responsible for g_free-ing this string.
* @file: NautilusFile representing the file in question.
*
* Returns: Newly allocated string ready to display to the user.
*
**/
gchar *
nautilus_file_get_date_as_string (NautilusFile *file)
{
/* Note: This uses modified time. There's also accessed time and
* changed time. Accessed time doesn't seem worth showing to the user.
* Changed time is only subtly different from modified time
* (changed time includes "metadata" changes like file permissions).
* We should not display both, but we might change our minds as to
* which one is better.
*/
struct tm *file_time;
const char *format;
GDate *today;
GDate *file_date;
guint32 file_date_age;
g_return_val_if_fail (file != NULL, NULL);
file_time = localtime(&file->info->mtime);
file_date = nautilus_g_date_new_tm (file_time);
today = g_date_new ();
g_date_set_time (today, time (NULL));
/* Overflow results in a large number; fine for our purposes. */
file_date_age = g_date_julian (today) - g_date_julian (file_date);
g_date_free (file_date);
g_date_free (today);
/* Format varies depending on how old the date is. This minimizes
* the length (and thus clutter & complication) of typical dates
* while providing sufficient detail for recent dates to make
* them maximally understandable at a glance. Keep all format
* strings separate rather than combining bits & pieces for
* internationalization's sake.
*/
if (file_date_age == 0)
{
/* today, use special word */
format = _("today %-I:%M %p");
}
else if (file_date_age == 1)
{
/* yesterday, use special word */
format = _("yesterday %-I:%M %p");
}
else if (file_date_age < 7)
{
/* current week, include day of week */
format = _("%A %-m/%-d/%y %-I:%M %p");
}
else
{
format = _("%-m/%-d/%y %-I:%M %p");
}
return nautilus_strdup_strftime (format, file_time);
}
/**
* nautilus_file_get_size
*
* Get the file size.
* @file: NautilusFile representing the file in question.
*
* Returns: Size in bytes.
*
**/
GnomeVFSFileSize
nautilus_file_get_size (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, 0);
return file->info->size;
}
/**
* nautilus_file_get_size_as_string:
*
* Get a user-displayable string representing a file size. The caller
* is responsible for g_free-ing this string.
* @file: NautilusFile representing the file in question.
*
* Returns: Newly allocated string ready to display to the user.
*
**/
gchar *
nautilus_file_get_size_as_string (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
if (file->info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
return g_strdup (_("--"));
return gnome_vfs_file_size_to_string (file->info->size);
}
/**
* nautilus_file_get_type_as_string:
*
* Get a user-displayable string representing a file type. The caller
* is responsible for g_free-ing this string.
* @file: NautilusFile representing the file in question.
*
* Returns: Newly allocated string ready to display to the user.
*
**/
gchar *
nautilus_file_get_type_as_string (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
if (file->info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
/* Special-case this so it isn't "special/directory".
* Should this be "folder" instead?
*/
return g_strdup (_("directory"));
return g_strdup (file->info->mime_type);
}
/**
* nautilus_file_get_type
*
* Return this file's type.
* @file: NautilusFile representing the file in question.
*
* Returns: The type.
*
**/
GnomeVFSFileType
nautilus_file_get_type (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, FALSE);
return file->info->type;
}
/**
* nautilus_file_get_mime_type
*
* Return this file's mime type.
* @file: NautilusFile representing the file in question.
*
* Returns: The mime type.
*
**/
const char *
nautilus_file_get_mime_type (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, FALSE);
return file->info->mime_type;
}
/**
* nautilus_file_is_symbolic_link
*
* Check if this file is a symbolic link.
* @file: NautilusFile representing the file in question.
*
* Returns: True if the file is a symbolic link.
*
**/
gboolean
nautilus_file_is_symbolic_link (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, FALSE);
return GNOME_VFS_FILE_INFO_SYMLINK (file->info);
}
/**
* nautilus_file_is_executable
*
* Check if this file is executable at all.
* @file: NautilusFile representing the file in question.
*
* Returns: True if any of the execute bits are set.
*
**/
gboolean
nautilus_file_is_executable (NautilusFile *file)
{
g_return_val_if_fail (file != NULL, FALSE);
return (file->info->flags & (GNOME_VFS_PERM_USER_EXEC
| GNOME_VFS_PERM_GROUP_EXEC
| GNOME_VFS_PERM_OTHER_EXEC)) != 0;
}
#if !defined (NAUTILUS_OMIT_SELF_CHECK)
static int data_dummy;
static guint file_count;
static void
get_files_cb (NautilusDirectory *directory, NautilusFileList *files, gpointer data)
{
g_assert (NAUTILUS_IS_DIRECTORY (directory));
g_assert (files);
g_assert (data == &data_dummy);
file_count += g_list_length (files);
}
void
nautilus_self_check_directory (void)
{
NautilusDirectory *directory;
NautilusFile *file_1;
NautilusFile *file_2;
directory = nautilus_directory_get ("file:///etc");
NAUTILUS_CHECK_INTEGER_RESULT (g_hash_table_size (directory_objects), 1);
file_count = 0;
nautilus_directory_get_files (directory, get_files_cb, &data_dummy);
nautilus_directory_set_metadata (directory, "TEST", "default", "value");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_get_metadata (directory, "TEST", "default"), "value");
gtk_object_unref (GTK_OBJECT (directory));
NAUTILUS_CHECK_INTEGER_RESULT (g_hash_table_size (directory_objects), 0);
directory = nautilus_directory_get ("file:///etc");
NAUTILUS_CHECK_INTEGER_RESULT (g_hash_table_size (directory_objects), 1);
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_get_metadata (directory, "TEST", "default"), "value");
/* nautilus_directory_escape_slashes */
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes (""), "");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("a"), "a");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("/"), "%2F");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("%"), "%25");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("a/a"), "a%2Fa");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("a%a"), "a%25a");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("%25"), "%2525");
NAUTILUS_CHECK_STRING_RESULT (nautilus_directory_escape_slashes ("%2F"), "%252F");
/* sorting */
file_1 = nautilus_file_get ("file:///etc");
file_2 = nautilus_file_get ("file:///usr");
NAUTILUS_CHECK_INTEGER_RESULT (file_1->ref_count, 1);
NAUTILUS_CHECK_INTEGER_RESULT (file_2->ref_count, 1);
NAUTILUS_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_NAME) < 0, TRUE);
NAUTILUS_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort_reversed (file_1, file_2, NAUTILUS_FILE_SORT_BY_NAME) > 0, TRUE);
NAUTILUS_CHECK_BOOLEAN_RESULT (nautilus_file_compare_for_sort (file_1, file_1, NAUTILUS_FILE_SORT_BY_NAME) == 0, TRUE);
}
#endif /* !NAUTILUS_OMIT_SELF_CHECK */