/*

File File.m

Release 1.2, 7 June 1994
Copyright (C) 1994 by H. Scott Roy

This code is part of IconKit, a general toolbox for drag-and-drop applications.  IconKit is free for noncommercial use, but costs money for a commercial license.  You should have received a copy of the license agreement with this file.  If not, a copy of the license and the complete source of IconKit can be obtained from the author:

		H. Scott Roy
		2573 Stowe Ct.
		Northbrook, IL  60062-8103
		iconkit@cs.stanford.edu

For your editing convenience, this file is best viewed using an editor that automatically wraps long lines, in a fixed point font at 80 columns, with tabs every 4 spaces.

*/


/* ========================================================================== */


/*

These objects represent single files and folders.  The companion class FileList is used to build lists of multiple files, such as might appear in a browser selection.  The workspace is used to find the necessary icons, titles, and file information.  Files are a subclass of IKFolder so that the filesystem can be properly represented as a directed graph.

Every file in the filesystem corresponds to a single node in the File graph.  The graph is constructed lazily, with parts loaded in only as needed.  A hash table is used to match new files dragged in from the workspace to existing nodes.

It would be nice to demonstrate offloading, but that requires some care.  In general, the graph structure will not correspond to the filesystem after the user has dragged around a few things, so we're likely to get some anomolous results reloading parts of the File graph that were changed.

*/

#import <appkit/appkit.h>
#import <string.h>
#import <sys/types.h>
#import <sys/dir.h>
#import <sys/stat.h>

#import "File.h"
#import "FileList.h"

@implementation File


/* ========================================================================== */


/*

Two class variables are needed.  The fileMap keeps track of the correspondence between File objects and filenames in the system.  The draggedImage is used to steal the image for a new File directly from the dragging source.  The workspace croacks if we ask it for images during a dragging session.

*/ 

static NXImage		* draggedImage = nil;
static id			fileMap = nil;


/* ========================================================================== */


/*

Here are the class methods.  The list of paste types only includes NXFilenamePboardType, since that type is sufficient to determine whether the File class can read an object from the pasteboard.

*/


+ initialize
{
	if (self == [File  class])
			fileMap = [[HashTable  alloc]  initKeyDesc: "*"];
	
	return self;
}


+ multipleSelectionClass
{
	return [FileList  class];
}


+ (NXAtom *) pasteTypes
{
	static NXAtom
		pasteTypes[3] = { NULL, NULL, NULL };
	
	pasteTypes[0] = NXFilenamePboardType;
	pasteTypes[1] = IKidPboardType;
	
	return pasteTypes;
}


/* ========================================================================== */


/*

Here are the class methods that map filenames onto File objects.  Every unique path name in the filesystem corresponds to a single node in the File graph, so a hash table is used to make the correspondence.  Note that if the same file is reachable by two different paths, it will be mapped into two distinct File objects--one for each path.

A logical improvement would be to get the inode number, or some such, and cache on that instead.

*/


+ fileForPath: (const char *) thePath
{
	id
		file = [fileMap  valueForKey: thePath];
	
	if (file == nil)
	{
		file = [[File  alloc]  initPath: thePath];
		[fileMap  insertKey: [file  path]  value: file];
	}
	
	return file;
}


+ fileForPathList: (const char *) thePath
{
	id
		fileList;
	char
		paths [MAXPATHLEN],
		* p,
		* q;
	
	if (strchr(thePath, '\t') == NULL) fileList = [File  fileForPath: thePath];
	else
	{
		fileList = [[FileList  alloc]  init];
		p = paths;
		strcpy(p, thePath);
		
		while (p != NULL)
		{
			if ((q = strchr(p, '\t')) != NULL) *q++ = '\0';
			[fileList  addObject: [File  fileForPath: p]];
			p = q;
		}
	}
	
	return fileList;
}


/* ========================================================================== */


/*

Here is the initialization method.  The images, parents, and children of a File are all loaded lazily to improve performance.  Note that a File object initially only has access to a single parent; namely, the one that it can construct from its path.  A File object installs itself in the parent list of all its children when it loads them in.

*/

- initPath: (const char *) thePath
{
	struct stat
		status;
	char
		host [MAXNAMLEN + 1], * title = rindex(thePath, '/');
	
	if (title == NULL) return nil;
	else if (title[1] == '\0') gethostname(title = host, MAXNAMLEN);
	else title++;
	
	if ((self = [super  init: title]) != nil)
	{
		stat(thePath, &status);
		path = NXCopyStringBuffer(thePath);
		
		flags.dragAccepting = (status.st_mode & S_IFDIR)  ?  YES : NO;
		flags.leaf = !flags.dragAccepting || index(name, '.');
		flags.hidden = name[0] == '.';
		fileFlags.imageLoaded = NO;
		fileFlags.acceptingDragImageLoaded = NO;
		fileFlags.parentLoaded = NO;
		fileFlags.childrenLoaded = !flags.dragAccepting;
	}
	
	return self;
}


