2 Copyright (C) 2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE 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 SOPE 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 SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "WEResourceManager.h"
23 #include "WEStringTableManager.h"
24 #include "WEResourceKey.h"
27 @implementation WEResourceManager
29 static BOOL debugOn = NO;
30 static BOOL debugComponents = NO;
31 static NSArray *wsPathes = nil;
32 static NSArray *templatePathes = nil;
33 static NSString *suffix = nil;
34 static NSString *prefix = nil;
35 static NSFileManager *fm = nil;
36 static NSNull *null = nil;
37 static NSString *themesDirName = @"Themes";
39 + (NSString *)shareSubpath {
40 static NSString *shareSubPath = nil;
43 if (shareSubPath != nil)
46 p = [[WOApplication application] shareDirectoryName];
47 p = [@"share/" stringByAppendingString:p];
48 p = [p stringByAppendingString:@"/"];
49 shareSubPath = [p copy];
53 + (NSString *)gsTemplatesSubpath {
56 p = [[WOApplication application] gsTemplatesDirectoryName];
57 p = [@"Library/" stringByAppendingString:p];
60 + (NSString *)gsWebSubpath {
63 p = [[WOApplication application] gsWebDirectoryName];
64 p = [@"Library/" stringByAppendingString:p];
68 /* locate resource directories */
70 + (NSArray *)rootPathesInGNUstep {
74 env = [[NSProcessInfo processInfo] environment];
75 if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
76 tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
78 return [tmp componentsSeparatedByString:@":"];
80 + (NSArray *)rootPathesInFHS {
81 return [NSArray arrayWithObjects:@"/usr/local/", @"/usr/", nil];
84 + (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name
85 fhsName:(NSString *)_fhs
93 fm = [NSFileManager defaultManager];
94 ma = [NSMutableArray arrayWithCapacity:8];
96 e = [[self rootPathesInGNUstep] objectEnumerator];
97 while ((tmp = [e nextObject]) != nil) {
98 if (![tmp hasSuffix:@"/"])
99 tmp = [tmp stringByAppendingString:@"/"];
101 tmp = [tmp stringByAppendingString:_name];
102 if ([ma containsObject:tmp]) continue;
104 if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
105 if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
108 if (!isDir) continue;
113 /* hack in FHS pathes */
115 e = [[self rootPathesInFHS] objectEnumerator];
116 while ((tmp = [e nextObject]) != nil) {
117 tmp = [tmp stringByAppendingString:[[self class] shareSubpath]];
118 tmp = [tmp stringByAppendingString:_fhs];
119 if ([ma containsObject:tmp]) continue;
121 if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
124 [self logWithFormat:@"path is not a directory: %@", tmp];
135 return [super version] + 0 /* v4 */;
138 static BOOL isInitialized = NO;
139 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
140 if (isInitialized) return;
143 NSAssert2([super version] == 4,
144 @"invalid superclass (%@) version %i !",
145 NSStringFromClass([self superclass]), [super version]);
147 null = [[NSNull null] retain];
149 if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"]))
150 NSLog(@"Note: WEResourceManager debugging is enabled.");
151 debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"];
153 NSLog(@"Note: WEResourceManager component debugging is enabled.");
155 fm = [[NSFileManager defaultManager] retain];
157 suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
158 prefix = [[ud stringForKey:@"WOResourcePrefix"] copy];
160 wsPathes = [[self findResourceDirectoryPathesWithName:
161 [self gsWebSubpath] fhsName:@"www/"] copy];
163 NSLog(@"WebServerResources pathes: %@", wsPathes);
165 // TODO: use appname to enable different setups, maybe some var too
166 templatePathes = [[self findResourceDirectoryPathesWithName:
167 [[self class] gsTemplatesSubpath]
168 fhsName:@"templates/"] copy];
170 NSLog(@"template pathes: %@", templatePathes);
171 if ([templatePathes count] == 0)
172 NSLog(@"Note: found no directories containing flat templates.");
175 + (NSArray *)availableThemes {
176 static NSArray *lthemes = nil;
177 NSMutableSet *themes;
185 themes = [NSMutableSet setWithCapacity:16];
186 fm = [NSFileManager defaultManager];
188 e = [templatePathes objectEnumerator];
189 while ((path = [e nextObject]) != nil) {
192 path = [path stringByAppendingPathComponent:themesDirName];
193 dl = [fm directoryContentsAtPath:path];
195 [themes addObjectsFromArray:dl];
198 /* remove directories to be ignored */
199 [themes removeObject:@".svn"];
200 [themes removeObject:@"CVS"];
202 lthemes = [[[themes allObjects]
203 sortedArrayUsingSelector:@selector(compare:)] copy];
204 if ([lthemes count] > 0) {
205 NSLog(@"Note: located themes: %@",
206 [lthemes componentsJoinedByString:@", "]);
209 NSLog(@"Note: located no additional themes.");
213 - (id)initWithPath:(NSString *)_path {
214 if ((self = [super initWithPath:_path])) {
215 if ([WOApplication isCachingEnabled]) {
216 self->keyToComponentPath =
217 [[NSMutableDictionary alloc] initWithCapacity:128];
219 self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024];
220 self->keyToURL = [[NSMutableDictionary alloc] initWithCapacity:1024];
223 [self logWithFormat:@"Note: component path caching is disabled!"];
225 self->labelManager = [[WEStringTableManager alloc] init];
226 self->cachedKey = [[WEResourceKey alloc] initCachedKey];
231 // TODO: maybe search and set some 'share/ogo' path?
232 return [self initWithPath:nil];
236 [self->cachedKey release];
237 [self->labelManager release];
238 [self->keyToURL release];
239 [self->keyToPath release];
240 [self->keyToComponentPath release];
246 - (NGBundleManager *)bundleManager {
247 static NGBundleManager *bm = nil; // THREAD
248 if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
255 checkCache(NSDictionary *_cache, WEResourceKey *_key,
256 NSString *_n, NSString *_fw, NSString *_l)
259 if (debugOn) NSLog(@"cache disabled.");
260 return nil; /* caching disabled */
263 /* setup cache key (THREAD) */
264 _key->hashValue = 0; /* reset, calculate on next access */
266 _key->frameworkName = _fw;
269 return [_cache objectForKey:_key];
272 - (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache {
275 if (_cache == nil) return; /* caching disabled */
277 /* we need to dup, because the cachedKey does not retain! */
278 k = [self->cachedKey duplicate];
281 [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count],
285 [_cache setObject:(_value ? _value : (id)null) forKey:k];
286 [k release]; k = nil;
289 /* locate Resources */
291 - (NSString *)_weCheckPath:(NSString *)_p forResourceNamed:(NSString *)_name
292 inFramework:(NSString *)_frameworkName
293 language:(NSString *)_language
297 path = [_frameworkName length] > 0
298 ? [_p stringByAppendingPathComponent:_frameworkName]
302 if (_language != nil) {
303 path = [path stringByAppendingPathComponent:_language];
304 path = [path stringByAppendingPathExtension:@"lproj"];
307 path = [path stringByAppendingPathComponent:_name];
308 if (debugOn) [self debugWithFormat:@" check path: '%@'", path];
310 if (![fm fileExistsAtPath:path])
316 - (NSString *)_weCheckPathes:(NSArray *)_p forResourceNamed:(NSString *)_name
317 inFramework:(NSString *)_frameworkName
318 language:(NSString *)_language
323 e = [_p objectEnumerator];
324 while ((path = [e nextObject]) != nil) {
325 path = [self _weCheckPath:path forResourceNamed:_name
326 inFramework:_frameworkName language:_language];
328 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path];
335 - (NSString *)_wePathForResourceNamed:(NSString *)_name
336 inFramework:(NSString *)_fwName
337 language:(NSString *)_lang
338 searchPathes:(NSArray *)_pathes
340 // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing
343 if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name];
347 path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang);
349 if (debugOn) [self debugWithFormat:@" found in cache: %@", path];
350 return [path isNotNull] ? path : nil;
353 /* check for framework resources (webserver resources + framework) */
356 [self debugWithFormat:@"check framework resources ..."];
357 path = [self _weCheckPathes:_pathes forResourceNamed:_name
358 inFramework:_fwName language:_lang];
360 [self cacheValue:path inCache:self->keyToPath];
364 /* check in basepath of webserver resources */
366 // TODO: where is the difference, same call like above?
367 if (debugOn) [self debugWithFormat:@"check global resources ..."];
368 path = [self _weCheckPathes:_pathes forResourceNamed:_name
369 inFramework:_fwName language:_lang];
371 [self cacheValue:path inCache:self->keyToPath];
375 /* finished processing */
377 [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey];
381 - (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
382 if ([_name hasSuffix:@".wox"]) return NO;
383 if ([_name hasSuffix:@".wo"]) return NO;
387 - (NSString *)_wePathForResourceNamed:(NSString *)_name
388 inFramework:(NSString *)_fwName
389 language:(NSString *)_lang
393 /* check in webserver resources */
395 if ([self shouldLookupResourceInWebServerResources:_name]) {
396 p = [self _wePathForResourceNamed:_name inFramework:_fwName
397 language:_lang searchPathes:wsPathes];
398 if (p != nil) return p;
404 - (BOOL)isTemplateResourceName:(NSString *)_name {
405 // TODO: non-extensible
406 return [_name hasSuffix:@".wox"];
409 - (NSString *)pathForResourceNamed:(NSString *)_name
410 inFramework:(NSString *)_fwName
411 languages:(NSArray *)_langs
414 Note: this is also called by the superclass method
415 -pathToComponentNamed:inFramework: for each registered component
422 if ([_name length] == 0) {
423 [self debugWithFormat:@"got no name for resource lookup?!"];
428 [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)",
429 _name, _fwName, [_langs componentsJoinedByString:@","]];
432 if ([self isTemplateResourceName:_name]) {
433 if (debugOn) [self debugWithFormat:@" is template resource .."];
434 return [self pathToComponentNamed:[_name stringByDeletingPathExtension]
439 /* check languages */
441 e = [_langs objectEnumerator];
442 while ((language = [e nextObject]) != nil) {
446 [self logWithFormat:@" check language (%@): '%@'", _name, language];
447 rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
450 if (debugOn) [self debugWithFormat:@" FOUND: %@", rpath];
455 /* check without language */
457 rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
463 [self debugWithFormat:
464 @"did not find resource, try super lookup: '%@'", _name];
467 /* look using WOResourceManager */
469 rpath = [super pathForResourceNamed:_name inFramework:_fwName
474 /* locate WebServerResources */
476 - (NSString *)_urlForResourceNamed:(NSString *)_name
477 inFramework:(NSString *)_fwName
478 language:(NSString *)_lang
479 applicationName:(NSString *)_appName
486 [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@",
487 _name, _fwName, _lang];
492 url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang);
495 [self debugWithFormat:@" found in cache: %@ (#%d)", url,
496 [self->keyToURL count]];
498 return [url isNotNull] ? url : nil;
502 [self debugWithFormat:@" not found in cache: %@ (%@,#%d)",
503 url, self->cachedKey, [self->keyToURL count]];
506 /* check for framework resources */
508 if ([_fwName length] > 0) {
510 [self debugWithFormat:@"check framework: '%@'", _fwName];
511 e = [wsPathes objectEnumerator];
512 while ((path = [e nextObject])) {
515 path = [path stringByAppendingPathComponent:_fwName];
519 path = [path stringByAppendingString:_lang];
520 path = [path stringByAppendingPathExtension:@"lproj"];
523 path = [path stringByAppendingPathComponent:_name];
524 if (debugOn) [self debugWithFormat:@" check path: '%@'", path];
526 if (![fm fileExistsAtPath:path])
529 ms = [[NSMutableString alloc] initWithCapacity:256];
531 if (prefix) [ms appendString:prefix];
532 if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
533 [ms appendString:_appName];
534 if (suffix) [ms appendString:suffix];
535 [ms appendString:[ms hasSuffix:@"/"]
536 ? @"WebServerResources/" : @"/WebServerResources/"];
537 [ms appendString:_fwName];
538 [ms appendString:@"/"];
540 [ms appendString:_lang];
541 [ms appendString:@".lproj/"];
543 [ms appendString:_name];
546 [ms release]; ms = nil;
547 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
552 /* check for global resources */
554 if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."];
555 e = [wsPathes objectEnumerator];
556 while ((path = [e nextObject])) {
558 NSString *fpath, *basepath;
562 basepath = [path stringByAppendingString:_lang];
563 basepath = [basepath stringByAppendingPathExtension:@"lproj"];
568 fpath = [basepath stringByAppendingPathComponent:_name];
570 [self debugWithFormat:
571 @" check path: '%@'\n base: %@\n name: %@\n "
572 @" path: %@\n lang: %@",
573 fpath, basepath, _name, path, _lang];
576 if (![fm fileExistsAtPath:fpath])
579 ms = [[NSMutableString alloc] initWithCapacity:256];
581 if (prefix) [ms appendString:prefix];
582 if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
583 [ms appendString:_appName];
584 if (suffix) [ms appendString:suffix];
585 [ms appendString:[ms hasSuffix:@"/"]
586 ? @"WebServerResources/" : @"/WebServerResources/"];
588 [ms appendString:_lang];
589 [ms appendString:@".lproj/"];
591 [ms appendString:_name];
594 [ms release]; ms = nil;
595 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
599 /* finished processing */
601 [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey,
602 [self->keyToURL count]];
606 [self cacheValue:url inCache:self->keyToURL];
610 - (NSString *)urlForResourceNamed:(NSString *)_name
611 inFramework:(NSString *)_fwName
612 languages:(NSArray *)_langs
613 request:(WORequest *)_request
620 if ([_name length] == 0) {
621 if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"];
625 if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name];
628 _langs = [_request browserLanguages];
630 [self debugWithFormat:@"using browser languages: %@",
631 [_langs componentsJoinedByString:@", "]];
635 [self debugWithFormat:@"using given languages: %@",
636 [_langs componentsJoinedByString:@","]];
639 appName = [_request applicationName];
641 appName = [(WOApplication *)[WOApplication application] name];
643 /* check languages */
645 e = [_langs objectEnumerator];
646 while ((language = [e nextObject])) {
649 if (debugOn) [self logWithFormat:@" check language: '%@'", language];
650 url = [self _urlForResourceNamed:_name
653 applicationName:appName];
655 if (debugOn) [self logWithFormat:@" FOUND: %@", url];
660 /* check without language */
662 url = [self _urlForResourceNamed:_name
665 applicationName:appName];
670 [self debugWithFormat:
671 @"did not find resource in try super lookup: '%@'", _name];
674 url = [super urlForResourceNamed:_name
682 /* locate components */
684 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
685 inFramework:(NSString *)_framework theme:(NSString *)_theme
687 // TODO: what about languages/themes?!
691 if (debugComponents) {
692 [self logWithFormat:@"lookup component in std pathes: %@|%@|%@",
693 _name, _framework, _theme];
696 if ([_theme isNotNull] && [_theme length] == 0)
698 if ([_framework isNotNull] && [_framework length] == 0)
701 e = [templatePathes objectEnumerator];
702 while ((path = [e nextObject]) != nil) {
706 // TODO: should be lower case for FHS? or use a different path?
707 path = [path stringByAppendingPathComponent:themesDirName];
708 path = [path stringByAppendingPathComponent:_theme];
711 if (_framework != nil) {
714 pureName = [_framework lastPathComponent];
715 pureName = [pureName stringByDeletingPathExtension];
716 path = [path stringByAppendingPathComponent:pureName];
719 path = [path stringByAppendingPathComponent:_name];
721 pe = [path stringByAppendingPathExtension:@"wox"];
722 if (debugComponents) [self logWithFormat:@"CHECK %@", pe];
724 if ([fm fileExistsAtPath:pe])
727 pe = [path stringByAppendingPathExtension:@"html"];
728 if ([fm fileExistsAtPath:pe]) {
730 Note: we are passing in the path of the HTML template, this is some
731 kind of hack to make the wrapper template builder look for the
732 $name.html/$name.wod in there.
740 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
741 inFramework:(NSString *)_framework languages:(NSArray *)_langs
746 if (debugComponents) {
747 [self logWithFormat:@"lookup component in std pathes(langs): %@|%@",
751 /* extract theme from language array (we do not support nested themes ATM) */
754 if ([_langs count] > 1) {
757 theme = [_langs objectAtIndex:0];
758 r = [theme rangeOfString:@"_"];
759 theme = (r.length > 0)
760 ? [theme substringFromIndex:(r.location + r.length)]
766 /* check theme dirs */
769 path = [self lookupComponentInStandardPathes:_name inFramework:_framework
775 /* check base dirs */
777 path = [self lookupComponentInStandardPathes:_name inFramework:_framework
782 /* check without framework subdir */
784 if ([_framework length] > 0) {
785 path = [self lookupComponentInStandardPathes:_name inFramework:nil
794 - (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name {
795 // TODO: is this ever invoked?
796 NSFileManager *fm = nil;
797 NSString *wrapper = nil;
798 NSBundle *bundle = nil;
802 [self logWithFormat:@"lookup component using bundle manager: %@", _name];
804 bundle = [[self bundleManager]
805 bundleProvidingResource:_name
806 ofType:@"WOComponents"];
808 [self debugWithFormat:@"did not find a bundle providing component: %@",
814 [self debugWithFormat:@"bundle %@ for component %@",
815 [[bundle bundlePath] lastPathComponent], _name];
820 fm = [NSFileManager defaultManager];
821 wrapper = [_name stringByAppendingPathExtension:@"wo"];
823 path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"];
824 path = [path stringByAppendingPathComponent:wrapper];
825 if ([fm fileExistsAtPath:path])
828 path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper];
829 if ([fm fileExistsAtPath:path])
835 - (NSString *)pathToComponentNamed:(NSString *)_name
836 inFramework:(NSString *)_fw languages:(NSArray *)_langs
838 // TODO: what about languages in lookup?
841 if (debugComponents) {
842 [self logWithFormat:@"%s: lookup component: %@|%@",
843 __PRETTY_FUNCTION__, _name, _fw];
846 /* first check cache */
848 path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw,
849 [_langs count] > 0 ? [_langs objectAtIndex:0]:@"English");
852 [self logWithFormat:@" use cached location: %@", path];
853 return [path isNotNull] ? path : nil;
856 /* look in FHS locations */
858 path = [self lookupComponentInStandardPathes:_name inFramework:_fw
862 [self logWithFormat:@" found in standard pathes: %@", path];
866 /* try to find component by standard NGObjWeb method */
869 [self logWithFormat:@"lookup component using WOResourceManager ..."];
870 path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs];
873 [self logWithFormat:@" found using WOResourceManager: %@", path];
877 /* find component using NGBundleManager */
879 if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) {
881 [self logWithFormat:@" found using bundle manager: %@", path];
885 /* did not find component */
887 [self cacheValue:path inCache:self->keyToComponentPath];
893 - (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
894 return [self->labelManager labelForKey:_key component:_component];
897 - (NSString *)stringForKey:(NSString *)_key
898 inTableNamed:(NSString *)_tableName
899 withDefaultValue:(NSString *)_default
900 languages:(NSArray *)_langs
904 s = [self->labelManager
905 stringForKey:_key inTableNamed:_tableName
906 withDefaultValue:_default languages:_langs];
907 if (s != nil) return s;
909 s = [super stringForKey:_key inTableNamed:_tableName
910 withDefaultValue:_default
912 if (s != nil) return s;
914 return s != nil ? s : _default;
919 - (BOOL)isDebuggingEnabled {
922 - (NSString *)loggingPrefix {
926 @end /* WEResourceManager */
928 @implementation WOApplication(WEResourceManager)
930 - (NSString *)shareDirectoryName {
931 return [[self name] lowercaseString];
933 - (NSString *)gsTemplatesDirectoryName {
934 return [[self name] stringByAppendingString:@"/Templates/"];
936 - (NSString *)gsWebDirectoryName {
937 return [[self name] stringByAppendingString:@"/WebServerResources/"];
940 @end /* WOApplication(WEResourceManager) */