ftp.nice.ch/pub/next/unix/disk/SambaManager.1.0.NIHS.s.tar.gz#/SambaManager/NIDirectory.m

This is NIDirectory.m in view mode; [Download] [Up]

/*
    SambaManger. A graphical frontend to configure the NetInfo enhanced samba.
    Copyright (C) 1998  Robert Frank

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
		
		Robert Frank, frank@ifi.unibas.ch
*/

#import	"NIDirectory.h"
#import "NIProperty.h"
#import <nikit/NIDomainPanel.h>
#import <nikit/NILoginPanel.h>
#import <nikit/NIOpenPanel.h>
#import <nikit/NISavePanel.h>

#define	PROPNAME	"name"

#define ALERT1(a,m,b1,b2,b3,arg) \
							NXRunAlertPanel([strings valueForStringKey:a], \
															[strings valueForStringKey:m], \
															[strings valueForStringKey:b1], \
															b2?[strings valueForStringKey:b2]:NULL, \
															b3?[strings valueForStringKey:b3]:NULL, \
															arg)

#define ALERT_STATUS(a,m,b1,b2,b3,arg1,status) \
							NXRunAlertPanel([strings valueForStringKey:a], \
															[strings valueForStringKey:m], \
															[strings valueForStringKey:b1], \
															b2?[strings valueForStringKey:b2]:NULL, \
															b3?[strings valueForStringKey:b3]:NULL, \
															arg1, ni_error(status))

//*****************************************************
// An auxillary class for the authentication.
@interface AuthEntry:Object
{
		char	*key;				// This must remain unchanged during the object's lifetime!
		char	*userName;
		char 	*password;
}

- (BOOL)authenticate:(void *)handle;
- free;
- (const char *)key;
- init:(const char *)path user:(const char *)user passwd:(const char *)passwd;
@end

@implementation AuthEntry
- init:(const char *)path user:(const char *)user passwd:(const char *)passwd
{
		key = NXCopyStringBufferFromZone(path, [self zone]);
		userName = NXCopyStringBufferFromZone(user, [self zone]);
		password = NXCopyStringBufferFromZone(passwd, [self zone]);
		return self;
}

- free
{
		NXZoneFree([self zone], key);
		NXZoneFree([self zone], userName);
		NXZoneFree([self zone], password);
		return self;
}

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

- (BOOL)authenticate:(void *)handle
{
int	status;

		status = ni_setuser(handle, userName);
		if (status == NI_OK)
			status = ni_setpassword(handle, password);
		if (status == NI_OK)
			return YES;
		else
			return NO;
}
@end


//*****************************************************
// The actual NIDirectory class.

// Static (class) variables:
static NXStringTable	*strings = nil;		// The localized strings.
static HashTable			*authHash = nil;	// The hashtable for authenticated domains
static const char 		*defaultText = "default";
static AuthEntry			*globalLogin;
static const char			*gobalDomainName;


@implementation NIDirectory
// Dummy methods for the compiler:
- (BOOL)niDirOk:(const char *)domain path:(const char *)directory
{
		return NO;
}

//*****************************************************
// Class methods:

+ initialize
// Called once by the run time system
{
char	buf[MAXPATHLEN + 1];

		if (!strings)
			if ( [[NXBundle mainBundle] getPath:buf forResource:"NIDirectory" ofType:"strings"] )
				if (strings = [[[NXStringTable alloc] init] readFromFile:buf]) {
					defaultText = [strings valueForStringKey:"String:default"];
					[NIProperty init:strings];
				}
		
		if (!authHash)
			authHash = [[HashTable alloc] initKeyDesc:"*"];

		return nil;
}

+ new:sender root:(const char *)rootPath directory:(const char *)dirPath
{
		return [[NIDirectory alloc] init:sender dom:NULL root:rootPath dir:dirPath errors:YES];
}

