/** UKPluginsRegistry UKPluginsRegistry.m Plugins manager class used to register new plugins and obtain already registered plugins Copyright (C) 2004 Uli Kusterer Author: Uli Kusterer Quentin Mathe Date: August 2004 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #import #import "UKPluginsRegistry.h" #ifdef HAVE_UKTEST #import #endif #ifdef GNUSTEP #define APPLICATION_SUPPORT @"ApplicationSupport" #else /* Cocoa */ #define APPLICATION_SUPPORT @"Application Support" #endif static NSFileManager *fm = nil; /** UKPluginRegistry Description

Each plugin is represented by an NSMutableDictionary to which you can add your own entries as needed. The keys UKPluginRegistry adds to this dictionary are:

bundleNSBundle instance for this plugin. identifierUnique identifier for this plugin (bundle identifier in current implementation) imageIcon (NSImage) of the plugin (for display in toolbars etc.) nameDisplay name of the plugin (for display in lists, toolbars etc.) pathFull path to the bundle. classNSValue containing pointer to the principal class (type Class) for this bundle, so you can instantiate it. instanceIf instantiate == YES, this contains an instance of the principal class, instantiated using alloc+init. */ @implementation UKPluginsRegistry /**

Returns UKPluginsRegistry shared instance (singleton).

*/ + (id) sharedRegistry { static UKPluginsRegistry *sharedPluginRegistry = nil; if (sharedPluginRegistry == NO) sharedPluginRegistry = [[UKPluginsRegistry alloc] init]; return sharedPluginRegistry; } /* First test to run */ #ifdef HAVE_UKTEST - (void) test_Init { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES); UKNotNil(paths); UKTrue([paths count] >= 1); } #endif - (id) init { self = [super init]; if (self == nil) return nil; plugins = [[NSMutableArray alloc] init]; pluginPaths = [[NSMutableDictionary alloc] init]; fm = [NSFileManager defaultManager]; instantiate = YES; return self; } - (void) dealloc { [plugins release]; [pluginPaths release]; [super dealloc]; } #ifdef HAVE_UKTEST - (void) testLoadPluginOfType { NSBundle *bundle = [NSBundle mainBundle]; NSDictionary *info = [bundle infoDictionary]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES); UKNotNil(bundle); UKNotNil(info); NSString *supportDir = [[paths objectAtIndex: 0] stringByAppendingPathComponent: APPLICATION_SUPPORT]; BOOL isDir; UKTrue([fm fileExistsAtPath: supportDir isDirectory:&isDir]); UKTrue(isDir); } #endif // FIXME: Implement UTI check support for type parameter. /**

Locates and loads plugin bundles with extension ext.

Normally this is the only method you need to call to load a plugin.

*/ - (void) loadPluginsOfType: (NSString *)ext { NSBundle *bundle = [NSBundle mainBundle]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES); NSEnumerator *e = [paths objectEnumerator]; NSString *path = nil; NSString *appName = [[bundle infoDictionary] objectForKey: @"NSExecutable"]; if (appName == nil) appName = [[bundle infoDictionary] objectForKey: @"CFBundleExecutable"]; NSString *pluginsDir = [[APPLICATION_SUPPORT stringByAppendingPathComponent: appName] stringByAppendingPathComponent: @"PlugIns"]; while ((path = [e nextObject]) != nil) { [self loadPluginsFromPath: [path stringByAppendingPathComponent: pluginsDir] ofType: ext]; } [self loadPluginsFromPath: [bundle builtInPlugInsPath] ofType: ext]; } // FIXME: Implement UTI check support for type parameter. /**

Finds plugins within folder path which are identified by an extension matching ext. Finally loads these plugins by calling -loadPluginForPath:.

*/ - (void) loadPluginsFromPath: (NSString *)folder ofType: (NSString *)ext { NSDirectoryEnumerator *e = [fm enumeratorAtPath: folder]; NSString *fileName = nil; while ((fileName = [e nextObject]) != nil ) { [e skipDescendents]; /* Ignore subfolders and don't search in packages. */ /* Skip invisible files: */ if ([fileName characterAtIndex: 0] == '.') continue; /* Only process ones that have the right suffix: */ if ([[fileName pathExtension] isEqualToString: ext] == NO) continue; NS_DURING /* Get path, bundle and display name: */ NSString *path = [folder stringByAppendingPathComponent: fileName]; [self loadPluginForPath: path]; NS_HANDLER NSLog(@"Error while listing PrefPane: %@", localException); NS_ENDHANDLER } } #ifdef HAVE_UKTEST - (void) testLoadPluginForPath { NSArray *paths; NSString *path; #ifdef GNUstep paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES); path = [[paths objectAtIndex: 0] stringByAppendingPathComponent: @"PreferencePanes/PrefPaneExample.prefPane"]; #else paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES); path = [[paths objectAtIndex: 0] stringByAppendingPathComponent: @"PreferencePanes/Appearance.prefPane"]; #endif NSBundle *bundle = [NSBundle bundleWithPath: path]; NSDictionary *info = [bundle infoDictionary]; int lastCount = [plugins count]; BOOL isDir; UKTrue([fm fileExistsAtPath: path isDirectory: &isDir]); UKNotNil(bundle); UKNotNil(info); //UKTrue([[info allKeys] containsObject: NAME_ENTRY]); [self loadPluginForPath: path]; UKTrue(instantiate); UKIntsEqual([plugins count], lastCount + 1); UKTrue([[pluginPaths allKeys] containsObject: path]); info = [pluginPaths objectForKey: path]; UKNotNil(info); UKTrue([[info allKeys] containsObject: @"bundle"]); UKTrue([[info allKeys] containsObject: @"image"]); UKTrue([[info allKeys] containsObject: @"name"]); UKTrue([[info allKeys] containsObject: @"path"]); UKTrue([[info allKeys] containsObject: @"class"]); } #endif /**

