From: helge Date: Thu, 17 Feb 2005 15:50:52 +0000 (+0000) Subject: added the WEResourceManager X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b6ad56e2df8bb2b4ccd54ff7062a6ed81203d908;p=sope added the WEResourceManager git-svn-id: http://svn.opengroupware.org/SOPE/trunk@567 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- diff --git a/sope-appserver/WEExtensions/ChangeLog b/sope-appserver/WEExtensions/ChangeLog index 8f76deb2..b71582b7 100644 --- a/sope-appserver/WEExtensions/ChangeLog +++ b/sope-appserver/WEExtensions/ChangeLog @@ -1,3 +1,9 @@ +2005-02-17 Helge Hess + + * added a modified variant of the OGoResourceManager and the required + support classes, this allows for integration of SOPE apps with FHS + (v4.5.66) + 2005-01-04 Marcus Mueller * common.h, JSClipboard.m: changed to use WOContext's new diff --git a/sope-appserver/WEExtensions/GNUmakefile b/sope-appserver/WEExtensions/GNUmakefile index 08ccd4a1..a992b17e 100644 --- a/sope-appserver/WEExtensions/GNUmakefile +++ b/sope-appserver/WEExtensions/GNUmakefile @@ -52,6 +52,11 @@ libWEExtensions_OBJC_FILES = \ WEEpozEditor.m \ WEQualifierConditional.m \ WERedirect.m \ + \ + WEResourceManager.m \ + WEResourceKey.m \ + WEStringTable.m \ + WEStringTableManager.m \ libWEExtensions_SUBPROJECTS += \ WETableView diff --git a/sope-appserver/WEExtensions/Version b/sope-appserver/WEExtensions/Version index 4651c3e2..f6b45b24 100644 --- a/sope-appserver/WEExtensions/Version +++ b/sope-appserver/WEExtensions/Version @@ -1,5 +1,5 @@ # version file -SUBMINOR_VERSION:=65 +SUBMINOR_VERSION:=66 # v4.5.65 requires libNGObjWeb v4.5.106 diff --git a/sope-appserver/WEExtensions/WEResourceKey.h b/sope-appserver/WEExtensions/WEResourceKey.h new file mode 100644 index 00000000..a36913ed --- /dev/null +++ b/sope-appserver/WEExtensions/WEResourceKey.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2004-2005 SKYRIX Software AG + + This file is part of SOPE. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __WEExtensions_OGoResourceKey_H__ +#define __WEExtensions_OGoResourceKey_H__ + +#import + +/* + WEResourceKey + + TODO: explain + + This class is used internally by WEResourceManager. +*/ + +@class NSString; + +@interface WEResourceKey : NSObject < NSCopying > +{ +@public + unsigned hashValue; + NSString *frameworkName; + NSString *name; + NSString *language; + struct { + int retainsValues:1; + int reserved:31; + } flags; +} + +- (id)initCachedKey; +- (id)duplicate; + +@end + +#endif /* __WEExtensions_OGoResourceKey_H__ */ diff --git a/sope-appserver/WEExtensions/WEResourceKey.m b/sope-appserver/WEExtensions/WEResourceKey.m new file mode 100644 index 00000000..3cb50c51 --- /dev/null +++ b/sope-appserver/WEExtensions/WEResourceKey.m @@ -0,0 +1,116 @@ +/* + Copyright (C) 2004-2005 Helge Hess + + This file is part of SOPE. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "WEResourceKey.h" +#include "common.h" + +@implementation WEResourceKey + +- (id)initCachedKey { + if ((self = [self init])) { + self->flags.retainsValues = 0; + } + return self; +} + +- (void)dealloc { + if (self->flags.retainsValues) { + [self->frameworkName release]; + [self->name release]; + [self->language release]; + } + [super dealloc]; +} + +/* NSCopying */ + +- (id)duplicate { + /* returns a retained object */ + WEResourceKey *newKey; + + newKey = [[[self class] alloc] init]; + newKey->flags.retainsValues = 1; + newKey->hashValue = self->hashValue; + newKey->frameworkName = [self->frameworkName copy]; + newKey->name = [self->name copy]; + newKey->language = [self->language copy]; + return newKey; +} + +- (id)copyWithZone:(NSZone *)_zone { + if (!self->flags.retainsValues) + return [self duplicate]; + + /* we are immutable */ + return [self retain]; +} + +/* equality */ + +- (unsigned)hash { + if (self->hashValue == 0) { + /* don't know whether this is smart, Nat! needs to comment ;-) */ + self->hashValue = [self->name hash]; + if (self->language != nil) + self->hashValue += [self->language characterAtIndex:0]; + } + return self->hashValue; +} + +- (BOOL)isEqual:(id)_other { + /* this method isn't very tolerant, but fast ;-) */ + WEResourceKey *okey; + + if (_other == nil) return NO; + if (_other == self) return YES; + if (*(Class*)_other != *(Class *)self) return NO; + okey = _other; + + if (self->name != okey->name) { + if (![self->name isEqualToString:okey->name]) + return NO; + } + if (self->language != okey->language) { + if (![self->language isEqualToString:okey->language]) + return NO; + } + if (self->frameworkName != okey->frameworkName) { + if (![self->frameworkName isEqualToString:okey->frameworkName]) + return NO; + } + return YES; +} + +/* description */ + +- (NSString *)description { + NSMutableString *ms; + + ms = [NSMutableString stringWithCapacity:128]; + [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])]; + if (self->name) [ms appendFormat:@" name=%@", self->name]; + if (self->frameworkName) [ms appendFormat:@" fw=%@", self->frameworkName]; + if (self->language) [ms appendFormat:@" lang=%@", self->language]; + [ms appendString:@">"]; + return ms; +} + +@end /* WEResourceKey */ diff --git a/sope-appserver/WEExtensions/WEResourceManager.h b/sope-appserver/WEExtensions/WEResourceManager.h new file mode 100644 index 00000000..ff9a0749 --- /dev/null +++ b/sope-appserver/WEExtensions/WEResourceManager.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2005 SKYRIX Software AG + + This file is part of SOPE. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __WEExtensions_WEResourceManager_H__ +#define __WEExtensions_WEResourceManager_H__ + +#include + +/* + WEResourceManager + + This class extends the WOResourceManager with the capability to separate + templates (whether .wox or .html) from the resources of a bundle. + + Instead of placing the templates inside the bundle, they will live in either + $GNUSTEP_xxx_ROOT/Library/$APPNAME/Templates/$BUNDLE/$TEMPLATE + or in + /usr/XX/share/$APPNAME/templates/$BUNDLE/$TEMPLATE +*/ + +@class NSArray, NSMutableDictionary; +@class WEStringTableManager, WEResourceKey; + +@interface WEResourceManager : WOResourceManager +{ +@private + NSMutableDictionary *keyToComponentPath; + NSMutableDictionary *keyToURL; + NSMutableDictionary *keyToPath; + WEStringTableManager *labelManager; + WEResourceKey *cachedKey; +} + ++ (NSArray *)rootPathesInGNUstep; /* GNUSTEP_PATHLIST */ ++ (NSArray *)rootPathesInFHS; /* /usr/local, /usr */ ++ (NSArray *)availableThemes; + +@end + + +#include + +@interface WOApplication(WEResourceManager) + +- (NSString *)shareDirectoryName; +- (NSString *)gsTemplatesDirectoryName; + +@end + +#endif /* __WEExtensions_WEResourceManager_H__ */ diff --git a/sope-appserver/WEExtensions/WEResourceManager.m b/sope-appserver/WEExtensions/WEResourceManager.m new file mode 100644 index 00000000..68fbc415 --- /dev/null +++ b/sope-appserver/WEExtensions/WEResourceManager.m @@ -0,0 +1,993 @@ +/* + Copyright (C) 2005 SKYRIX Software AG + + This file is part of SOPE. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "WEResourceManager.h" +#include "WEStringTableManager.h" +#include "WEResourceKey.h" +#include "common.h" + +@implementation WEResourceManager + +static BOOL debugOn = NO; +static BOOL debugComponents = NO; +static NSArray *wsPathes = nil; +static NSArray *templatePathes = nil; +static NSString *suffix = nil; +static NSString *prefix = nil; +static NSFileManager *fm = nil; +static NSNull *null = nil; +static NSString *themesDirName = @"Themes"; + ++ (NSString *)shareSubpath { + static NSString *shareSubPath = nil; + NSString *p; + + if (shareSubPath != nil) + return shareSubPath; + + p = [[WOApplication application] shareDirectoryName]; + p = [@"share/" stringByAppendingString:p]; + p = [p stringByAppendingString:@"/"]; + shareSubPath = [p copy]; + return shareSubPath; +} + ++ (NSString *)gsTemplatesSubpath { + static NSString *gsTemplatesSubpath = nil; + NSString *p; + + if (gsTemplatesSubpath != nil) + return gsTemplatesSubpath; + + p = [[WOApplication application] gsTemplatesDirectoryName]; + p = [@"Library/" stringByAppendingString:p]; + gsTemplatesSubpath = [p copy]; + return gsTemplatesSubpath; +} + +/* locate resource directories */ + ++ (NSArray *)rootPathesInGNUstep { + NSDictionary *env; + id tmp; + + env = [[NSProcessInfo processInfo] environment]; + if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil) + tmp = [env objectForKey:@"GNUSTEP_PATHLIST"]; + + return [tmp componentsSeparatedByString:@":"]; +} ++ (NSArray *)rootPathesInFHS { + return [NSArray arrayWithObjects:@"/usr/local/", @"/usr/", nil]; +} + ++ (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name + fhsName:(NSString *)_fhs +{ + NSEnumerator *e; + NSFileManager *fm; + NSMutableArray *ma; + BOOL isDir; + id tmp; + + fm = [NSFileManager defaultManager]; + ma = [NSMutableArray arrayWithCapacity:8]; + + e = [[self rootPathesInGNUstep] objectEnumerator]; + while ((tmp = [e nextObject]) != nil) { + if (![tmp hasSuffix:@"/"]) + tmp = [tmp stringByAppendingString:@"/"]; + + tmp = [tmp stringByAppendingString:_name]; + if ([ma containsObject:tmp]) continue; + + if (debugOn) [self logWithFormat:@"CHECK: %@", tmp]; + if (![fm fileExistsAtPath:tmp isDirectory:&isDir]) + continue; + + if (!isDir) continue; + + [ma addObject:tmp]; + } + + /* hack in FHS pathes */ + + e = [[self rootPathesInFHS] objectEnumerator]; + while ((tmp = [e nextObject]) != nil) { + tmp = [tmp stringByAppendingString:[[self class] shareSubpath]]; + tmp = [tmp stringByAppendingString:_fhs]; + if ([ma containsObject:tmp]) continue; + + if (![fm fileExistsAtPath:tmp isDirectory:&isDir]) + continue; + if (!isDir) { + [self logWithFormat:@"path is not a directory: %@", tmp]; + continue; + } + + [ma addObject:tmp]; + } + + return ma; +} + ++ (int)version { + return [super version] + 0 /* v4 */; +} ++ (void)initialize { + static BOOL isInitialized = NO; + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + if (isInitialized) return; + isInitialized = YES; + + NSAssert2([super version] == 4, + @"invalid superclass (%@) version %i !", + NSStringFromClass([self superclass]), [super version]); + + null = [[NSNull null] retain]; + + if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"])) + NSLog(@"Note: WEResourceManager debugging is enabled."); + debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"]; + if (debugComponents) + NSLog(@"Note: WEResourceManager component debugging is enabled."); + + fm = [[NSFileManager defaultManager] retain]; + + suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy]; + prefix = [[ud stringForKey:@"WOResourcePrefix"] copy]; + + wsPathes = [[self findResourceDirectoryPathesWithName: + @"WebServerResources/" fhsName:@"www/"] copy]; + if (debugOn) + NSLog(@"WebServerResources pathes: %@", wsPathes); + + // TODO: use appname to enable different setups, maybe some var too + templatePathes = [[self findResourceDirectoryPathesWithName: + [[self class] gsTemplatesSubpath] + fhsName:@"templates/"] copy]; + if (debugOn) + NSLog(@"template pathes: %@", templatePathes); + if ([templatePathes count] == 0) + NSLog(@"WARNING: found no directories containing SOPE templates!"); +} + ++ (NSArray *)availableThemes { + static NSArray *lthemes = nil; + NSMutableSet *themes; + NSEnumerator *e; + NSFileManager *fm; + NSString *path; + + if (lthemes != nil) + return lthemes; + + themes = [NSMutableSet setWithCapacity:16]; + fm = [NSFileManager defaultManager]; + + e = [templatePathes objectEnumerator]; + while ((path = [e nextObject]) != nil) { + NSArray *dl; + + path = [path stringByAppendingPathComponent:themesDirName]; + dl = [fm directoryContentsAtPath:path]; + + [themes addObjectsFromArray:dl]; + } + + /* remove directories to be ignored */ + [themes removeObject:@".svn"]; + [themes removeObject:@"CVS"]; + + lthemes = [[[themes allObjects] + sortedArrayUsingSelector:@selector(compare:)] copy]; + if ([lthemes count] > 0) { + NSLog(@"Note: located themes: %@", + [lthemes componentsJoinedByString:@", "]); + } + else + NSLog(@"Note: located no additional themes."); + return lthemes; +} + +- (id)initWithPath:(NSString *)_path { + if ((self = [super initWithPath:_path])) { + if ([WOApplication isCachingEnabled]) { + self->keyToComponentPath = + [[NSMutableDictionary alloc] initWithCapacity:128]; + + self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024]; + self->keyToURL = [[NSMutableDictionary alloc] initWithCapacity:1024]; + } + else + [self logWithFormat:@"Note: component path caching is disabled!"]; + + self->labelManager = [[WEStringTableManager alloc] init]; + self->cachedKey = [[WEResourceKey alloc] initCachedKey]; + } + return self; +} +- (id)init { + // TODO: maybe search and set some 'share/ogo' path? + return [self initWithPath:nil]; +} + +- (void)dealloc { + [self->cachedKey release]; + [self->labelManager release]; + [self->keyToURL release]; + [self->keyToPath release]; + [self->keyToComponentPath release]; + [super dealloc]; +} + +/* accessors */ + +- (NGBundleManager *)bundleManager { + static NGBundleManager *bm = nil; // THREAD + if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain]; + return bm; +} + +/* resource cache */ + +static id +checkCache(NSDictionary *_cache, WEResourceKey *_key, + NSString *_n, NSString *_fw, NSString *_l) +{ + if (_cache == nil) { + if (debugOn) NSLog(@"cache disabled."); + return nil; /* caching disabled */ + } + + /* setup cache key (THREAD) */ + _key->hashValue = 0; /* reset, calculate on next access */ + _key->name = _n; + _key->frameworkName = _fw; + _key->language = _l; + + return [_cache objectForKey:_key]; +} + +- (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache { + WEResourceKey *k; + + if (_cache == nil) return; /* caching disabled */ + + /* we need to dup, because the cachedKey does not retain! */ + k = [self->cachedKey duplicate]; + + if (debugOn) { + [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count], + _value]; + } + + [_cache setObject:(_value ? _value : (id)null) forKey:k]; + [k release]; k = nil; +} + +/* locate Resources */ + +- (NSString *)_checkPath:(NSString *)_p forResourceNamed:(NSString *)_name + inFramework:(NSString *)_frameworkName + language:(NSString *)_language +{ + NSString *path; + + path = [_frameworkName length] > 0 + ? [_p stringByAppendingPathComponent:_frameworkName] + : _p; + + /* check language */ + if (_language != nil) { + path = [path stringByAppendingPathComponent:_language]; + path = [path stringByAppendingPathExtension:@"lproj"]; + } + + path = [path stringByAppendingPathComponent:_name]; + if (debugOn) [self debugWithFormat:@" check path: '%@'", path]; + + if (![fm fileExistsAtPath:path]) + return nil; + + return path; +} + +- (NSString *)_checkPathes:(NSArray *)_p forResourceNamed:(NSString *)_name + inFramework:(NSString *)_frameworkName + language:(NSString *)_language +{ + NSEnumerator *e; + NSString *path; + + e = [_p objectEnumerator]; + while ((path = [e nextObject]) != nil) { + path = [self _checkPath:path forResourceNamed:_name + inFramework:_frameworkName language:_language]; + if (path != nil) { + if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path]; + return path; + } + } + return nil; +} + +- (NSString *)_pathForResourceNamed:(NSString *)_name + inFramework:(NSString *)_fwName + language:(NSString *)_lang + searchPathes:(NSArray *)_pathes +{ + // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing + NSString *path; + + if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name]; + + /* check cache */ + + path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang); + if (path != nil) { + if (debugOn) [self debugWithFormat:@" found in cache: %@", path]; + return [path isNotNull] ? path : nil; + } + + /* check for framework resources (webserver resources + framework) */ + + if (debugOn) + [self debugWithFormat:@"check framework resources ..."]; + path = [self _checkPathes:_pathes forResourceNamed:_name + inFramework:_fwName language:_lang]; + if (path != nil) { + [self cacheValue:path inCache:self->keyToPath]; + return path; + } + + /* check in basepath of webserver resources */ + + // TODO: where is the difference, same call like above? + if (debugOn) [self debugWithFormat:@"check global resources ..."]; + path = [self _checkPathes:_pathes forResourceNamed:_name + inFramework:_fwName language:_lang]; + if (path != nil) { + [self cacheValue:path inCache:self->keyToPath]; + return path; + } + + /* finished processing */ + if (debugOn) + [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey]; + return nil; +} + +- (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name { + if ([_name hasSuffix:@".wox"]) return NO; + if ([_name hasSuffix:@".wo"]) return NO; + return YES; +} + +- (NSString *)_pathForResourceNamed:(NSString *)_name + inFramework:(NSString *)_fwName + language:(NSString *)_lang +{ + NSString *p; + + /* check in webserver resources */ + + if ([self shouldLookupResourceInWebServerResources:_name]) { + p = [self _pathForResourceNamed:_name inFramework:_fwName + language:_lang searchPathes:wsPathes]; + if (p != nil) return p; + } + + return nil; +} + +- (NSString *)lookupComponentsConfigInFramework:(NSString *)_fwName + theme:(NSString *)_theme +{ + NSString *path; + NSEnumerator *e; + + /* check cache */ + + path = checkCache(self->keyToPath, self->cachedKey, @"components.cfg", + _fwName, _theme); + if (path != nil) { + if (debugOn) [self debugWithFormat:@" found in cache: %@", path]; + return [path isNotNull] ? path : nil; + } + + /* traverse template dirs */ + + e = [templatePathes objectEnumerator]; + while ((path = [e nextObject]) != nil) { + if (_theme != nil) { + path = [path stringByAppendingPathComponent:themesDirName]; + path = [path stringByAppendingPathComponent:_theme]; + } + if (_fwName != nil) + path = [path stringByAppendingPathComponent:_fwName]; + path = [path stringByAppendingPathComponent:@"components.cfg"]; + + if ([fm fileExistsAtPath:path]) { +#if 0 + [self logWithFormat:@"components.cfg theme %@ fw %@: %@", + _theme, _fwName, path]; +#endif + [self cacheValue:path inCache:self->keyToPath]; + return path; + } + } + + /* cache miss */ + [self cacheValue:nil inCache:self->keyToPath]; + return nil; +} + +- (NSString *)lookupComponentsConfigInFramework:(NSString *)_fwName + languages:(NSArray *)_langs +{ + NSEnumerator *e; + NSString *rpath; + + if (![_fwName isNotNull]) + _fwName = [[WOApplication application] name]; + + if (debugOn) { + [self debugWithFormat:@"lookup components cfg: %@/%@", _fwName, + [_langs componentsJoinedByString:@","]]; + } + + /* check themes */ + + e = [_langs objectEnumerator]; + while ((rpath = [e nextObject]) != nil) { + NSRange r; + NSString *theme; + + r = [rpath rangeOfString:@"_"]; + theme = (r.length > 0) + ? [rpath substringFromIndex:(r.location + r.length)] + : nil; /* default theme */ + + rpath = [self lookupComponentsConfigInFramework:_fwName theme:theme]; + if (rpath != nil) + return rpath; + + [self logWithFormat:@"Note: did not find components.cfg(%@) for theme: %@", + _fwName, (theme != nil ? theme : @"default-theme")]; + } + + /* look using OWResourceManager */ + + rpath = [super pathForResourceNamed:@"components.cfg" + inFramework:_fwName + languages:_langs]; + + /* not found */ + + if (rpath == nil) { + [self logWithFormat:@"Note: did not find components.cfg: %@/%@", + _fwName ? _fwName : @"[app]", + [_langs componentsJoinedByString:@","]]; + } + return rpath; +} + +- (NSString *)pathForResourceNamed:(NSString *)_name + inFramework:(NSString *)_fwName + languages:(NSArray *)_langs +{ + /* + Note: this is also called by the superclass method + -pathToComponentNamed:inFramework: for each registered component + extension. + */ + NSEnumerator *e; + NSString *language; + NSString *rpath; + + if ([_name length] == 0) { + if (debugOn) [self logWithFormat:@"got no name for resource lookup?!"]; + return nil; + } + + /* special handling for components.cfg */ + + if ([_name isEqualToString:@"components.cfg"]) + return [self lookupComponentsConfigInFramework:_fwName languages:_langs]; + + if (debugOn) { + [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)", + _name, _fwName, [_langs componentsJoinedByString:@","]]; + } + + /* check languages */ + + e = [_langs objectEnumerator]; + while ((language = [e nextObject])) { + NSString *rpath; + + if (debugOn) + [self logWithFormat:@" check language (%@): '%@'", _name, language]; + rpath = [self _pathForResourceNamed:_name inFramework:_fwName + language:language]; + if (rpath != nil) { + if (debugOn) [self logWithFormat:@" FOUND: %@", rpath]; + return rpath; + } + } + + /* check without language */ + + rpath = [self _pathForResourceNamed:_name inFramework:_fwName + language:nil]; + if (rpath != nil) + return rpath; + + if (debugOn) { + [self debugWithFormat: + @"did not find resource, try super lookup: '%@'", _name]; + } + + /* look using OWResourceManager */ + + rpath = [super pathForResourceNamed:_name inFramework:_fwName + languages:_langs]; + return rpath; +} + +/* locate WebServerResources */ + +- (NSString *)_urlForResourceNamed:(NSString *)_name + inFramework:(NSString *)_fwName + language:(NSString *)_lang + applicationName:(NSString *)_appName +{ + NSString *url; + NSEnumerator *e; + NSString *path; + + // TODO: some kind of hack ... - need to decide how we want to deal with + // the bundle less main component + _appName = @"OpenGroupware10a"; + + if (debugOn) { + [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@", + _name, _fwName, _lang]; + } + + /* check cache */ + + url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang); + if (url != nil) { + if (debugOn) { + [self debugWithFormat:@" found in cache: %@ (#%d)", url, + [self->keyToURL count]]; + } + return [url isNotNull] ? url : nil; + } + + if (debugOn) { + [self debugWithFormat:@" not found in cache: %@ (%@,#%d)", + url, self->cachedKey, [self->keyToURL count]]; + } + + /* check for framework resources */ + + if ([_fwName length] > 0) { + if (debugOn) + [self debugWithFormat:@"check framework: '%@'", _fwName]; + e = [wsPathes objectEnumerator]; + while ((path = [e nextObject])) { + NSMutableString *ms; + + path = [path stringByAppendingPathComponent:_fwName]; + + /* check language */ + if (_lang) { + path = [path stringByAppendingString:_lang]; + path = [path stringByAppendingPathExtension:@"lproj"]; + } + + path = [path stringByAppendingPathComponent:_name]; + if (debugOn) [self debugWithFormat:@" check path: '%@'", path]; + + if (![fm fileExistsAtPath:path]) + continue; + + ms = [[NSMutableString alloc] initWithCapacity:256]; + + if (prefix) [ms appendString:prefix]; + if (![ms hasSuffix:@"/"]) [ms appendString:@"/"]; + [ms appendString:_appName]; + if (suffix) [ms appendString:suffix]; + [ms appendString:[ms hasSuffix:@"/"] + ? @"WebServerResources/" : @"/WebServerResources/"]; + [ms appendString:_fwName]; + [ms appendString:@"/"]; + if (_lang) { + [ms appendString:_lang]; + [ms appendString:@".lproj/"]; + } + [ms appendString:_name]; + + url = [ms copy]; + [ms release]; ms = nil; + if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url]; + goto done; + } + } + + /* check for global resources */ + + if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."]; + e = [wsPathes objectEnumerator]; + while ((path = [e nextObject])) { + NSMutableString *ms; + NSString *fpath, *basepath; + + /* check language */ + if (_lang) { + basepath = [path stringByAppendingString:_lang]; + basepath = [basepath stringByAppendingPathExtension:@"lproj"]; + } + else + basepath = path; + + fpath = [basepath stringByAppendingPathComponent:_name]; + if (debugOn) { + [self debugWithFormat: + @" check path: '%@'\n base: %@\n name: %@\n " + @" path: %@\n lang: %@", + fpath, basepath, _name, path, _lang]; + } + + if (![fm fileExistsAtPath:fpath]) + continue; + + ms = [[NSMutableString alloc] initWithCapacity:256]; + + if (prefix) [ms appendString:prefix]; + if (![ms hasSuffix:@"/"]) [ms appendString:@"/"]; + [ms appendString:_appName]; + if (suffix) [ms appendString:suffix]; + [ms appendString:[ms hasSuffix:@"/"] + ? @"WebServerResources/" : @"/WebServerResources/"]; + if (_lang) { + [ms appendString:_lang]; + [ms appendString:@".lproj/"]; + } + [ms appendString:_name]; + + url = [ms copy]; + [ms release]; ms = nil; + if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url]; + goto done; + } + + /* finished processing */ + if (debugOn) { + [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey, + [self->keyToURL count]]; + } + + done: + [self cacheValue:url inCache:self->keyToURL]; + return url; +} + +- (NSString *)urlForResourceNamed:(NSString *)_name + inFramework:(NSString *)_fwName + languages:(NSArray *)_langs + request:(WORequest *)_request +{ + NSEnumerator *e; + NSString *language; + NSString *url; + NSString *appName; + + if ([_name length] == 0) { + if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"]; + return nil; + } + + if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name]; + + if (_langs == nil) { + _langs = [_request browserLanguages]; + if (debugOn) { + [self debugWithFormat:@"using browser languages: %@", + [_langs componentsJoinedByString:@", "]]; + } + } + else if (debugOn) { + [self debugWithFormat:@"using given languages: %@", + [_langs componentsJoinedByString:@","]]; + } + + appName = [(WOApplication *)[WOApplication application] name]; + if (appName == nil) + appName = [_request applicationName]; + + /* check languages */ + + e = [_langs objectEnumerator]; + while ((language = [e nextObject])) { + NSString *url; + + if (debugOn) [self logWithFormat:@" check language: '%@'", language]; + url = [self _urlForResourceNamed:_name + inFramework:_fwName + language:language + applicationName:appName]; + if (url) { + if (debugOn) [self logWithFormat:@" FOUND: %@", url]; + return url; + } + } + + /* check without language */ + + url = [self _urlForResourceNamed:_name + inFramework:_fwName + language:nil + applicationName:appName]; + if (url != nil) + return url; + + if (debugOn) { + [self debugWithFormat: + @"did not find resource in try super lookup: '%@'", _name]; + } + + url = [super urlForResourceNamed:_name + inFramework:_fwName + languages:_langs + request:_request]; + + return url; +} + +/* locate components */ + +- (NSString *)lookupComponentInStandardPathes:(NSString *)_name + inFramework:(NSString *)_framework theme:(NSString *)_theme +{ + // TODO: what about languages/themes?! + NSEnumerator *e; + NSString *path; + + e = [templatePathes objectEnumerator]; + while ((path = [e nextObject])) { + NSString *pe; + + if (_theme != nil) { + // TODO: should be lower case for FHS? or use a different path? + path = [path stringByAppendingPathComponent:themesDirName]; + path = [path stringByAppendingPathComponent:_theme]; + } + + if ([_framework length] > 0) { + NSString *pureName; + + pureName = [_framework lastPathComponent]; + pureName = [pureName stringByDeletingPathExtension]; + path = [path stringByAppendingPathComponent:pureName]; + } + + path = [path stringByAppendingPathComponent:_name]; + + pe = [path stringByAppendingPathExtension:@"wox"]; + if ([fm fileExistsAtPath:pe]) + return pe; + + pe = [path stringByAppendingPathExtension:@"html"]; + if ([fm fileExistsAtPath:pe]) { + /* + Note: we are passing in the path of the HTML template, this is some + kind of hack to make the wrapper template builder look for the + $name.html/$name.wod in there. + */ + return pe; + } + } + + return nil; +} +- (NSString *)lookupComponentInStandardPathes:(NSString *)_name + inFramework:(NSString *)_framework languages:(NSArray *)_langs +{ + NSString *path; + NSString *theme; + + /* extract theme from language array (we do not support nested themes ATM) */ + + theme = nil; + if ([_langs count] > 1) { + NSRange r; + + theme = [_langs objectAtIndex:0]; + r = [theme rangeOfString:@"_"]; + theme = (r.length > 0) + ? [theme substringFromIndex:(r.location + r.length)] + : nil; + } + else + theme = nil; + + /* check theme dirs */ + + if (theme != nil) { + path = [self lookupComponentInStandardPathes:_name inFramework:_framework + theme:theme]; + if (path != nil) + return path; + } + + /* check base dirs */ + + path = [self lookupComponentInStandardPathes:_name inFramework:_framework + theme:nil]; + if (path != nil) + return path; + + return nil; +} + +- (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name { + // TODO: is this ever invoked? + NSFileManager *fm = nil; + NSString *wrapper = nil; + NSBundle *bundle = nil; + NSString *path; + + bundle = [[self bundleManager] + bundleProvidingResource:_name + ofType:@"WOComponents"]; + if (bundle == nil) { + [self debugWithFormat:@"did not find a bundle providing component: %@", + _name]; + return nil; + } + + if (debugOn) { + [self debugWithFormat:@"bundle %@ for component %@", + [[bundle bundlePath] lastPathComponent], _name]; + } + + [bundle load]; + + fm = [NSFileManager defaultManager]; + wrapper = [_name stringByAppendingPathExtension:@"wo"]; + + path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"]; + path = [path stringByAppendingPathComponent:wrapper]; + if ([fm fileExistsAtPath:path]) + return path; + + path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper]; + if ([fm fileExistsAtPath:path]) + return path; + + return nil; +} + +- (NSString *)pathToComponentNamed:(NSString *)_name + inFramework:(NSString *)_fw languages:(NSArray *)_langs +{ + // TODO: what about languages in lookup? + NSString *path; + + if (debugComponents) { + [self logWithFormat:@"%s: lookup component: %@|%@", + __PRETTY_FUNCTION__, _name, _fw]; + } + + /* first check cache */ + + path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw, + [_langs count] > 0 ? [_langs objectAtIndex:0]:@"English"); + if (path != nil) { + if (debugComponents) + [self logWithFormat:@" use cached location: %@", path]; + return [path isNotNull] ? path : nil; + } + + /* look in FHS locations */ + + path = [self lookupComponentInStandardPathes:_name inFramework:_fw + languages:_langs]; + if (path != nil) { + if (debugComponents) + [self logWithFormat:@" found in standard pathes: %@", path]; + goto done; + } + + /* try to find component by standard NGObjWeb method */ + + path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs]; + if (path != nil) { + if (debugComponents) + [self logWithFormat:@" found using OWResourceManager: %@", path]; + goto done; + } + + /* find component using NGBundleManager */ + + if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) { + if (debugComponents) + [self logWithFormat:@" found using bundle manager: %@", path]; + goto done; + } + + /* did not find component */ + done: + [self cacheValue:path inCache:self->keyToComponentPath]; + return path; +} + +/* string tables */ + +- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{ + return [self->labelManager labelForKey:_key component:_component]; +} + +- (NSString *)stringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_langs +{ + NSString *s; + + s = [self->labelManager + stringForKey:_key inTableNamed:_tableName + withDefaultValue:_default languages:_langs]; + if (s != nil) return s; + + s = [super stringForKey:_key inTableNamed:_tableName + withDefaultValue:_default + languages:_langs]; + if (s != nil) return s; + + return s != nil ? s : _default; +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} +- (NSString *)loggingPrefix { + return @"[we-rm]"; +} + +@end /* WEResourceManager */ + +@implementation WOApplication(WEResourceManager) + +- (NSString *)shareDirectoryName { + return [[self name] lowercaseString]; +} +- (NSString *)gsTemplatesDirectoryName { + return [[self name] stringByAppendingString:@"/Templates/"]; +} + +@end /* WOApplication(WEResourceManager) */ diff --git a/sope-appserver/WEExtensions/WEStringTable.h b/sope-appserver/WEExtensions/WEStringTable.h new file mode 100644 index 00000000..bf7b2337 --- /dev/null +++ b/sope-appserver/WEExtensions/WEStringTable.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2004-2005 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo 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, or (at your option) any + later version. + + OGo 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 OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __OGoFoundation_WEStringTable_H__ +#define __OGoFoundation_WEStringTable_H__ + +#import + +/* + WEStringTable + + Keeps the contents of a .strings file (translation mapping file). + + TODO: support standard translation formats. +*/ + +@class NSString, NSDictionary, NSDate; + +@interface WEStringTable : NSObject +{ +@protected + NSString *path; + NSDictionary *data; + NSDate *lastRead; +} + ++ (id)stringTableWithPath:(NSString *)_path; +- (id)initWithPath:(NSString *)_path; + +- (NSString *)stringForKey:(NSString *)_key withDefaultValue:(NSString *)_def; + +@end + +#endif /* __OGoFoundation_WEStringTable_H__ */ diff --git a/sope-appserver/WEExtensions/WEStringTable.m b/sope-appserver/WEExtensions/WEStringTable.m new file mode 100644 index 00000000..3886b7ac --- /dev/null +++ b/sope-appserver/WEExtensions/WEStringTable.m @@ -0,0 +1,133 @@ +/* + Copyright (C) 2004-2005 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "WEStringTable.h" +#include "common.h" + +@implementation WEStringTable + +static BOOL debugOn = NO; +static BOOL useLatin1Strings = NO; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"WEStringTableDebugEnabled"]; + useLatin1Strings = [ud boolForKey:@"WEStringTableUseLatin1"]; +} + ++ (id)stringTableWithPath:(NSString *)_path { + return [[(WEStringTable *)[self alloc] initWithPath:_path] autorelease]; +} + +- (id)initWithPath:(NSString *)_path { + self->path = [_path copyWithZone:[self zone]]; + return self; +} + +- (void)dealloc { + [self->path release]; + [self->lastRead release]; + [self->data release]; + [super dealloc]; +} + +/* loading */ + +- (NSException *)reportParsingError:(NSException *)_error { + [self logWithFormat:@"%s: could not load strings file '%@': %@", + __PRETTY_FUNCTION__, self->path, _error]; + return nil; +} + +- (NSStringEncoding)stringsFileEncoding { + return useLatin1Strings ? NSISOLatin1StringEncoding : NSUTF8StringEncoding; +} + +- (void)checkState { + NSString *plistString; + NSDictionary *plist; + NSData *rawData; + + if (self->data != nil) + return; + + rawData = [[NSData alloc] initWithContentsOfMappedFile:self->path]; + if (rawData == nil) { + [self logWithFormat:@"ERROR: could not load strings file: %@", self->path]; + return; + } + + if (debugOn) { + [self debugWithFormat:@"read strings file %@, len: %d", + self->path, [rawData length]]; + } + + plistString = [[NSString alloc] initWithData:rawData + encoding:[self stringsFileEncoding]]; + [rawData release]; rawData = nil; + if (plistString == nil) { + [self logWithFormat:@"ERROR: could not decode strings file charset: %@", + self->path]; + return; + } + + if (debugOn) { + [self debugWithFormat:@" string len %d, class %@", + [plistString length], NSStringFromClass([plistString class])]; + } + + NS_DURING { + if ((plist = [plistString propertyListFromStringsFileFormat]) == nil) { + NSLog(@"%s: could not load strings file '%@'", + __PRETTY_FUNCTION__, + self->path); + } + self->data = [plist copy]; + self->lastRead = [[NSDate date] retain]; + } + NS_HANDLER + [[self reportParsingError:localException] raise]; + NS_ENDHANDLER; + + if (debugOn) + [self debugWithFormat:@" parsed entries: %d", [self->data count]]; + + [plistString release]; +} + +/* access */ + +- (NSString *)stringForKey:(NSString *)_key withDefaultValue:(NSString *)_def { + NSString *value; + + [self checkState]; + value = [self->data objectForKey:_key]; + return value ? value : _def; +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} + +@end /* WEStringTable */ diff --git a/sope-appserver/WEExtensions/WEStringTableManager.h b/sope-appserver/WEExtensions/WEStringTableManager.h new file mode 100644 index 00000000..ebb212a8 --- /dev/null +++ b/sope-appserver/WEExtensions/WEStringTableManager.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2004-2005 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo 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, or (at your option) any + later version. + + OGo 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 OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __WEExtensions_WEStringTableManager_H__ +#define __WEExtensions_WEStringTableManager_H__ + +#import + +/* + WEStringTableManager + + This class manages the lookup and processing of string tables (that is, the + ".strings" files containing label translations) + + The class is used by the WEResourceManager. +*/ + +@class NSString, NSArray, NSMutableDictionary; +@class WOComponent; + +@interface WEStringTableManager : NSObject +{ + NSMutableDictionary *nameToTable; + NSMutableDictionary *compLabelCache; +} + ++ (NSArray *)availableTranslations; + +/* string tables */ + +- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component; + +- (NSString *)stringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_languages; + +@end + + +#include + +@interface WOApplication(WEStringTableManager) + +- (NSString *)shareTranslationsDirectoryName; +- (NSString *)gsTranslationsDirectoryName; + +@end + +#endif /* __WEExtensions_WEStringTableManager_H__ */ diff --git a/sope-appserver/WEExtensions/WEStringTableManager.m b/sope-appserver/WEExtensions/WEStringTableManager.m new file mode 100644 index 00000000..83eb2ff1 --- /dev/null +++ b/sope-appserver/WEExtensions/WEStringTableManager.m @@ -0,0 +1,493 @@ +/* + Copyright (C) 2004-2005 SKYRIX Software AG + + This file is part of SOPE. + + SOPE 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, or (at your option) any + later version. + + SOPE 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 SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#include "WEStringTableManager.h" +#include "WEStringTable.h" +#include "WEResourceManager.h" +#include "common.h" + +@implementation WEStringTableManager + +static NSNull *null = nil; +static BOOL debugOn = NO; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + if (null == nil) null = [[NSNull null] retain]; + + if ((debugOn = [ud boolForKey:@"WEStringTableManagerDebugEnabled"])) + NSLog(@"Note: WEStringTableManager debugging is enabled."); +} + +- (id)init { + if ((self = [super init])) { + if ([WOApplication isCachingEnabled]) { + // TODO: find proper capacities + self->nameToTable = + [[NSMutableDictionary alloc] initWithCapacity:16]; + self->compLabelCache = + [[NSMutableDictionary alloc] initWithCapacity:512]; + } + else + [self logWithFormat:@"Note: label caching is disabled (slow!)."]; + } + return self; +} + +- (void)dealloc { + [self->compLabelCache release]; + [self->nameToTable release]; + [super dealloc]; +} + +/* accessors */ + +- (NGBundleManager *)bundleManager { + static NGBundleManager *bm = nil; // THREAD + if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain]; + return bm; +} + +/* resource pathes */ + ++ (NSString *)findResourceDirectoryNamed:(NSString *)_name + fhsName:(NSString *)_fhs +{ + static NSDictionary *env = nil; + NSFileManager *fm; + NSString *path; + id tmp; + + if (env == nil) env = [[[NSProcessInfo processInfo] environment] retain]; + fm = [NSFileManager defaultManager]; + + /* look in GNUstep pathes */ + + tmp = [[WEResourceManager rootPathesInGNUstep] objectEnumerator]; + while ((path = [tmp nextObject]) != nil) { + path = [path stringByAppendingPathComponent:_name]; + + if ([fm fileExistsAtPath:path]) + return path; + } + + /* look in FHS pathes */ + + tmp = [[WEResourceManager rootPathesInFHS] objectEnumerator]; + while ((path = [tmp nextObject])) { + path = [path stringByAppendingPathComponent:_fhs]; + if ([fm fileExistsAtPath:path]) + return path; + } + return nil; +} + +/* labels */ + +- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{ + WOApplication *app; + NSArray *langs; + NSBundle *bundle; + id value = nil; + NGBundleManager *bm; + NSString *cname; + id cacheKey; + + if ([_key length] == 0) + return _key; + if (_component == nil) + return _key; + + if (null == nil) null = [[NSNull null] retain]; + + cname = [_component name]; + langs = [(WOSession *)[_component session] languages]; + + /* cache ?? */ + + { + /* cache cachekeys here ;-) */ + cacheKey = [NSArray arrayWithObjects:_key, cname, langs, nil]; + } + + if ((value = [self->compLabelCache objectForKey:cacheKey]) != nil) { + if (value == null) value = nil; + return value; + } + + /* lookup bundle */ + + app = [WOApplication application]; + + bm = [NGBundleManager defaultBundleManager]; + bundle = [bm bundleProvidingResource:cname ofType:@"WOComponents"]; + if (bundle == nil) + bundle = [NGBundle bundleForClass:[_component class]]; + + /* look in BundleName.strings */ + + value = [self stringForKey:_key + inTableNamed:[bundle bundleName] + withDefaultValue:nil + languages:langs]; + if (value) + goto done; + + /* look in App.strings */ + value = [self stringForKey:_key + inTableNamed:cname + withDefaultValue:nil + languages:langs]; + if (value) + goto done; + + /* no string found, use key as string */ + value = _key; + + done: +#if DEBUG + NSAssert(value, @"missing value .."); +#endif + + [self->compLabelCache setObject:value forKey:cacheKey]; +#if 0 + NSLog(@"%s: label cache size now: %i", __PRETTY_FUNCTION__, + [self->compLabelCache count]); +#endif + return value; +} + +- (NSString *)_cachedStringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_languages +{ + if (_tableName == nil) _tableName = @"default"; + + /* look into string cache */ + + if ([_languages count] > 0) { + NSString *tname; + NSRange r; + NSString *language; + id table; + + language = [_languages objectAtIndex:0]; + + r = [language rangeOfString:@"_"]; + if (r.length > 0) + language = [language substringToIndex:r.location]; + + tname = [language stringByAppendingString:@"+++"]; + tname = [tname stringByAppendingString:_tableName]; + + if ((table = [self->nameToTable objectForKey:tname])) { + if (debugOn) { + [self logWithFormat: + @"resolved label %@ table %@ in lang %@ (languages=%@)", + _key, _tableName, language, + [_languages componentsJoinedByString:@","]]; + } + return [table stringForKey:_key withDefaultValue:_default]; + } + } + else if ([_languages count] == 0) { + id table; + + NSLog(@"WARNING: called %s without languages array %@ !", + __PRETTY_FUNCTION__, _languages); + + if ((table = [self->nameToTable objectForKey:_tableName])) + return [table stringForKey:_key withDefaultValue:_default]; + } + + return nil; +} + +- (NSString *)_bundleStringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_languages +{ + NSBundle *bundle; + NSString *path; + NSEnumerator *e; + NSString *language; + + if (_tableName == nil) _tableName = @"default"; + + bundle = [[self bundleManager] + bundleProvidingResource:_tableName + ofType:@"strings"]; + if (bundle == nil) + return nil; + + e = [_languages objectEnumerator]; + while ((language = [e nextObject])) { + NSArray *ls; + NSRange r; + + r = [language rangeOfString:@"_"]; + if (r.length > 0) + language = [language substringToIndex:r.location]; + + ls = [NSArray arrayWithObject:language]; + + path = [bundle pathForResource:_tableName + ofType:@"strings" + languages:ls]; + if (path) { + NSString *tname; + id table; + + tname = [language stringByAppendingString:@"+++"]; + tname = [tname stringByAppendingString:_tableName]; + + table = [WEStringTable stringTableWithPath:path]; + [self->nameToTable setObject:table forKey:tname]; + + return [table stringForKey:_key withDefaultValue:_default]; + } + } + + path = [bundle pathForResource:_tableName + ofType:@"strings" + languages:nil]; + + if (path) { + WEStringTable *table; + + table = [WEStringTable stringTableWithPath:path]; + + [self->nameToTable setObject:table forKey:_tableName]; + + return [table stringForKey:_key withDefaultValue:_default]; + } + else if (debugOn) { + NSLog(@"WARNING: missing string table %@ in bundle %@", + _tableName, [bundle bundlePath]); + } + return _default; +} + ++ (NSString *)gsTranslationsSubpath { + NSString *p; + + p = [[WOApplication application] gsTranslationsDirectoryName]; + if ([p length] == 0) + return nil; + + p = [@"Resources/" stringByAppendingString:p]; + return p; +} ++ (NSString *)fhsTranslationsSubpath { + NSString *p; + + p = [[WOApplication application] shareTranslationsDirectoryName]; + if ([p length] == 0) + return nil; + + p = [@"share/" stringByAppendingString:p]; + return [p stringByAppendingString:@"/translations/"]; +} + ++ (NSArray *)availableTranslations { + static NSArray *ltranslations = nil; + NSMutableSet *translations; + NSEnumerator *e; + NSFileManager *fm; + NSString *path; + NSArray *translationPathes; + + if (ltranslations != nil) + return ltranslations; + + // TODO: use lists ..., or maybe NGResourceLocator + path = [self findResourceDirectoryNamed:[self gsTranslationsSubpath] + fhsName:[self fhsTranslationsSubpath]]; + if (path == nil) + return nil; + + translationPathes = [NSArray arrayWithObject:path]; + translations = [NSMutableSet setWithCapacity:16]; + fm = [NSFileManager defaultManager]; + + e = [translationPathes objectEnumerator]; + while ((path = [e nextObject]) != nil) { + NSEnumerator *dl; + NSString *l; + + dl = [[fm directoryContentsAtPath:path] objectEnumerator]; + while ((l = [dl nextObject]) != nil) { + if (![l hasSuffix:@".lproj"]) + continue; + if ([l hasPrefix:@"."]) + continue; + + l = [l stringByDeletingPathExtension]; + [translations addObject:l]; + } + } + + ltranslations = [[translations allObjects] copy]; + if ([ltranslations count] > 0) { + NSLog(@"Note: located translations: %@", + [ltranslations componentsJoinedByString:@", "]); + } + else + NSLog(@"Note: located no additional translations."); + return ltranslations; +} + +- (NSString *)_resourceStringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_languages +{ + NSString *rpath, *path; + NSEnumerator *e; + NSString *language; + NSFileManager *fm; + + if (_tableName == nil) _tableName = @"default"; + + fm = [NSFileManager defaultManager]; + // TODO: should be NGResourceLocator method? + rpath = [[self class] findResourceDirectoryNamed: + [[self class] gsTranslationsSubpath] + fhsName: + [[self class] fhsTranslationsSubpath]]; + if (rpath == nil) { + [self logWithFormat:@"missing translations directory ..."]; + return nil; + } + + /* look into language projects .. */ + + e = [_languages objectEnumerator]; + while ((language = [e nextObject]) != nil) { + WEStringTable *table; + NSArray *ls; + NSRange r; + NSString *tname; + + r = [language rangeOfString:@"_"]; + if (r.length > 0) + language = [language substringToIndex:r.location]; + + ls = [NSArray arrayWithObject:language]; + + path = [_tableName stringByAppendingPathExtension:@"strings"]; + path = [[language stringByAppendingPathExtension:@"lproj"] + stringByAppendingPathComponent:path]; + path = [rpath stringByAppendingPathComponent:path]; + + if (![fm fileExistsAtPath:path]) + continue; + + tname = [language stringByAppendingString:@"+++"]; + tname = [tname stringByAppendingString:_tableName]; + + table = [WEStringTable stringTableWithPath:path]; + [self->nameToTable setObject:table forKey:tname]; + + return [table stringForKey:_key withDefaultValue:_default]; + } + + /* look into resource dir */ + + path = [_tableName stringByAppendingPathExtension:@"strings"]; + path = [rpath stringByAppendingPathComponent:path]; + + if ([fm fileExistsAtPath:path]) { + WEStringTable *table; + + table = [WEStringTable stringTableWithPath:path]; + if (self->nameToTable == nil) + self->nameToTable = [[NSMutableDictionary alloc] initWithCapacity:16]; + + [self->nameToTable setObject:table forKey:_tableName]; + + return [table stringForKey:_key withDefaultValue:_default]; + } + + if (debugOn) + NSLog(@"WARNING: missing string table %@ in %@", _tableName, path); + + return _default; +} + +- (NSString *)stringForKey:(NSString *)_key + inTableNamed:(NSString *)_tableName + withDefaultValue:(NSString *)_default + languages:(NSArray *)_languages +{ + NSString *s; + + if (_tableName == nil) _tableName = @"default"; + + /* look into string cache */ + + s = [self _cachedStringForKey:_key + inTableNamed:_tableName + withDefaultValue:_default + languages:_languages]; + if (s != nil) return s; + + if (self->nameToTable == nil) + self->nameToTable = [[NSMutableDictionary alloc] initWithCapacity:16]; + + /* search for string */ + + s = [self _resourceStringForKey:_key + inTableNamed:_tableName + withDefaultValue:nil + languages:_languages]; + if (s != nil) return s; + + s = [self _bundleStringForKey:_key + inTableNamed:_tableName + withDefaultValue:nil + languages:_languages]; + if (s != nil) return s; + + return nil; +} + +/* debugging */ + +- (BOOL)isDebuggingEnabled { + return debugOn; +} + +@end /* WEStringTableManager */ + +@implementation WOApplication(WEStringTableManager) + +- (NSString *)shareTranslationsDirectoryName { + return [[self name] lowercaseString]; +} +- (NSString *)gsTranslationsDirectoryName { + return [self name]; +} + +@end /* WOApplication(WEStringTableManager) */