+ open:sender root:(const char *)rootPath withTitle:(const char *)title
// Open a panel displaying the domains and the entries of the directory given in baseDir.
{
NIOpenPanel	*openPanel;
NIDirectory	*propList;
const char	*dir, *domainPath;
char				*fullDir;

		openPanel = [NIOpenPanel new];
		[openPanel setDirectoryPath:rootPath];
		[openPanel setPanelTitle:[strings valueForStringKey:"Title:Select in NetInfo Domain"]];
		[openPanel setListTitle:title];
		if ([openPanel runModal] != NX_ALERTDEFAULT)
			return nil;
		
		if (((dir = [openPanel directory]) == NULL) || ((domainPath = [openPanel domain]) == NULL))
			return nil;
			
		fullDir = NXZoneMalloc([self zone], sizeof(char)*(strlen(rootPath)+strlen(dir)+2));
		(void)strcpy(fullDir, rootPath);
		if ((fullDir[strlen(fullDir)-1] != '/') && (*dir != '/'))
			(void)strcat(fullDir, "/");
		(void)strcat(fullDir, dir);

		// Check for possible duplications. If the sender can
		// respond to niDirOk and returns NO, abort.
		if ([sender respondsTo:@selector(niDirOk:path:)])
			if (![sender niDirOk:domainPath path:fullDir]) {
				NXZoneFree([self zone], fullDir);
				return nil;
			}
		
		propList = [[NIDirectory alloc] init:sender dom:domainPath root:rootPath dir:dir errors:YES];
		NXZoneFree([self zone], fullDir);
		if (propList)
			[propList setSaveTitle:title];

		return propList;
}

//*****************************************************
// Local methods
- (BOOL)panel:thePanel authenticateUser:(const char *)userName
                       withPassword:(const char *)password inDomain:(void *)aDomain
{
		globalLogin = [[AuthEntry alloc] init:gobalDomainName user:userName passwd:password];
		return YES;
}

- (BOOL)authenticate:(const char *)path forDomain:(NIDomain *)aDomain andUser:(const char *)userName
// Check for an authenticated path. If userName is NULL, use root and disallow changing the name.
{
NILoginPanel	*loginPanel = [NILoginPanel new];

		gobalDomainName = [aDomain getFullPath];
		if (!(globalLogin = [authHash valueForKey:gobalDomainName])) {
			[loginPanel setDelegate:self];
      if ([loginPanel runModalWithValidation:self inDomain:[aDomain getDomainHandle]
											withUser:userName?userName:"root"
								      withInstruction:[strings valueForStringKey:"Title:User Authentication"]
					  		      allowChange:(userName != NULL)])
				[authHash insertKey:[globalLogin key] value:globalLogin];
		}
		 
		return [globalLogin authenticate:[aDomain getDomainHandle]];
}