Loads the plugin bundle located at path, checks it conforms to Plugin schema stored in the related bundle property list.

Every property list values associated to Plugin schema are put in a dictionary which represents a plugin object; eventual validity errors may be reported each time a value is read in NSBundle description values returned by -infoDictionary.

*/ - (NSMutableDictionary *) loadPluginForPath: (NSString *)path { NSMutableDictionary *info = [pluginPaths objectForKey: path]; // NOTE: We may refactor plugin schema conformance test in a dedicated // method. We would be able to call it in subclasses to validate plugins // in a specific method. For example -validatePreferencePane could be // used by -preferencePaneForPath: to know when the preference pane has to // be reloaded because it is invalid. /* if (isInvalid) { [pluginsPath removeObjectForKey: path]; [plugins removeObject: info]; info = nil } */ if (info == nil) { NSBundle *bundle = [NSBundle bundleWithPath: path]; NSString *identifier; NSImage *image; NSString *name; /* We retrieve plugin's name */ name = [[bundle infoDictionary] objectForKey: @"CFBundleName"]; if (name == nil) name = [[bundle infoDictionary] objectForKey: @"ApplicationName"]; if (name == nil) name = [[bundle infoDictionary] objectForKey: @"NSExecutable"]; if (name == nil) name = @"Unknown"; /* We retrieve plugin's identifier */ identifier = [bundle bundleIdentifier]; if (identifier == nil) { NSLog(@"Plugin %@ is missing an identifier, it may be impossible to use it.", name); identifier = path; /* When no identifier is available, falling back on path otherwise. */ } /* Get icon, falling back on file icon when needed, or in worst case using our app icon: */ NSString *iconFileName = [[bundle infoDictionary] objectForKey: @"NSPrefPaneIconFile"]; NSString *iconPath = nil; if(iconFileName == nil) iconFileName = [[bundle infoDictionary] objectForKey: @"NSIcon"]; if(iconFileName == nil) iconFileName = [[bundle infoDictionary] objectForKey: @"ApplicationIcon"]; if(iconFileName == nil) iconFileName = [[bundle infoDictionary] objectForKey: @"CFBundleIcon"]; if (iconFileName != nil) iconPath = [bundle pathForImageResource: iconFileName]; if (iconPath == nil) { image = [NSImage imageNamed: @"NSApplicationIcon"]; } else { image = [[[NSImage alloc] initWithContentsOfFile: iconPath] autorelease]; } /* When image loading has failed, we set its value to null object in in order to be able to create info dictionary without glitches a 'nil' value would produce (like subsequent elements being ignored). */ if (image == nil) image = [NSNull null]; /* Add a new entry for this pane to our list: */ info = [NSMutableDictionary dictionaryWithObjectsAndKeys: bundle, @"bundle", identifier, @"identifier", image, @"image", name, @"name", path, @"path", [NSValue valueWithPointer: [bundle principalClass]], @"class", nil]; if (instantiate) { id obj = [[[[bundle principalClass] alloc] init] autorelease]; [info setObject: obj forKey: @"instance"]; } [plugins addObject: info]; [pluginPaths setObject: info forKey: path]; } return info; } #ifdef HAVE_UKTEST - (void) testLoadedPlugins { //UKNotNil([self loadedPlugins]); } #endif /**

Returns the currently registered plugins (loaded by the way).

Nil is returned when no plugins have been registered.

*/ - (NSArray *) loadedPlugins { if ([plugins count] > 0) { return plugins; } else { return nil; } } /**

Returns instantiate value.

Read -setInstantiate: documentation to learn more.

*/ - (BOOL) instantiate { return instantiate; } /**

Sets instantiate value to YES if you want to have plugins main class automatically instantiated when they are loaded.

*/ - (void) setInstantiate: (BOOL)n { instantiate = n; } @end