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)authenticateForDomain:(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]]; } - niMkDirs:(void*)handle path:(const char *)dirPath domain:(NIDomain *)ldomain { ni_name path = ni_name_dup(dirPath); ni_name next; ni_id root, dir; int status, choice; ni_name value; ni_property lproperty; ni_proplist lproperties; // The root must exist ... status = ni_pathsearch(handle, &root, "/"); NI_INIT(&lproperties); lproperties.ni_proplist_len = 1; lproperties.ni_proplist_val = &lproperty; lproperty.nip_name = PROPNAME; lproperty.nip_val.ni_namelist_len = 1; lproperty.nip_val.ni_namelist_val = &value; if (*path == '/') value = path+1; else value = path; while (value && *value) { if (next = strchr(value, '/')) *next++ = '\0'; if (![self authenticateForDomain:ldomain andUser:user]) { ni_name_free(&path); return nil; } do { status = ni_create(handle, &root, lproperties, &dir, NI_INDEX_NULL); if (status != NI_OK) { choice = ALERT_STATUS("Alert:NetInfo Error", "Message:Creating directory", "Button:Reauthenticate", "Button:Cancel", NULL, value, status); switch (choice) { case NX_ALERTDEFAULT: [(AuthEntry *)[authHash valueForKey:[ldomain getFullPath]] free]; [authHash removeKey:[ldomain getFullPath]]; if ([self authenticateForDomain:ldomain andUser:user]) break; case NX_ALERTALTERNATE: default: ni_name_free(&path); return nil; } } } while (status != NI_OK); root = dir; value = next; } ni_name_free(&path); return self; } - 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 authenticateForDomain: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 authenticateForDomain: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 { // If the base directory (dirName) doesn't exist, create it and all intermediate paths! // Get the root directory. status = ni_pathsearch(tempHandle, &tempDir, dirName); switch (status) { case NI_OK: break; case NI_NODIR: // Create the base path! if (![self niMkDirs:tempHandle path:dirName domain:tempDomain]) { NXZoneFree([self zone], tempFullPath); NXZoneFree([self zone], tempFullDir); [tempDomain free]; return nil; } status = ni_pathsearch(tempHandle, &tempDir, dirName); break; default: 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 authenticateForDomain: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); mayChangeName = NO; 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; } - setAllowChangName:(BOOL)flag { mayChangeName = flag; return self; } - save { if (domain) return [self doSave]; else return [self saveToDomain]; return nil; } - saveToDomain { int index, status; ni_name oldDomainName = domainName, oldBaseName = baseName, oldFullPath = fullPath; ni_id oldDir = directory; ni_property nameProp; NISavePanel *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 if (mayChangeName) status = [savePanel runModalWithString: properties.ni_proplist_val[index].nip_val.ni_namelist_val[0]]; 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, baseName, 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 authenticateForDomain: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.