- doSave
// Check if baseName is identical to the name property, if so, save. Otherwise,
// delete the old entry and create a new on.
{
ni_name			tempName = baseName, tempFullDir, tempFullPath;
int					index, status, choice;
u_long			oldInstance = directory.nii_instance;
ni_id				tempDir, tempRoot;
void				*tempHandle = NULL;
NIDomain		*tempDomain;

		// Have to check each textfield, as it may not have been ended.
		for (index = 0; index < [list count]; index++)
			if (![[list objectAt:index] updateProperty])
				return nil;

		// Check the properties list for a name property. If none exists and
		// baseName isn't defined, display an error and exit with nil.
		index = ni_proplist_match(properties, PROPNAME, NULL);
		if (index != NI_INDEX_NULL)
			tempName = properties.ni_proplist_val[index].nip_val.ni_namelist_val[0];
		else if (!baseName || !*baseName)	{
			ALERT1("Alert:Alert", "Message:Need a name", "Button:OK", NULL, NULL, NULL);
			return nil;
		}
			
		tempFullDir = NXZoneMalloc([self zone], sizeof(char)*(strlen(dirName)+strlen(tempName)+2));
		(void)strcpy(tempFullDir, dirName);
		if ((tempFullDir[strlen(tempFullDir)-1] != '/') && (*tempName != '/'))
			(void)strcat(tempFullDir, "/");
		(void)strcat(tempFullDir, tempName);

		tempFullPath = NXZoneMalloc([self zone], sizeof(char)*(strlen(domainName)+strlen(tempFullDir)+2));
		(void)strcpy(tempFullPath, domainName);
		if ((tempFullPath[strlen(tempFullPath)-1] != '/') && (*tempFullDir != '/'))
			(void)strcat(tempFullPath, "/");
		(void)strcat(tempFullPath, tempFullDir);

		// Allocate and connect a NetInfo handle.
		tempDomain = [[NIDomain alloc] init];
		status = [tempDomain setConnection:domainName readTimeout:5 writeTimeout:10 canAbort:YES mustWrite:YES];
		if (status == NI_OK) {
			tempHandle = [tempDomain getDomainHandle];
			if (tempHandle == NULL) {
				status = [tempDomain lastError];
			}
		}
		if (status != NI_OK) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Connecting to", "Button:OK", NULL, NULL, domainName, status);
			NXZoneFree([self zone], tempFullPath);
			NXZoneFree([self zone], tempFullDir);
			[tempDomain free];
			return nil;
		}
		// Locate the directory.
		status = ni_pathsearch(tempHandle, &tempDir, tempFullDir);
		
		// If the paths differ, check the new name and path for an existing
		// entry with that name and path. If it exists, display an alert and
		// ask whether to overwrite. If no, exit with nil. OtherWise, overwrite
		// the old entry.
		if (strcmp(tempFullPath, fullPath?fullPath:"") && (status == NI_OK)) {
			oldInstance = 0;
			choice = ALERT1("Alert:NetInfo Exists", "Message:Overwrite Existing Entry?",
											"Button:Yes", "Button:Cancel", NULL, tempFullPath);
			if (choice ==  NX_ALERTALTERNATE) {
				NXZoneFree([self zone], tempFullPath);
				NXZoneFree([self zone], tempFullDir);
				[tempDomain free];
				return nil;
			}
		}
		
		if (![self authenticate:domainName forDomain:tempDomain andUser:user]) {
			NXZoneFree([self zone], tempFullPath);
			NXZoneFree([self zone], tempFullDir);
			[tempDomain free];
			return nil;
		}
		
		// If the check for an existing entry was successful, overwrite.
		// Otherwise create a new entry.
		if (status == NI_OK) {
			// Just in case someone else wrote to this directory before us!
			if (oldInstance && (oldInstance != tempDir.nii_instance)) {
				choice = ALERT1("Alert:Warning", "Message:old instance",
												"Button:Yes", "Button:No", NULL, tempFullPath);
				switch (choice) {
					case NX_ALERTDEFAULT:
						break;
					case NX_ALERTALTERNATE:
					default:
						NXZoneFree([self zone], tempFullPath);
						NXZoneFree([self zone], tempFullDir);
						[tempDomain free];
						return nil;
				}
			}
		
			do {
				status = ni_write([tempDomain getDomainHandle], &tempDir, properties);
				if (status != NI_OK) {
					choice = ALERT_STATUS("Alert:NetInfo Error", "Message:Writing directory",
																"Button:Reauthenticate", "Button:Cancel", NULL, tempFullPath, status);
					switch (choice) {
						case NX_ALERTDEFAULT:
							[(AuthEntry *)[authHash valueForKey:[tempDomain getFullPath]] free];
							[authHash removeKey:[tempDomain getFullPath]];
							if ([self authenticate:domainName forDomain:tempDomain andUser:user])
								break;
						case NX_ALERTALTERNATE:
						default:
							NXZoneFree([self zone], tempFullPath);
							NXZoneFree([self zone], tempFullDir);
							[tempDomain free];
							return nil;
					}
				}
			} while (status != NI_OK);
			directory = tempDir;
		} else {
			// Get the root directory.
			if ((status = ni_pathsearch(tempHandle, &tempDir, dirName)) != NI_OK) {
				ALERT_STATUS("Alert:NetInfo Error", "Message:Reading directory", "Button:OK", NULL, NULL,
											dirName, status);
				NXZoneFree([self zone], tempFullPath);
				NXZoneFree([self zone], tempFullDir);
				[tempDomain free];
				return nil;
			}

			// Create the new directory.
			do {
				status = ni_create(tempHandle, &tempDir, properties, &directory, NI_INDEX_NULL);
				if (status != NI_OK) {
					choice = ALERT_STATUS("Alert:NetInfo Error", "Message:Creating directory",
																"Button:Reauthenticate", "Button:Cancel", NULL, tempFullPath, status);
					switch (choice) {
						case NX_ALERTDEFAULT:
							[(AuthEntry *)[authHash valueForKey:[tempDomain getFullPath]] free];
							[authHash removeKey:[tempDomain getFullPath]];
							if ([self authenticate:domainName forDomain:tempDomain andUser:user])
								break;
						case NX_ALERTALTERNATE:
						default:
							NXZoneFree([self zone], tempFullPath);
							NXZoneFree([self zone], tempFullDir);
							[tempDomain free];
							return nil;
					}
				}
			} while (status != NI_OK);
		}

		// Update some variables.
		ni_name_free(&fullPath);
		fullPath = ni_name_dup(tempFullPath);
		if (domain)
			[domain free];
		domain = tempDomain;
		
		NXZoneFree([self zone], tempFullPath);
		NXZoneFree([self zone], tempFullDir);

		// If the names differed and baseName was set, remove the old entry.
		if (baseName && strcmp(tempName, baseName)) {
			tempFullDir = NXZoneMalloc([self zone], sizeof(char)*(strlen(dirName)+strlen(baseName)+2));
			(void)strcpy(tempFullDir, dirName);
			if ((tempFullDir[strlen(tempFullDir)-1] != '/') && (*baseName != '/'))
				(void)strcat(tempFullDir, "/");
			(void)strcat(tempFullDir, baseName);
			// Locate the directory.
			status = ni_pathsearch(tempHandle, &tempDir, tempFullDir);
			if (status == NI_OK) 
				status = ni_pathsearch(tempHandle, &tempRoot, dirName);
			if (status == NI_OK)
				status = ni_destroy(tempHandle, &tempRoot, tempDir);
			if (status != NI_OK) {
				ALERT_STATUS("Alert:NetInfo Warning", "Message:Removing directory", "Button:OK", NULL, NULL,
											dirName, status);
				NXZoneFree([self zone], tempFullDir);
			}

			NXZoneFree([self zone], tempFullDir);
		}

		// Update the remaining variables.
		ni_name_free(&baseName);
		baseName = ni_name_dup(tempName);
	
		return self;
}

