2 Copyright (C) 2002-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "SoProductRegistry.h"
23 #include "SoProduct.h"
25 #include "SoClassSecurityInfo.h"
27 #include <NGObjWeb/WOResponse.h>
28 #include <NGObjWeb/WOContext.h>
30 @implementation SoProductRegistry
32 static int debugOn = 0;
35 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
36 static BOOL didInit = NO;
40 debugOn = [ud boolForKey:@"SoProductRegistryDebugEnabled"] ? 1 : 0;
43 + (id)sharedProductRegistry {
44 static SoProductRegistry *reg = nil; // THREAD
46 reg = [[SoProductRegistry alloc] init];
51 if ((self = [super init])) {
52 [self scanForAvailableProducts];
54 [[NSNotificationCenter defaultCenter]
55 addObserver:self selector:@selector(_bundleDidLoad:)
56 name:@"NSBundleDidLoadNotification" object:nil];
62 [[NSNotificationCenter defaultCenter] removeObserver:self];
63 [self->bundlePathToFirstName release];
64 [self->products release];
70 - (void)_bundleDidLoad:(NSNotification *)_notification {
72 If bundles are loaded by some other code, check whether they contain
75 [self registerProductBundle:[_notification object]];
80 - (NSFileManager *)fileManager {
81 return [NSFileManager defaultManager];
84 - (void)registerProductBundle:(NSBundle *)_bundle {
85 NSString *productName, *firstProductName, *bundlePath;
89 if (![_bundle isNotNull])
92 bundlePath = [_bundle bundlePath];
94 if (_bundle != [NSBundle mainBundle]) {
96 [[bundlePath lastPathComponent] stringByDeletingPathExtension];
99 productName = @"MAIN";
101 if ((product = [self->products objectForKey:productName])) {
102 [self debugWithFormat:@"product '%@' already registered.", productName];
103 [product reloadIfPossible];
108 [self->bundlePathToFirstName objectForKey:bundlePath];
109 if (firstProductName != nil) {
110 [self debugWithFormat:
111 @"Note: register bundle with a different name '%@': '%@'",
112 productName, bundlePath];
113 if ((product = [self->products objectForKey:firstProductName]) != nil) {
114 [self debugWithFormat:
115 @"add additional name '%@' (first %@) for product '%@'",
116 productName, firstProductName, product];
117 [self->products setObject:product forKey:productName];
121 [self warnWithFormat:
122 @"no product object for first name '%@' (name=%@,bundle=%@)",
123 firstProductName, productName, bundlePath];
127 manifest = [_bundle pathForResource:@"product" ofType:@"plist"];
128 if ([manifest length] == 0) {
129 if ([productName isEqualToString:@"MAIN"])
130 [self debugWithFormat:@" main bundle has no manifest."];
136 if (self->products == nil)
137 self->products = [[NSMutableDictionary alloc] initWithCapacity:32];
138 if (self->bundlePathToFirstName == nil) {
139 self->bundlePathToFirstName =
140 [[NSMutableDictionary alloc] initWithCapacity:32];
145 [self debugWithFormat:@"register product bundle: '%@' (0x%08X[%@])",
146 bundlePath, _bundle, NSStringFromClass([_bundle class])];
148 [self debugWithFormat:@" register as product: %@", productName];
150 if ((product = [[SoProduct alloc] initWithBundle:_bundle]) == nil) {
151 [self debugWithFormat:@" could not init product from bundle: %@",
156 [self->bundlePathToFirstName setObject:productName forKey:bundlePath];
157 [self->products setObject:product forKey:productName];
161 - (void)registerProductAtPath:(NSString *)_path {
162 static NGBundleManager *bm = nil;
165 if (![_path isNotNull])
168 if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
169 bundle = [bm bundleWithPath:_path];
172 [self logWithFormat:@"could not init bundle object for path: %@", _path];
175 [self registerProductBundle:bundle];
178 - (void)scanForProductsInDirectory:(NSString *)_path {
180 NSEnumerator *pathes;
183 fm = [self fileManager];
184 pathes = [[fm directoryContentsAtPath:_path] objectEnumerator];
185 while ((lPath = [pathes nextObject])) {
188 lPath = [_path stringByAppendingPathComponent:lPath];
190 if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
195 [self registerProductAtPath:lPath];
199 - (void)scanForAvailableProducts {
207 /* scan mail bundle & frameworks */
209 if ((bundle = [NSBundle mainBundle]))
210 [self registerProductBundle:bundle];
212 NSLog(@"%s: missing main bundle ...", __PRETTY_FUNCTION__);
214 pathes = [NSBundle allFrameworks];
215 for (i = 0; i < [pathes count]; i++)
216 [self registerProductBundle:[pathes objectAtIndex:i]];
218 pathes = [NSBundle allBundles];
219 for (i = 0; i < [pathes count]; i++)
220 [self registerProductBundle:[pathes objectAtIndex:i]];
222 /* scan library pathes */
224 fm = [NSFileManager defaultManager];
225 pi = [NSProcessInfo processInfo];
227 #if COCOA_Foundation_LIBRARY
229 TODO: (like COMPILE_FOR_GNUSTEP)
230 This should actually check whether we are compiling in the
231 GNUstep environment since this modifies the location of bundles.
233 pathes = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
237 pathes = [[pi environment] objectForKey:@"GNUSTEP_PATHPREFIX_LIST"];
239 pathes = [[pi environment] objectForKey:@"GNUSTEP_PATHLIST"];
241 pathes = [[pathes stringValue] componentsSeparatedByString:@":"];
244 [self debugWithFormat:@"scanning for products ..."];
246 #if !COCOA_Foundation_LIBRARY
247 relPath = @"Library/";
251 relPath = [NSString stringWithFormat:@"%@SoProducts-%i.%i/", relPath,
252 SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION];
253 for (i = 0; i < [pathes count]; i++) {
257 lPath = [[pathes objectAtIndex:i] stringByAppendingPathComponent:relPath];
259 if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
264 [self debugWithFormat:@" directory %@", lPath];
265 [self scanForProductsInDirectory:lPath];
268 #if COCOA_Foundation_LIBRARY
269 /* look in wrapper places */
270 bundle = [NSBundle bundleForClass:[self class]];
271 relPath = [[bundle resourcePath]
272 stringByAppendingPathComponent:@"SoProducts"];
273 [self scanForProductsInDirectory:relPath];
275 /* look into FHS pathes */
277 relPath = [NSString stringWithFormat:@"lib/sope-%i.%i/products/",
278 SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION];
279 pathes = [NSArray arrayWithObjects:
280 [@"/usr/local/" stringByAppendingString:relPath],
281 [@"/usr/" stringByAppendingString:relPath],
283 for (i = 0; i < [pathes count]; i++) {
287 lPath = [pathes objectAtIndex:i];
288 if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
293 [self debugWithFormat:@" directory %@", lPath];
294 [self scanForProductsInDirectory:lPath];
299 [self debugWithFormat:
300 @"finished scan for products (%i products registered).",
301 [self->products count]];
304 /* registering products */
306 - (BOOL)loadProductNamed:(NSString *)_name {
308 NSEnumerator *requiredProducts;
311 if ((product = [self->products objectForKey:_name]) == nil) {
312 [self debugWithFormat:@"did not find product: %@", _name];
316 /* load dependencies (TODO: should detect cycles) */
317 requiredProducts = [[product requiredProducts] objectEnumerator];
318 while ((rqname = [requiredProducts nextObject])) {
319 if (![self loadProductNamed:rqname]) {
320 if ([rqname isEqualToString:@"MAIN"]) continue;
321 [self errorWithFormat:@"failed to load product %@ required by %@.",
327 return [product load];
330 - (BOOL)loadAllProducts {
334 e = [self->products keyEnumerator];
335 while ((p = [e nextObject])) {
336 if (![self loadProductNamed:p])
337 [self logWithFormat:@"could not load product: %@", p];
342 /* lookup products */
344 - (NSArray *)registeredProductNames {
345 return [self->products allKeys];
347 - (SoProduct *)productWithName:(NSString *)_name {
348 return [self->products objectForKey:_name];
353 - (SoProduct *)productForBundle:(NSBundle *)_bundle {
354 /* TODO: add a registry based on path ... */
355 NSString *pname, *bpath;
358 bpath = [_bundle bundlePath];
360 /* check whether a name is cached for the bundle .. */
362 pname = [self->bundlePathToFirstName objectForKey:bpath];
363 if ((product = [self productWithName:pname]) != nil)
366 /* 'calculate' name of bundle */
368 pname = [[bpath lastPathComponent] stringByDeletingPathExtension];
369 if ((product = [self productWithName:pname]) != nil)
372 /* load missing product */
375 @"product '%@' not yet registered, attempting to load ...", pname];
376 if (![self loadProductNamed:pname])
378 return [self productWithName:pname];
381 /* product registry as a SoObject */
383 - (NSArray *)allKeys {
384 return [self registeredProductNames];
387 - (BOOL)hasName:(NSString *)_key inContext:(id)_ctx {
388 if ([self->products objectForKey:_key])
390 return [super hasName:_key inContext:_ctx];
393 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
396 if ((product = [self productWithName:_key]))
399 return [super lookupName:_key inContext:_ctx acquire:_flag];
402 - (NSArray *)toOneRelationshipKeys {
406 if ((root = [super toOneRelationshipKeys]) == nil)
407 return [self->products allKeys];
409 ma = [[NSMutableSet alloc] initWithArray:root];
410 [ma addObjectsFromArray:[self->products allKeys]];
411 root = [ma allObjects];
418 - (NSString *)loggingPrefix {
419 return @"[so-product-registry]";
421 - (BOOL)isDebuggingEnabled {
422 return debugOn ? YES : NO;
425 /* web representation */
427 - (void)appendToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
431 [_r appendContentString:@"<h3>SOPE Product Registry</h3>"];
433 e = [[self toOneRelationshipKeys] objectEnumerator];
434 while ((name = [e nextObject])) {
435 [_r appendContentString:@"<li><a href=\""];
436 [_r appendContentHTMLAttributeValue:name];
437 [_r appendContentString:@"\">"];
438 [_r appendContentHTMLString:name];
439 [_r appendContentString:@"</a></li>"];
443 @end /* SoProductRegistry */