1/*
2  Copyright (C) 2005-2006 SKYRIX Software AG
3  Copyright (C) 2006      Helge Hess
4
5  This file is part of SOPE.
6
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
10  later version.
11
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.
16
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
20  02111-1307, USA.
21*/
22
23#include "WEResourceManager.h"
24#include "WEStringTableManager.h"
25#include "WEResourceKey.h"
26#include "common.h"
27
28@implementation WEResourceManager
29
30static BOOL          debugOn         = NO;
31static BOOL          debugComponents = NO;
32static NSArray       *wsPathes       = nil;
33static NSArray       *templatePathes = nil;
34static NSString      *suffix   = nil;
35static NSString      *prefix   = nil;
36static NSFileManager *fm   = nil;
37static NSNull        *null = nil;
38static NSString *themesDirName = @"Themes";
39
40+ (NSString *)shareSubpath {
41  static NSString *shareSubPath = nil;
42  NSString *p;
43
44  if (shareSubPath != nil)
45    return shareSubPath;
46
47  p = [[WOApplication application] shareDirectoryName];
48  p = [@"share/" stringByAppendingString:p];
49  p = [p stringByAppendingString:@"/"];
50  shareSubPath = [p copy];
51  return shareSubPath;
52}
53
54+ (NSString *)gsTemplatesSubpath {
55  NSString *p;
56  p = [[WOApplication application] gsTemplatesDirectoryName];
57#if ! GNUSTEP_BASE_LIBRARY
58  // for GNUSTEP_BASE_LIBRARY this is already there in rootPathesInGNUstep
59  p = [@"Library/" stringByAppendingString:p];
60#endif
61  return p;
62}
63+ (NSString *)gsWebSubpath {
64  NSString *p;
65
66  p = [[WOApplication application] gsWebDirectoryName];
67#if ! GNUSTEP_BASE_LIBRARY
68  // for GNUSTEP_BASE_LIBRARY this is already there in rootPathesInGNUstep
69  p = [@"Library/" stringByAppendingString:p];
70#endif
71  return p;
72}
73
74/* locate resource directories */
75
76+ (NSArray *)rootPathesInGNUstep {
77  id tmp;
78#if GNUSTEP_BASE_LIBRARY
79  NSEnumerator *libraryPaths;
80  NSString *directory;
81
82  tmp = [NSMutableArray array];
83  libraryPaths = [NSStandardLibraryPaths() objectEnumerator];
84  while ((directory = [libraryPaths nextObject]))
85    [tmp addObject: directory];
86  return tmp;
87#else
88  NSDictionary *env;
89  env = [[NSProcessInfo processInfo] environment];
90  if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
91    tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
92#endif
93
94  return [tmp componentsSeparatedByString:@":"];
95}
96+ (NSArray *)rootPathesInFHS {
97  return [NSArray arrayWithObjects:
98#ifdef FHS_INSTALL_ROOT
99		    FHS_INSTALL_ROOT,
100#endif
101		    @"/usr/local/", @"/usr/", nil];
102}
103
104+ (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name
105  fhsName:(NSString *)_fhs
106{
107  /* find directories which might contain resources */
108  NSEnumerator   *e;
109  NSFileManager  *fm;
110  NSMutableArray *ma;
111  BOOL           isDir;
112  id tmp;
113  fm  = [NSFileManager defaultManager];
114  ma  = [NSMutableArray arrayWithCapacity:8];
115
116#ifdef GNUSTEP_BASE_LIBRARY
117  NSEnumerator *libraryPaths;
118  NSString *directory;
119
120  libraryPaths = [NSStandardLibraryPaths() objectEnumerator];
121  while ((directory = [libraryPaths nextObject]))
122    [ma addObject: [directory stringByAppendingPathComponent: _name]];
123#else
124
125  e = [[self rootPathesInGNUstep] objectEnumerator];
126  while ((tmp = [e nextObject]) != nil) {
127    if (![tmp hasSuffix:@"/"])
128      tmp = [tmp stringByAppendingString:@"/"];
129
130    tmp = [tmp stringByAppendingString:_name];
131    if ([ma containsObject:tmp]) continue;
132
133    if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
134    if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
135      continue;
136
137    if (!isDir) continue;
138
139    [ma addObject:tmp];
140  }
141#endif
142
143  /* hack in FHS pathes */
144
145  e = [[self rootPathesInFHS] objectEnumerator];
146  while ((tmp = [e nextObject]) != nil) {
147    tmp = [tmp stringByAppendingString:[[self class] shareSubpath]];
148    tmp = [tmp stringByAppendingString:_fhs];
149    if ([ma containsObject:tmp]) continue;
150    if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
151
152    if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
153      continue;
154    if (!isDir) {
155      [self logWithFormat:@"path is not a directory: %@", tmp];
156      continue;
157    }
158
159    [ma addObject:tmp];
160  }
161
162  return ma;
163}
164
165+ (void)initialize {
166  static BOOL isInitialized = NO;
167  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
168  if (isInitialized) return;
169  isInitialized = YES;
170
171  null = [[NSNull null] retain];
172
173  if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"]))
174    NSLog(@"Note: WEResourceManager debugging is enabled.");
175  debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"];
176  if (debugComponents)
177    NSLog(@"Note: WEResourceManager component debugging is enabled.");
178
179  fm = [[NSFileManager defaultManager] retain];
180
181  suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
182  prefix = [[ud stringForKey:@"WOResourcePrefix"]    copy];
183
184  wsPathes = [[self findResourceDirectoryPathesWithName:
185		      [self gsWebSubpath] fhsName:@"www/"] copy];
186  if (debugOn)
187    NSLog(@"WebServerResources pathes: %@", wsPathes);
188
189  // TODO: use appname to enable different setups, maybe some var too
190  templatePathes = [[self findResourceDirectoryPathesWithName:
191			    [[self class] gsTemplatesSubpath]
192			  fhsName:@"templates/"] copy];
193  if (debugOn)
194    NSLog(@"template pathes: %@", templatePathes);
195  if (![templatePathes isNotEmpty]) {
196    NSLog(@"Note: found no directories containing flat templates (subpath=%@)",
197	  [[self class] gsTemplatesSubpath]);
198  }
199}
200
201+ (NSArray *)availableThemes {
202  static NSArray *lthemes = nil;
203  NSMutableSet  *themes;
204  NSEnumerator  *e;
205  NSFileManager *fm;
206  NSString      *path;
207
208  if (lthemes != nil)
209    return lthemes;
210
211  themes = [NSMutableSet setWithCapacity:16];
212  fm     = [NSFileManager defaultManager];
213
214  e = [templatePathes objectEnumerator];
215  while ((path = [e nextObject]) != nil) {
216    NSArray *dl;
217
218    path = [path stringByAppendingPathComponent:themesDirName];
219    dl   = [fm directoryContentsAtPath:path];
220
221    [themes addObjectsFromArray:dl];
222  }
223
224  /* remove directories to be ignored */
225  [themes removeObject:@".svn"];
226  [themes removeObject:@"CVS"];
227
228  lthemes = [[[themes allObjects]
229	       sortedArrayUsingSelector:@selector(compare:)] copy];
230  if ([lthemes isNotEmpty]) {
231    NSLog(@"Note: located themes: %@",
232	  [lthemes componentsJoinedByString:@", "]);
233  }
234  else
235    NSLog(@"Note: located no additional themes.");
236  return lthemes;
237}
238
239- (id)initWithPath:(NSString *)_path {
240  if ((self = [super initWithPath:_path]) != nil) {
241    if ([WOApplication isCachingEnabled]) {
242      self->keyToComponentPath =
243	[[NSMutableDictionary alloc] initWithCapacity:128];
244
245      self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024];
246      self->keyToURL  = [[NSMutableDictionary alloc] initWithCapacity:1024];
247    }
248    else
249      [self logWithFormat:@"Note: component path caching is disabled!"];
250
251    self->labelManager = [[WEStringTableManager alloc] init];
252    self->cachedKey    = [[WEResourceKey alloc] initCachedKey];
253  }
254  return self;
255}
256- (id)init {
257  // TODO: maybe search and set some 'share/ogo' path?
258  return [self initWithPath:nil];
259}
260
261- (void)dealloc {
262  [self->cachedKey          release];
263  [self->labelManager       release];
264  [self->keyToURL           release];
265  [self->keyToPath          release];
266  [self->keyToComponentPath release];
267  [super dealloc];
268}
269
270/* accessors */
271
272- (NGBundleManager *)bundleManager {
273  static NGBundleManager *bm = nil; // THREAD
274  if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
275  return bm;
276}
277
278/* resource cache */
279
280static id
281checkCache(NSDictionary *_cache, WEResourceKey *_key,
282	   NSString *_n, NSString *_fw, NSString *_l)
283{
284  if (_cache == nil) {
285    if (debugOn) NSLog(@"cache disabled.");
286    return nil; /* caching disabled */
287  }
288
289  /* setup cache key (THREAD) */
290  _key->hashValue     = 0; /* reset, calculate on next access */
291  _key->name          = _n;
292  _key->frameworkName = _fw;
293  _key->language      = _l;
294
295  return [_cache objectForKey:_key];
296}
297
298- (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache {
299  WEResourceKey *k;
300
301  if (_cache == nil) return; /* caching disabled */
302
303  /* we need to dup, because the cachedKey does not retain! */
304  k = [self->cachedKey duplicate];
305
306  if (debugOn) {
307    [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count],
308	    _value];
309  }
310
311  [_cache setObject:(_value ? _value : (id)null) forKey:k];
312  [k release]; k = nil;
313}
314
315/* locate Resources */
316
317- (NSString *)_weCheckPath:(NSString *)_p forResourceNamed:(NSString *)_name
318  inFramework:(NSString *)_frameworkName
319  language:(NSString *)_language
320{
321  NSString *path;
322
323  path = [_frameworkName isNotEmpty]
324    ? [_p stringByAppendingPathComponent:_frameworkName]
325    : _p;
326
327  /* check language */
328  if (_language != nil) {
329    path = [path stringByAppendingPathComponent:_language];
330    path = [path stringByAppendingPathExtension:@"lproj"];
331  }
332
333  path = [path stringByAppendingPathComponent:_name];
334  if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
335
336  if (![fm fileExistsAtPath:path])
337    return nil;
338
339  return path;
340}
341
342- (NSString *)_weCheckPathes:(NSArray *)_p forResourceNamed:(NSString *)_name
343  inFramework:(NSString *)_frameworkName
344  language:(NSString *)_language
345{
346  NSEnumerator *e;
347  NSString     *path;
348
349  e = [_p objectEnumerator];
350  while ((path = [e nextObject]) != nil) {
351    path = [self _weCheckPath:path forResourceNamed:_name
352		 inFramework:_frameworkName language:_language];
353    if (path != nil) {
354      if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path];
355      return path;
356    }
357  }
358  return nil;
359}
360
361- (NSString *)_wePathForResourceNamed:(NSString *)_name
362  inFramework:(NSString *)_fwName
363  language:(NSString *)_lang
364  searchPathes:(NSArray *)_pathes
365{
366  // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing
367  NSString *path;
368
369  if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name];
370
371  /* check cache */
372
373  path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang);
374  if (path != nil) {
375    if (debugOn) [self debugWithFormat:@"  found in cache: %@", path];
376    return [path isNotNull] ? path : (NSString *)nil;
377  }
378
379  /* check for framework resources (webserver resources + framework) */
380
381  if (debugOn)
382    [self debugWithFormat:@"check framework resources ..."];
383  path = [self _weCheckPathes:_pathes forResourceNamed:_name
384	       inFramework:_fwName language:_lang];
385  if (path != nil) {
386    [self cacheValue:path inCache:self->keyToPath];
387    return path;
388  }
389
390  /* check in basepath of webserver resources */
391
392  // TODO: where is the difference, same call like above?
393  if (debugOn) [self debugWithFormat:@"check global resources ..."];
394  path = [self _weCheckPathes:_pathes forResourceNamed:_name
395	       inFramework:_fwName language:_lang];
396  if (path != nil) {
397    [self cacheValue:path inCache:self->keyToPath];
398    return path;
399  }
400
401  /* finished processing */
402  if (debugOn)
403    [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey];
404  return nil;
405}
406
407- (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
408  if ([_name hasSuffix:@".wox"]) return NO;
409  if ([_name hasSuffix:@".wo"])  return NO;
410  return YES;
411}
412
413- (NSString *)_wePathForResourceNamed:(NSString *)_name
414  inFramework:(NSString *)_fwName
415  language:(NSString *)_lang
416{
417  NSString *p;
418
419  /* check in webserver resources */
420
421  if ([self shouldLookupResourceInWebServerResources:_name]) {
422    p = [self _wePathForResourceNamed:_name inFramework:_fwName
423              language:_lang searchPathes:wsPathes];
424    if (p != nil) return p;
425  }
426
427  return nil;
428}
429
430- (BOOL)isTemplateResourceName:(NSString *)_name {
431  // TODO: non-extensible
432  return [_name hasSuffix:@".wox"];
433}
434
435- (NSString *)pathForResourceNamed:(NSString *)_name
436  inFramework:(NSString *)_fwName
437  languages:(NSArray *)_langs
438{
439  /*
440     Note: this is also called by the superclass method
441           -pathToComponentNamed:inFramework: for each registered component
442	   extension.
443  */
444  NSEnumerator *e;
445  NSString     *language;
446  NSString     *rpath;
447
448  if (![_name isNotEmpty]) {
449    [self debugWithFormat:@"got no name for resource lookup?!"];
450    return nil;
451  }
452
453  if (debugOn) {
454    [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)",
455            _name, _fwName, [_langs componentsJoinedByString:@","]];
456  }
457
458  if ([self isTemplateResourceName:_name]) {
459    if (debugOn) [self debugWithFormat:@"  is template resource .."];
460    return [self pathToComponentNamed:[_name stringByDeletingPathExtension]
461		 inFramework:_fwName
462		 languages:_langs];
463  }
464
465  /* check languages */
466
467  e = [_langs objectEnumerator];
468  while ((language = [e nextObject]) != nil) {
469      NSString *rpath;
470
471      if (debugOn)
472	[self logWithFormat:@"  check language (%@): '%@'", _name, language];
473      rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
474		    language:language];
475      if (rpath != nil) {
476	if (debugOn) [self debugWithFormat:@"  FOUND: %@", rpath];
477	return rpath;
478      }
479  }
480
481  /* check without language */
482
483  rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
484		language:nil];
485  if (rpath != nil)
486    return rpath;
487
488  if (debugOn) {
489    [self debugWithFormat:
490	      @"did not find resource, try super lookup: '%@'", _name];
491  }
492
493  /* look using WOResourceManager */
494
495  rpath = [super pathForResourceNamed:_name inFramework:_fwName
496		 languages:_langs];
497  return rpath;
498}
499
500/* locate WebServerResources */
501
502- (NSString *)_urlForResourceNamed:(NSString *)_name
503  inFramework:(NSString *)_fwName
504  language:(NSString *)_lang
505  applicationName:(NSString *)_appName
506{
507  NSString     *url;
508  NSEnumerator *e;
509  NSString     *path;
510
511  if (debugOn) {
512    [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@",
513	    _name, _fwName, _lang];
514  }
515
516  /* check cache */
517
518  url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang);
519  if (url != nil) {
520    if (debugOn) {
521      [self debugWithFormat:@"  found in cache: %@ (#%d)", url,
522	      [self->keyToURL count]];
523    }
524    return [url isNotNull] ? url : (NSString *)nil;
525  }
526
527  if (debugOn) {
528    [self debugWithFormat:@"  not found in cache: %@ (%@,#%d)",
529	    url, self->cachedKey, [self->keyToURL count]];
530  }
531
532  /* check for framework resources */
533
534  if ([_fwName isNotEmpty]) {
535    if (debugOn)
536      [self debugWithFormat:@"check framework: '%@'", _fwName];
537    e = [wsPathes objectEnumerator];
538    while ((path = [e nextObject])) {
539      NSMutableString *ms;
540
541      path = [path stringByAppendingPathComponent:_fwName];
542
543      /* check language */
544      if (_lang) {
545        path = [path stringByAppendingPathComponent:_lang];
546        path = [path stringByAppendingPathExtension:@"lproj"];
547      }
548
549      path = [path stringByAppendingPathComponent:_name];
550      if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
551
552      if (![fm fileExistsAtPath:path])
553	continue;
554
555      ms = [[NSMutableString alloc] initWithCapacity:256];
556
557      if (prefix) [ms appendString:prefix];
558      if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
559      [ms appendString:_appName];
560      if (suffix) [ms appendString:suffix];
561      [ms appendString:[ms hasSuffix:@"/"]
562            ? @"WebServerResources/" : @"/WebServerResources/"];
563      [ms appendString:_fwName];
564      [ms appendString:@"/"];
565      if (_lang) {
566          [ms appendString:_lang];
567          [ms appendString:@".lproj/"];
568      }
569      [ms appendString:_name];
570
571      url = ms;
572      if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
573      goto done;
574    }
575  }
576
577  /* check for global resources */
578
579  if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."];
580  e = [wsPathes objectEnumerator];
581  while ((path = [e nextObject])) {
582    NSMutableString *ms;
583    NSString *fpath, *basepath;
584    NSDate *lastModified;
585
586    /* check language */
587    if (_lang) {
588      basepath = [path stringByAppendingPathComponent:_lang];
589      basepath = [basepath stringByAppendingPathExtension:@"lproj"];
590    }
591    else
592      basepath = path;
593
594    fpath = [basepath stringByAppendingPathComponent:_name];
595    if (debugOn) {
596      [self debugWithFormat:
597	      @"  check path: '%@'\n base: %@\n name: %@\n "
598	      @" path: %@\n lang: %@",
599	      fpath, basepath, _name, path, _lang];
600    }
601
602    if (![fm fileExistsAtPath:fpath])
603      continue;
604
605    lastModified = [[fm fileAttributesAtPath: fpath
606                                traverseLink: YES]
607                     fileModificationDate];
608
609    ms = [[NSMutableString alloc] initWithCapacity:256];
610
611    if (prefix) [ms appendString:prefix];
612    if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
613    [ms appendString:_appName];
614    if (suffix) [ms appendString:suffix];
615    [ms appendString:[ms hasSuffix:@"/"]
616          ? @"WebServerResources/" : @"/WebServerResources/"];
617    if (_lang) {
618      [ms appendString:_lang];
619      [ms appendString:@".lproj/"];
620    }
621    [ms appendString:_name];
622    [ms appendFormat: @"?lm=%u",
623        (unsigned) [lastModified timeIntervalSince1970]];
624
625    url = ms;
626    if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
627    goto done;
628  }
629
630  /* finished processing */
631  if (debugOn) {
632    [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey,
633	    [self->keyToURL count]];
634  }
635
636 done:
637  [self cacheValue:url inCache:self->keyToURL];
638  [url autorelease];
639
640  return url;
641}
642
643- (NSString *)urlForResourceNamed:(NSString *)_name
644  inFramework:(NSString *)_fwName
645  languages:(NSArray *)_langs
646  request:(WORequest *)_request
647{
648  NSEnumerator *e;
649  NSString     *language;
650  NSString     *url;
651  NSString     *appName;
652
653  if (![_name isNotEmpty]) {
654    if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"];
655    return nil;
656  }
657
658  if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name];
659
660  if (_langs == nil) {
661    _langs = [_request browserLanguages];
662    if (debugOn) {
663      [self debugWithFormat:@"using browser languages: %@",
664	      [_langs componentsJoinedByString:@", "]];
665    }
666  }
667  else if (debugOn) {
668    [self debugWithFormat:@"using given languages: %@",
669	    [_langs componentsJoinedByString:@","]];
670  }
671
672  appName = [_request applicationName];
673  if (appName == nil)
674    appName = [(WOApplication *)[WOApplication application] name];
675
676  /* check languages */
677
678  e = [_langs objectEnumerator];
679  while ((language = [e nextObject])) {
680    NSString *url;
681
682    if (debugOn) [self logWithFormat:@"  check language: '%@'", language];
683    url = [self _urlForResourceNamed:_name
684                inFramework:_fwName
685                language:language
686                applicationName:appName];
687    if (url != nil) {
688      if (debugOn) [self logWithFormat:@"  FOUND: %@", url];
689      return url;
690    }
691  }
692
693  /* check without language */
694
695  url = [self _urlForResourceNamed:_name
696              inFramework:_fwName
697              language:nil
698              applicationName:appName];
699  if (url != nil)
700    return url;
701
702  if (debugOn) {
703    [self debugWithFormat:
704	    @"did not find resource in try super lookup: '%@'", _name];
705  }
706
707  url = [super urlForResourceNamed:_name
708               inFramework:_fwName
709               languages:_langs
710               request:_request];
711
712  return url;
713}
714
715/* locate components */
716
717- (NSString *)lookupComponentInStandardPathes:(NSString *)_name
718  inFramework:(NSString *)_framework theme:(NSString *)_theme
719{
720  // TODO: what about languages/themes?!
721  NSEnumerator *e;
722  NSString *path;
723
724  if (debugComponents) {
725    [self logWithFormat:@"lookup component in std pathes: %@|%@|%@",
726	  _name, _framework, _theme];
727  }
728
729  if ([_theme isNotNull] && [_theme length] == 0)
730    _theme = nil;
731  if ([_framework isNotNull] && [_framework length] == 0)
732    _framework = nil;
733
734  e = [templatePathes objectEnumerator];
735  while ((path = [e nextObject]) != nil) {
736    NSString *pe;
737
738    if (_theme != nil) {
739      // TODO: should be lower case for FHS? or use a different path?
740      path = [path stringByAppendingPathComponent:themesDirName];
741      path = [path stringByAppendingPathComponent:_theme];
742    }
743
744    if (_framework != nil) {
745      NSString *pureName;
746
747      pureName = [_framework lastPathComponent];
748      pureName = [pureName stringByDeletingPathExtension];
749      path = [path stringByAppendingPathComponent:pureName];
750    }
751
752    path = [path stringByAppendingPathComponent:_name];
753
754    pe = [path stringByAppendingPathExtension:@"wox"];
755    if (debugComponents) [self logWithFormat:@"CHECK %@", pe];
756
757    if ([fm fileExistsAtPath:pe])
758      return pe;
759
760    pe = [path stringByAppendingPathExtension:@"html"];
761    if ([fm fileExistsAtPath:pe]) {
762      /*
763	 Note: we are passing in the path of the HTML template, this is some
764	       kind of hack to make the wrapper template builder look for the
765               $name.html/$name.wod in there.
766      */
767      return pe;
768    }
769  }
770
771  return nil;
772}
773- (NSString *)lookupComponentInStandardPathes:(NSString *)_name
774  inFramework:(NSString *)_framework languages:(NSArray *)_langs
775{
776  NSString *path;
777  NSString *theme;
778
779  if (debugComponents) {
780    [self logWithFormat:@"lookup component in std pathes(langs): %@|%@",
781	  _name, _framework];
782  }
783
784  /* extract theme from language array (we do not support nested themes ATM) */
785
786  theme = nil;
787  if ([_langs count] > 1) {
788    NSRange r;
789
790    theme = [_langs objectAtIndex:0];
791    r = [theme rangeOfString:@"_"];
792    theme = (r.length > 0)
793      ? [theme substringFromIndex:(r.location + r.length)]
794      : (NSString *)nil;
795  }
796  else
797    theme = nil;
798
799  /* check theme dirs */
800
801  if (theme != nil) {
802    path = [self lookupComponentInStandardPathes:_name inFramework:_framework
803		 theme:theme];
804    if (path != nil)
805      return path;
806  }
807
808  /* check base dirs */
809
810  path = [self lookupComponentInStandardPathes:_name inFramework:_framework
811	       theme:nil];
812  if (path != nil)
813    return path;
814
815  /* check without framework subdir */
816
817  if ([_framework isNotEmpty]) {
818    path = [self lookupComponentInStandardPathes:_name inFramework:nil
819		 theme:nil];
820    if (path != nil)
821      return path;
822  }
823
824  return nil;
825}
826
827- (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name {
828  // TODO: is this ever invoked?
829  NSFileManager *fm = nil;
830  NSString *wrapper = nil;
831  NSBundle *bundle  = nil;
832  NSString *path;
833
834  if (debugComponents)
835    [self logWithFormat:@"lookup component using bundle manager: %@", _name];
836
837  bundle = [[self bundleManager]
838                  bundleProvidingResource:_name
839                  ofType:@"WOComponents"];
840  if (bundle == nil) {
841    [self debugWithFormat:@"did not find a bundle providing component: %@",
842	    _name];
843    return nil;
844  }
845
846  if (debugOn) {
847    [self debugWithFormat:@"bundle %@ for component %@",
848            [[bundle bundlePath] lastPathComponent], _name];
849  }
850
851  [bundle load];
852
853  fm      = [NSFileManager defaultManager];
854  wrapper = [_name stringByAppendingPathExtension:@"wo"];
855
856  path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"];
857  path = [path stringByAppendingPathComponent:wrapper];
858  if ([fm fileExistsAtPath:path])
859    return path;
860
861  path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper];
862  if ([fm fileExistsAtPath:path])
863    return path;
864
865  return nil;
866}
867
868- (NSString *)pathToComponentNamed:(NSString *)_name
869  inFramework:(NSString *)_fw languages:(NSArray *)_langs
870{
871  // TODO: what about languages in lookup?
872  NSString *path;
873
874  if (debugComponents) {
875    [self logWithFormat:@"%s: lookup component: %@|%@",
876	  __PRETTY_FUNCTION__, _name, _fw];
877  }
878
879  /* first check cache */
880
881  path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw,
882		    [_langs isNotEmpty]
883		    ? [_langs objectAtIndex:0] : (id)@"English");
884  if (path != nil) {
885    if (debugComponents)
886      [self logWithFormat:@"  use cached location: %@", path];
887    return [path isNotNull] ? path : (NSString *)nil;
888  }
889
890  /* look in FHS locations */
891
892  path = [self lookupComponentInStandardPathes:_name inFramework:_fw
893	       languages:_langs];
894  if (path != nil) {
895    if (debugComponents)
896      [self logWithFormat:@"  found in standard pathes: %@", path];
897    goto done;
898  }
899
900  /* try to find component by standard NGObjWeb method */
901
902  if (debugComponents)
903    [self logWithFormat:@"lookup component using WOResourceManager ..."];
904  path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs];
905  if (path != nil) {
906    if (debugComponents)
907      [self logWithFormat:@"  found using WOResourceManager: %@", path];
908    goto done;
909  }
910
911  /* find component using NGBundleManager */
912
913  if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) {
914    if (debugComponents)
915      [self logWithFormat:@"  found using bundle manager: %@", path];
916    goto done;
917  }
918
919  /* did not find component */
920 done:
921  [self cacheValue:path inCache:self->keyToComponentPath];
922  return path;
923}
924
925/* string tables */
926
927- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
928  return [self->labelManager labelForKey:_key component:_component];
929}
930
931- (id)stringTableWithName:(NSString *)_tableName
932  inFramework:(NSString *)_framework
933  languages:(NSArray *)_languages
934{
935  id table;
936
937  table = [self->labelManager
938	       stringTableWithName:_tableName inFramework:_framework
939	       languages:_languages];
940  if (table != nil) return table;
941
942  return [super stringTableWithName:_tableName inFramework:_framework
943		languages:_languages];
944}
945
946- (NSString *)stringForKey:(NSString *)_key
947  inTableNamed:(NSString *)_tableName
948  withDefaultValue:(NSString *)_defaultValue
949  inFramework:(NSString *)_framework
950  languages:(NSArray *)_languages
951{
952  NSString *s;
953
954  s = [self->labelManager
955	   stringForKey:_key inTableNamed:_tableName
956	   withDefaultValue:_defaultValue languages:_languages];
957  if (s != nil) return s;
958
959  s = [super stringForKey:_key inTableNamed:_tableName
960	     withDefaultValue:_defaultValue
961	     inFramework:_framework
962	     languages:_languages];
963  if (s != nil) return s;
964
965  return s != nil ? s : _defaultValue;
966}
967
968/* debugging */
969
970- (BOOL)isDebuggingEnabled {
971  return debugOn;
972}
973- (NSString *)loggingPrefix {
974  return @"[we-rm]";
975}
976
977@end /* WEResourceManager */
978
979@implementation WOApplication(WEResourceManager)
980
981- (NSString *)shareDirectoryName {
982  return [[self name] lowercaseString];
983}
984- (NSString *)gsTemplatesDirectoryName {
985  return [[self name] stringByAppendingString:@"/Templates/"];
986}
987- (NSString *)gsWebDirectoryName {
988  return [[self name] stringByAppendingString:@"/WebServerResources/"];
989}
990
991@end /* WOApplication(WEResourceManager) */
992