2 Copyright (C) 2005-2006 SKYRIX Software AG
3 Copyright (C) 2006 Helge Hess
5 This file is part of SOPE.
7 SOPE is free software; you can redistribute it and/or modify it under
8 the terms of the GNU Lesser General Public License as published by the
9 Free Software Foundation; either version 2, or (at your option) any
12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with SOPE; see the file COPYING. If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "WEResourceManager.h"
24 #include "WEStringTableManager.h"
25 #include "WEResourceKey.h"
28 @implementation WEResourceManager
30 static BOOL debugOn = NO;
31 static BOOL debugComponents = NO;
32 static NSArray *wsPathes = nil;
33 static NSArray *templatePathes = nil;
34 static NSString *suffix = nil;
35 static NSString *prefix = nil;
36 static NSFileManager *fm = nil;
37 static NSNull *null = nil;
38 static NSString *themesDirName = @"Themes";
40 + (NSString *)shareSubpath {
41 static NSString *shareSubPath = nil;
44 if (shareSubPath != nil)
47 p = [[WOApplication application] shareDirectoryName];
48 p = [@"share/" stringByAppendingString:p];
49 p = [p stringByAppendingString:@"/"];
50 shareSubPath = [p copy];
54 + (NSString *)gsTemplatesSubpath {
57 p = [[WOApplication application] gsTemplatesDirectoryName];
58 p = [@"Library/" stringByAppendingString:p];
61 + (NSString *)gsWebSubpath {
64 p = [[WOApplication application] gsWebDirectoryName];
65 p = [@"Library/" stringByAppendingString:p];
69 /* locate resource directories */
71 + (NSArray *)rootPathesInGNUstep {
75 env = [[NSProcessInfo processInfo] environment];
76 if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
77 tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
79 return [tmp componentsSeparatedByString:@":"];
81 + (NSArray *)rootPathesInFHS {
82 return [NSArray arrayWithObjects:
83 #ifdef FHS_INSTALL_ROOT
86 @"/usr/local/", @"/usr/", nil];
89 + (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name
90 fhsName:(NSString *)_fhs
92 /* find directories which might contain resources */
99 fm = [NSFileManager defaultManager];
100 ma = [NSMutableArray arrayWithCapacity:8];
102 e = [[self rootPathesInGNUstep] objectEnumerator];
103 while ((tmp = [e nextObject]) != nil) {
104 if (![tmp hasSuffix:@"/"])
105 tmp = [tmp stringByAppendingString:@"/"];
107 tmp = [tmp stringByAppendingString:_name];
108 if ([ma containsObject:tmp]) continue;
110 if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
111 if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
114 if (!isDir) continue;
119 /* hack in FHS pathes */
121 e = [[self rootPathesInFHS] objectEnumerator];
122 while ((tmp = [e nextObject]) != nil) {
123 tmp = [tmp stringByAppendingString:[[self class] shareSubpath]];
124 tmp = [tmp stringByAppendingString:_fhs];
125 if ([ma containsObject:tmp]) continue;
126 if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
128 if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
131 [self logWithFormat:@"path is not a directory: %@", tmp];
142 return [super version] + 0 /* v4 */;
145 static BOOL isInitialized = NO;
146 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
147 if (isInitialized) return;
150 NSAssert2([super version] == 4,
151 @"invalid superclass (%@) version %i !",
152 NSStringFromClass([self superclass]), [super version]);
154 null = [[NSNull null] retain];
156 if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"]))
157 NSLog(@"Note: WEResourceManager debugging is enabled.");
158 debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"];
160 NSLog(@"Note: WEResourceManager component debugging is enabled.");
162 fm = [[NSFileManager defaultManager] retain];
164 suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
165 prefix = [[ud stringForKey:@"WOResourcePrefix"] copy];
167 wsPathes = [[self findResourceDirectoryPathesWithName:
168 [self gsWebSubpath] fhsName:@"www/"] copy];
170 NSLog(@"WebServerResources pathes: %@", wsPathes);
172 // TODO: use appname to enable different setups, maybe some var too
173 templatePathes = [[self findResourceDirectoryPathesWithName:
174 [[self class] gsTemplatesSubpath]
175 fhsName:@"templates/"] copy];
177 NSLog(@"template pathes: %@", templatePathes);
178 if (![templatePathes isNotEmpty]) {
179 NSLog(@"Note: found no directories containing flat templates (subpath=%@)",
180 [[self class] gsTemplatesSubpath]);
184 + (NSArray *)availableThemes {
185 static NSArray *lthemes = nil;
186 NSMutableSet *themes;
194 themes = [NSMutableSet setWithCapacity:16];
195 fm = [NSFileManager defaultManager];
197 e = [templatePathes objectEnumerator];
198 while ((path = [e nextObject]) != nil) {
201 path = [path stringByAppendingPathComponent:themesDirName];
202 dl = [fm directoryContentsAtPath:path];
204 [themes addObjectsFromArray:dl];
207 /* remove directories to be ignored */
208 [themes removeObject:@".svn"];
209 [themes removeObject:@"CVS"];
211 lthemes = [[[themes allObjects]
212 sortedArrayUsingSelector:@selector(compare:)] copy];
213 if ([lthemes isNotEmpty]) {
214 NSLog(@"Note: located themes: %@",
215 [lthemes componentsJoinedByString:@", "]);
218 NSLog(@"Note: located no additional themes.");
222 - (id)initWithPath:(NSString *)_path {
223 if ((self = [super initWithPath:_path]) != nil) {
224 if ([WOApplication isCachingEnabled]) {
225 self->keyToComponentPath =
226 [[NSMutableDictionary alloc] initWithCapacity:128];
228 self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024];
229 self->keyToURL = [[NSMutableDictionary alloc] initWithCapacity:1024];
232 [self logWithFormat:@"Note: component path caching is disabled!"];
234 self->labelManager = [[WEStringTableManager alloc] init];
235 self->cachedKey = [[WEResourceKey alloc] initCachedKey];
240 // TODO: maybe search and set some 'share/ogo' path?
241 return [self initWithPath:nil];
245 [self->cachedKey release];
246 [self->labelManager release];
247 [self->keyToURL release];
248 [self->keyToPath release];
249 [self->keyToComponentPath release];
255 - (NGBundleManager *)bundleManager {
256 static NGBundleManager *bm = nil; // THREAD
257 if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
264 checkCache(NSDictionary *_cache, WEResourceKey *_key,
265 NSString *_n, NSString *_fw, NSString *_l)
268 if (debugOn) NSLog(@"cache disabled.");
269 return nil; /* caching disabled */
272 /* setup cache key (THREAD) */
273 _key->hashValue = 0; /* reset, calculate on next access */
275 _key->frameworkName = _fw;
278 return [_cache objectForKey:_key];
281 - (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache {
284 if (_cache == nil) return; /* caching disabled */
286 /* we need to dup, because the cachedKey does not retain! */
287 k = [self->cachedKey duplicate];
290 [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count],
294 [_cache setObject:(_value ? _value : (id)null) forKey:k];
295 [k release]; k = nil;
298 /* locate Resources */
300 - (NSString *)_weCheckPath:(NSString *)_p forResourceNamed:(NSString *)_name
301 inFramework:(NSString *)_frameworkName
302 language:(NSString *)_language
306 path = [_frameworkName isNotEmpty]
307 ? [_p stringByAppendingPathComponent:_frameworkName]
311 if (_language != nil) {
312 path = [path stringByAppendingPathComponent:_language];
313 path = [path stringByAppendingPathExtension:@"lproj"];
316 path = [path stringByAppendingPathComponent:_name];
317 if (debugOn) [self debugWithFormat:@" check path: '%@'", path];
319 if (![fm fileExistsAtPath:path])
325 - (NSString *)_weCheckPathes:(NSArray *)_p forResourceNamed:(NSString *)_name
326 inFramework:(NSString *)_frameworkName
327 language:(NSString *)_language
332 e = [_p objectEnumerator];
333 while ((path = [e nextObject]) != nil) {
334 path = [self _weCheckPath:path forResourceNamed:_name
335 inFramework:_frameworkName language:_language];
337 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path];
344 - (NSString *)_wePathForResourceNamed:(NSString *)_name
345 inFramework:(NSString *)_fwName
346 language:(NSString *)_lang
347 searchPathes:(NSArray *)_pathes
349 // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing
352 if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name];
356 path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang);
358 if (debugOn) [self debugWithFormat:@" found in cache: %@", path];
359 return [path isNotNull] ? path : (NSString *)nil;
362 /* check for framework resources (webserver resources + framework) */
365 [self debugWithFormat:@"check framework resources ..."];
366 path = [self _weCheckPathes:_pathes forResourceNamed:_name
367 inFramework:_fwName language:_lang];
369 [self cacheValue:path inCache:self->keyToPath];
373 /* check in basepath of webserver resources */
375 // TODO: where is the difference, same call like above?
376 if (debugOn) [self debugWithFormat:@"check global resources ..."];
377 path = [self _weCheckPathes:_pathes forResourceNamed:_name
378 inFramework:_fwName language:_lang];
380 [self cacheValue:path inCache:self->keyToPath];
384 /* finished processing */
386 [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey];
390 - (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
391 if ([_name hasSuffix:@".wox"]) return NO;
392 if ([_name hasSuffix:@".wo"]) return NO;
396 - (NSString *)_wePathForResourceNamed:(NSString *)_name
397 inFramework:(NSString *)_fwName
398 language:(NSString *)_lang
402 /* check in webserver resources */
404 if ([self shouldLookupResourceInWebServerResources:_name]) {
405 p = [self _wePathForResourceNamed:_name inFramework:_fwName
406 language:_lang searchPathes:wsPathes];
407 if (p != nil) return p;
413 - (BOOL)isTemplateResourceName:(NSString *)_name {
414 // TODO: non-extensible
415 return [_name hasSuffix:@".wox"];
418 - (NSString *)pathForResourceNamed:(NSString *)_name
419 inFramework:(NSString *)_fwName
420 languages:(NSArray *)_langs
423 Note: this is also called by the superclass method
424 -pathToComponentNamed:inFramework: for each registered component
431 if (![_name isNotEmpty]) {
432 [self debugWithFormat:@"got no name for resource lookup?!"];
437 [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)",
438 _name, _fwName, [_langs componentsJoinedByString:@","]];
441 if ([self isTemplateResourceName:_name]) {
442 if (debugOn) [self debugWithFormat:@" is template resource .."];
443 return [self pathToComponentNamed:[_name stringByDeletingPathExtension]
448 /* check languages */
450 e = [_langs objectEnumerator];
451 while ((language = [e nextObject]) != nil) {
455 [self logWithFormat:@" check language (%@): '%@'", _name, language];
456 rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
459 if (debugOn) [self debugWithFormat:@" FOUND: %@", rpath];
464 /* check without language */
466 rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
472 [self debugWithFormat:
473 @"did not find resource, try super lookup: '%@'", _name];
476 /* look using WOResourceManager */
478 rpath = [super pathForResourceNamed:_name inFramework:_fwName
483 /* locate WebServerResources */
485 - (NSString *)_urlForResourceNamed:(NSString *)_name
486 inFramework:(NSString *)_fwName
487 language:(NSString *)_lang
488 applicationName:(NSString *)_appName
495 [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@",
496 _name, _fwName, _lang];
501 url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang);
504 [self debugWithFormat:@" found in cache: %@ (#%d)", url,
505 [self->keyToURL count]];
507 return [url isNotNull] ? url : (NSString *)nil;
511 [self debugWithFormat:@" not found in cache: %@ (%@,#%d)",
512 url, self->cachedKey, [self->keyToURL count]];
515 /* check for framework resources */
517 if ([_fwName isNotEmpty]) {
519 [self debugWithFormat:@"check framework: '%@'", _fwName];
520 e = [wsPathes objectEnumerator];
521 while ((path = [e nextObject])) {
524 path = [path stringByAppendingPathComponent:_fwName];
528 path = [path stringByAppendingString:_lang];
529 path = [path stringByAppendingPathExtension:@"lproj"];
532 path = [path stringByAppendingPathComponent:_name];
533 if (debugOn) [self debugWithFormat:@" check path: '%@'", path];
535 if (![fm fileExistsAtPath:path])
538 ms = [[NSMutableString alloc] initWithCapacity:256];
540 if (prefix) [ms appendString:prefix];
541 if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
542 [ms appendString:_appName];
543 if (suffix) [ms appendString:suffix];
544 [ms appendString:[ms hasSuffix:@"/"]
545 ? @"WebServerResources/" : @"/WebServerResources/"];
546 [ms appendString:_fwName];
547 [ms appendString:@"/"];
549 [ms appendString:_lang];
550 [ms appendString:@".lproj/"];
552 [ms appendString:_name];
555 [ms release]; ms = nil;
556 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
561 /* check for global resources */
563 if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."];
564 e = [wsPathes objectEnumerator];
565 while ((path = [e nextObject])) {
567 NSString *fpath, *basepath;
571 basepath = [path stringByAppendingString:_lang];
572 basepath = [basepath stringByAppendingPathExtension:@"lproj"];
577 fpath = [basepath stringByAppendingPathComponent:_name];
579 [self debugWithFormat:
580 @" check path: '%@'\n base: %@\n name: %@\n "
581 @" path: %@\n lang: %@",
582 fpath, basepath, _name, path, _lang];
585 if (![fm fileExistsAtPath:fpath])
588 ms = [[NSMutableString alloc] initWithCapacity:256];
590 if (prefix) [ms appendString:prefix];
591 if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
592 [ms appendString:_appName];
593 if (suffix) [ms appendString:suffix];
594 [ms appendString:[ms hasSuffix:@"/"]
595 ? @"WebServerResources/" : @"/WebServerResources/"];
597 [ms appendString:_lang];
598 [ms appendString:@".lproj/"];
600 [ms appendString:_name];
603 [ms release]; ms = nil;
604 if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
608 /* finished processing */
610 [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey,
611 [self->keyToURL count]];
615 [self cacheValue:url inCache:self->keyToURL];
619 - (NSString *)urlForResourceNamed:(NSString *)_name
620 inFramework:(NSString *)_fwName
621 languages:(NSArray *)_langs
622 request:(WORequest *)_request
629 if (![_name isNotEmpty]) {
630 if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"];
634 if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name];
637 _langs = [_request browserLanguages];
639 [self debugWithFormat:@"using browser languages: %@",
640 [_langs componentsJoinedByString:@", "]];
644 [self debugWithFormat:@"using given languages: %@",
645 [_langs componentsJoinedByString:@","]];
648 appName = [_request applicationName];
650 appName = [(WOApplication *)[WOApplication application] name];
652 /* check languages */
654 e = [_langs objectEnumerator];
655 while ((language = [e nextObject])) {
658 if (debugOn) [self logWithFormat:@" check language: '%@'", language];
659 url = [self _urlForResourceNamed:_name
662 applicationName:appName];
664 if (debugOn) [self logWithFormat:@" FOUND: %@", url];
669 /* check without language */
671 url = [self _urlForResourceNamed:_name
674 applicationName:appName];
679 [self debugWithFormat:
680 @"did not find resource in try super lookup: '%@'", _name];
683 url = [super urlForResourceNamed:_name
691 /* locate components */
693 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
694 inFramework:(NSString *)_framework theme:(NSString *)_theme
696 // TODO: what about languages/themes?!
700 if (debugComponents) {
701 [self logWithFormat:@"lookup component in std pathes: %@|%@|%@",
702 _name, _framework, _theme];
705 if ([_theme isNotNull] && [_theme length] == 0)
707 if ([_framework isNotNull] && [_framework length] == 0)
710 e = [templatePathes objectEnumerator];
711 while ((path = [e nextObject]) != nil) {
715 // TODO: should be lower case for FHS? or use a different path?
716 path = [path stringByAppendingPathComponent:themesDirName];
717 path = [path stringByAppendingPathComponent:_theme];
720 if (_framework != nil) {
723 pureName = [_framework lastPathComponent];
724 pureName = [pureName stringByDeletingPathExtension];
725 path = [path stringByAppendingPathComponent:pureName];
728 path = [path stringByAppendingPathComponent:_name];
730 pe = [path stringByAppendingPathExtension:@"wox"];
731 if (debugComponents) [self logWithFormat:@"CHECK %@", pe];
733 if ([fm fileExistsAtPath:pe])
736 pe = [path stringByAppendingPathExtension:@"html"];
737 if ([fm fileExistsAtPath:pe]) {
739 Note: we are passing in the path of the HTML template, this is some
740 kind of hack to make the wrapper template builder look for the
741 $name.html/$name.wod in there.
749 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
750 inFramework:(NSString *)_framework languages:(NSArray *)_langs
755 if (debugComponents) {
756 [self logWithFormat:@"lookup component in std pathes(langs): %@|%@",
760 /* extract theme from language array (we do not support nested themes ATM) */
763 if ([_langs count] > 1) {
766 theme = [_langs objectAtIndex:0];
767 r = [theme rangeOfString:@"_"];
768 theme = (r.length > 0)
769 ? [theme substringFromIndex:(r.location + r.length)]
775 /* check theme dirs */
778 path = [self lookupComponentInStandardPathes:_name inFramework:_framework
784 /* check base dirs */
786 path = [self lookupComponentInStandardPathes:_name inFramework:_framework
791 /* check without framework subdir */
793 if ([_framework isNotEmpty]) {
794 path = [self lookupComponentInStandardPathes:_name inFramework:nil
803 - (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name {
804 // TODO: is this ever invoked?
805 NSFileManager *fm = nil;
806 NSString *wrapper = nil;
807 NSBundle *bundle = nil;
811 [self logWithFormat:@"lookup component using bundle manager: %@", _name];
813 bundle = [[self bundleManager]
814 bundleProvidingResource:_name
815 ofType:@"WOComponents"];
817 [self debugWithFormat:@"did not find a bundle providing component: %@",
823 [self debugWithFormat:@"bundle %@ for component %@",
824 [[bundle bundlePath] lastPathComponent], _name];
829 fm = [NSFileManager defaultManager];
830 wrapper = [_name stringByAppendingPathExtension:@"wo"];
832 path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"];
833 path = [path stringByAppendingPathComponent:wrapper];
834 if ([fm fileExistsAtPath:path])
837 path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper];
838 if ([fm fileExistsAtPath:path])
844 - (NSString *)pathToComponentNamed:(NSString *)_name
845 inFramework:(NSString *)_fw languages:(NSArray *)_langs
847 // TODO: what about languages in lookup?
850 if (debugComponents) {
851 [self logWithFormat:@"%s: lookup component: %@|%@",
852 __PRETTY_FUNCTION__, _name, _fw];
855 /* first check cache */
857 path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw,
859 ? [_langs objectAtIndex:0] : (id)@"English");
862 [self logWithFormat:@" use cached location: %@", path];
863 return [path isNotNull] ? path : (NSString *)nil;
866 /* look in FHS locations */
868 path = [self lookupComponentInStandardPathes:_name inFramework:_fw
872 [self logWithFormat:@" found in standard pathes: %@", path];
876 /* try to find component by standard NGObjWeb method */
879 [self logWithFormat:@"lookup component using WOResourceManager ..."];
880 path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs];
883 [self logWithFormat:@" found using WOResourceManager: %@", path];
887 /* find component using NGBundleManager */
889 if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) {
891 [self logWithFormat:@" found using bundle manager: %@", path];
895 /* did not find component */
897 [self cacheValue:path inCache:self->keyToComponentPath];
903 - (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
904 return [self->labelManager labelForKey:_key component:_component];
907 - (id)stringTableWithName:(NSString *)_tableName
908 inFramework:(NSString *)_framework
909 languages:(NSArray *)_languages
913 table = [self->labelManager
914 stringTableWithName:_tableName inFramework:_framework
915 languages:_languages];
916 if (table != nil) return table;
918 return [super stringTableWithName:_tableName inFramework:_framework
919 languages:_languages];
922 - (NSString *)stringForKey:(NSString *)_key
923 inTableNamed:(NSString *)_tableName
924 withDefaultValue:(NSString *)_defaultValue
925 inFramework:(NSString *)_framework
926 languages:(NSArray *)_languages
930 s = [self->labelManager
931 stringForKey:_key inTableNamed:_tableName
932 withDefaultValue:_defaultValue languages:_languages];
933 if (s != nil) return s;
935 s = [super stringForKey:_key inTableNamed:_tableName
936 withDefaultValue:_defaultValue
937 inFramework:_framework
938 languages:_languages];
939 if (s != nil) return s;
941 return s != nil ? s : _defaultValue;
946 - (BOOL)isDebuggingEnabled {
949 - (NSString *)loggingPrefix {
953 @end /* WEResourceManager */
955 @implementation WOApplication(WEResourceManager)
957 - (NSString *)shareDirectoryName {
958 return [[self name] lowercaseString];
960 - (NSString *)gsTemplatesDirectoryName {
961 return [[self name] stringByAppendingString:@"/Templates/"];
963 - (NSString *)gsWebDirectoryName {
964 return [[self name] stringByAppendingString:@"/WebServerResources/"];
967 @end /* WOApplication(WEResourceManager) */