- copyFromZone: (NXZone *) zone;
{
	File
		* copy = [super  copyFromZone: zone];
	
	copy->path = NXCopyStringBufferFromZone(path, zone);
	copy->fileFlags = fileFlags;
	
	return copy;
}


- free
{
	[fileMap  removeKey: path];
	free(path);
	
	return [super  free];
}


/* ========================================================================== */


/*

Here are the methods that retrieve information about a file.  Files can be dragged and edited.  Folders also accept drags.  The acceptingDragImage is currently always the open folder.

*/

- image
{
	if (!fileFlags.imageLoaded) image = draggedImage ?
				[draggedImage  copy]:
				[[Application  workspace]  getIconForFile: path];
	
	fileFlags.imageLoaded = 1;
	return image;
}


- acceptingDragImage
{
	return [super  acceptingDragImage];
}


- (const char *) path
{
	return path;
}


/* ========================================================================== */


/*

The parent of a File is not loaded until requested, typically when the user selects a file sitting on the shelf.  In that case, if the file was dragged in from the Workspace its parent will likely not yet be loaded.

There's one bit of stickiness to watch out for.  One would like to avoid accessing the disk when the first call to parent is via a call to addParent:.  So addParent: always marks its parent list as loaded.

*/


- parents
{
	char
		* p,
		directory [MAXNAMLEN + 1];
	
	if (!fileFlags.parentLoaded)
	{
		fileFlags.parentLoaded = 1;
		strcpy (directory, path);
		if ((p = rindex (directory, '/')) != directory)
		{
			p[0] = '\0';
			[[File  fileForPath: directory]  addChild: self];
		}
		
		else if (p[1] != '\0')
			[[File  fileForPath: "/"]  addChild: self];
	}
	
	return parents;
}


- addParent: parent
{
	fileFlags.parentLoaded = 1;
	return [super  addParent: parent];
}


/* ========================================================================== */


/*

The files contained in a directory are not loaded until actually needed.  Aside from improving performance, such waiting avoids the mistake of reading in the entire file system whenever "/" is loaded.  The File installs itself in the parent list of all its children.

*/

- children
{
	struct direct
		** file;
	char
		filename [MAXNAMLEN + 1];
	int
		i,
		n,
		k;
	
	if (!fileFlags.childrenLoaded)
	{
		fileFlags.childrenLoaded = 1;
		strcpy(filename, path);
		if (filename[n = strlen(filename)-1] != '/') filename[++n] = '/';
		filename[++n] = '\0';
		k = scandir(filename, &file, NULL, alphasort);
		
		if (k != -1)
		{
			[users  setSendAnnouncements: NO];
			for (i = 0; i < k; i++)
			{
				strcpy(filename + n, file[i]->d_name);
				[self  addChild: [File  fileForPath: filename]];
				free(file[i]);
			}
		
			[users  setSendAnnouncements: YES];
			free(file);
		}
	}
	
	return children;
}


/* ========================================================================== */


/*

The workspace overloads when asked for a file icon during a dragging session, so the file class grabs the icon directly from the dragging source when it learns that a dragging session is about to begin.

*/

+ shelf: sender  dragWillEnter: (id <NXDraggingInfo>) source
{
	draggedImage = [source  draggedImage];
	return self;
}


+ shelf: sender  dragWillExit: (id <NXDraggingInfo>) source
{
	draggedImage = nil;
	return self;
}

+ shelf: sender  dragWillComplete: (id <NXDraggingInfo>) source
{
	draggedImage = nil;
	return self;
}


/* ========================================================================== */


/*

Files copy themselves both as local ids and file names.  Notice that the read method checks for the local id type first.

*/


- copyToPasteboard: (Pasteboard *) pboard
{
	NXAtom
		pasteTypes[] = { NXFilenamePboardType };
	
	[super  copyToPasteboard: pboard];
	[pboard  addTypes: pasteTypes  num: 1  owner: nil];
	[pboard  writeType: NXFilenamePboardType  data: path
				length: strlen(path)+1];
	
	return self;
}


+ readFromPasteboard: (Pasteboard *) pboard
{
	NXAtom
		pasteTypes[] = { NXFilenamePboardType };
	char
		* data;
	int
		n;
	id
		file = nil;
	
	if ((file = [super  readFromPasteboard: pboard]) == nil &&
		[pboard  findAvailableTypeFrom: pasteTypes  num: 1] != NULL)
	{
		[pboard  readType: NXFilenamePboardType  data: &data  length: &n];
		file = [File  fileForPathList: data];
		[pboard  deallocatePasteboardData: data  length: n];
	}
	
	return file;
}


@end