- add:(NIProperty *)p
{
		[hash insertKey:[p name] value:p];
		[list addObject:p];
		return p;
}

//*****************************************************
// Public methods

- init:sender dom:(const char *)domPath root:(const char *)basePath dir:(const char *)dirPath errors:(BOOL)warn
{
void		*handle = NULL;
int			status;
ni_name	fullDir;

		delegate = sender;
		domainName = domPath?ni_name_dup(domPath):NULL;
		dirName = ni_name_dup(basePath);
		baseName = dirPath?ni_name_dup(dirPath):NULL;
		NI_INIT(&fullPath);
		NI_INIT(&saveTitle);
		NI_INIT(&user);
		domain = nil;
		NI_INIT(&directory);
		NI_INIT(&properties);
		hash = [[HashTable alloc] initKeyDesc:"*"];
		list = [[List alloc] init];

		if (!domPath || !dirPath)
			return self;

		// Allocate and connect a NetInfo handle.
		domain = [[NIDomain alloc] init];
		status = [domain setConnection:domPath readTimeout:5 writeTimeout:10 canAbort:YES mustWrite:YES];
		if (status == NI_OK) {
			handle = [domain getDomainHandle];
			if (handle == NULL) {
				status = [domain lastError];
			}
		}
		
		if (status != NI_OK) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Connecting to", "Button:OK", NULL, NULL,
										domPath, status);
			[domain free];
			return nil;
		}
		
		if (baseName) {
			fullDir = NXZoneMalloc([self zone], sizeof(char)*(strlen(dirName)+strlen(baseName)+2));
			(void)strcpy(fullDir, dirName);
			if ((fullDir[strlen(fullDir)-1] != '/') && (baseName[0] != '/'))
				(void)strcat(fullDir, "/");
			(void)strcat(fullDir, baseName);
		} else {
			fullDir = NXZoneMalloc([self zone], sizeof(char)*strlen(dirName));
			(void)strcpy(fullDir, dirName);
		}
		
		// Locate the directory.
		status = ni_pathsearch(handle, &directory, fullDir);
		if ((status != NI_OK) && warn) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Locating directory", "Button:OK", NULL, NULL,
										fullDir, status);
			NXZoneFree([self zone], fullDir);
			[domain free];
			return nil;
		}
		fullPath = NXZoneMalloc([self zone], sizeof(char)*(strlen(domainName)+strlen(fullDir)+2));
		(void)strcpy(fullPath, domainName);
		if ((fullDir[strlen(fullPath)-1] != '/') && (*fullDir != '/'))
			(void)strcat(fullPath, "/");
		(void)strcat(fullPath, fullDir);
		
		if (status == NI_OK) {
			// Load the existing data by reading the directory.
			if ((status = ni_read(handle, &directory, &properties)) != NI_OK) {
				ALERT_STATUS("Alert:NetInfo Error", "Message:Reading directory", "Button:OK", NULL, NULL,
											fullDir, status);
				NXZoneFree([self zone], fullDir);
				[domain free];
				return nil;
			}
		}
		NXZoneFree([self zone], fullDir);

		domainName = ni_name_dup(domPath);
		
		return self;
}

- setSaveTitle:(const char *)title
{
		ni_name_free(&saveTitle);
		saveTitle = ni_name_dup(title);
		return self;
}

- setAuthenticationUser:(const char *)userName
{
		ni_name_free(&user);
		if (userName && *userName)
			user = ni_name_dup(userName);
		return self;
}

- save
{
	if (domain)
		return [self doSave];
	else
		return [self saveToDomain];
	return nil;
}

- saveToDomain
{
NISavePanel	*savePanel;
int					index, status;
ni_name			oldDomainName = domainName,
						oldBaseName = baseName,
						oldFullPath = fullPath;
ni_id				oldDir = directory;
ni_property	nameProp;

		savePanel = [NISavePanel new];
		[savePanel setDirectoryPath:dirName];
		[savePanel setStartingDomainPath:domainName?domainName:"/"];
		[savePanel setPanelTitle:[strings valueForStringKey:"Title:Select in NetInfo Domain"]];
		[savePanel setListTitle:saveTitle?saveTitle:""];

		// If we have a property 'name', set that for the modal.
		// If not, ask for a new name!
		index = ni_proplist_match(properties, PROPNAME, NULL);
		if (index == NI_INDEX_NULL)
			status = [savePanel runModalWithString:""];
		else {
			status = [savePanel runModalWithUneditableString:
			                    properties.ni_proplist_val[index].nip_val.ni_namelist_val[0]];
		}
		
		if (status != NX_ALERTDEFAULT)
			return nil;
		
		domainName = ni_name_dup([savePanel domain]);
		baseName = ni_name_dup([savePanel directory]);
		fullPath = NULL;
		NI_INIT(&directory);
		
		// If no name was set, create a name entry an set it!
		if (index == NI_INDEX_NULL) {
			NI_INIT(&nameProp);
			nameProp.nip_name = PROPNAME;
			ni_namelist_insert(&nameProp.nip_val, [savePanel directory], NI_INDEX_NULL);
			ni_proplist_insert(&properties, nameProp, 0);
			ni_namelist_free(&nameProp.nip_val);
		}

		// Try to save to the new directory.
		if ([self doSave]) {
			ni_name_free(&oldDomainName);
			ni_name_free(&oldBaseName);
			ni_name_free(&oldFullPath);
			return self;
		} else {
			domainName = oldDomainName;
			baseName = oldBaseName;
			fullPath = oldFullPath;
			directory = oldDir;
			return nil;
		}
}

- delete
{
ni_id	parent;
int		status;

		if (![self authenticate:domainName forDomain:domain andUser:user])
			return nil;

		if ((status = ni_self([domain getDomainHandle], &directory)) == NI_OK)
			if ((status = ni_pathsearch([domain getDomainHandle], &parent, dirName)) == NI_OK)
				if ((status = ni_destroy([domain getDomainHandle], &parent, directory)) == NI_OK) {
					ni_name_free(&domainName);
					[domain free];
					domain = nil;
				}
		
		if (status != NI_OK) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Removing directory", "Button:OK", NULL, NULL,
										dirName, status);
			return nil;
		}
		
		return self;
}

- close
{
		if (fullPath)
			NXZoneFree([self zone], fullPath);
		ni_name_free(&saveTitle);
		ni_name_free(&user);
		ni_name_free(&domainName);
		ni_name_free(&dirName);
		ni_name_free(&baseName);
		if (domain)
			[domain free];
		ni_proplist_free(&properties);
		[hash free];
		[list freeObjects];
		[list free];
		return [super free];
}


- setDelegate:sender
{
		delegate = sender;
		return self;
}

- delegate
{
		return delegate;
}

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

- (const char *)directory
// Return just the directory path.
{
		return fullPath+strlen(domainName);
}

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

- (NIDomain *)domain
{
		return domain;
}

- (void *)handle
{
		return [domain getDomainHandle];
}

- (ni_id)directoryID
{
		return directory;
}


// Methods for adding properties of specific types.
- addBool:(const char *)label outlet:obj
{
		return [self add:[[NIBoolProperty alloc] init:delegate properties:&properties name:label outlet:obj]];
}

- addChar:(const char *)label outlet:obj
{
		return [self add:[[NICharProperty alloc] init:delegate properties:&properties name:label outlet:obj]];
}

- addInt:(const char *)label text:tObj slider:sObj zero:(const char *)string
{
		return [self add:[[NIIntProperty alloc] init:delegate properties:&properties name:label text:tObj slider:sObj
																						default:defaultText zero:string]];
}

- addString:(const char *)label outlet:obj
{
		return [self add:[[NIStringProperty alloc] init:delegate properties:&properties name:label text:obj button:nil
																							 mode:NIPT_NONE path:NULL title:NULL]];
}

- addString:(const char *)label text:tObj button:bObj mode:(int)m path:(const char *)p
		title:(const char *)tString
{
		return [self add:[[NIStringProperty alloc] init:delegate properties:&properties name:label text:tObj button:bObj
																							 mode:m path:p title:tString]];
}

- addBrowser:(const char *)label browser:bObj text:tObj add:aObj remove:dObj
{
		return [self add:[[NIBrowserProperty alloc] init:delegate properties:&properties name:label text:tObj browser:bObj
																								mode:NIPT_NONE path:NULL
																								add:aObj remove:dObj title:NULL]];
}

- addBrowser:(const char *)label browser:bObj add:aObj remove:dObj
		mode:(int)m path:(const char *)p title:(const char *)tString
{
		return [self add:[[NIBrowserProperty alloc] init:delegate properties:&properties name:label 
																								text:nil browser:bObj mode:m path:p
																								add:aObj remove:dObj title:tString]];
}

- addPopup:(const char *)label outlet:obj
{
		return [self add:[[NIPopupProperty alloc] init:delegate properties:&properties name:label 
																							outlet:obj default:defaultText]];
}

- addPopup:(const char *)label outlet:obj default:(const char *)defStrng
{
		return [self add:[[NIPopupProperty alloc] init:delegate properties:&properties name:label 
																							outlet:obj default:defStrng]];
}

- addCall:(const char *)label displayAction:(SEL)action
{
		return [self add:[[NICallProperty alloc] init:delegate properties:&properties
		                                         name:label displayAction:action]];
}

- addProperty:(const char *)label
{
		return [self add:[[NICallProperty alloc] init:delegate properties:&properties
		                                         name:label displayAction:(SEL)nil]];
}

// Transfere from NetInfo to the GUI
- scan
{
int	i;
		
		for (i = 0; i < [list count]; i++)
			[[list objectAt:i] display];

		return self;
}

// redisplay previously read values
- reset
{
int					status;
ni_name			fullDir;
ni_id				tdir;
ni_proplist	props;

		NI_INIT(&tdir);
		fullDir = NXZoneMalloc([self zone], sizeof(char)*(strlen(dirName)+strlen(baseName)+2));
		(void)strcpy(fullDir, dirName);
		if ((fullDir[strlen(fullDir)-1] != '/') && (*baseName != '/'))
			(void)strcat(fullDir, "/");
		(void)strcat(fullDir, baseName);

		if ((status = ni_pathsearch([domain getDomainHandle], &tdir, fullDir)) != NI_OK) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Reading directory", "Button:OK", NULL, NULL,
										fullDir, status);
			NXZoneFree([self zone], fullDir);
			return nil;
		}

		NI_INIT(&props);
		if ((status = ni_read([domain getDomainHandle], &tdir, &props)) != NI_OK) {
			ALERT_STATUS("Alert:NetInfo Error", "Message:Reading directory", "Button:OK", NULL, NULL,
										fullDir, status);
			NXZoneFree([self zone], fullDir);
			return nil;
		}
		NXZoneFree([self zone], fullDir);

		ni_proplist_free(&properties);
		properties = props;
		directory = tdir;

		return [self scan];
}

// Return the NetInfo property of the given label.
- property:(const char *)label
{
		return [hash valueForKey:label];
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.