]> err.no Git - sope/commitdiff
added the WEResourceManager
authorhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 17 Feb 2005 15:50:52 +0000 (15:50 +0000)
committerhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Thu, 17 Feb 2005 15:50:52 +0000 (15:50 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@567 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

sope-appserver/WEExtensions/ChangeLog
sope-appserver/WEExtensions/GNUmakefile
sope-appserver/WEExtensions/Version
sope-appserver/WEExtensions/WEResourceKey.h [new file with mode: 0644]
sope-appserver/WEExtensions/WEResourceKey.m [new file with mode: 0644]
sope-appserver/WEExtensions/WEResourceManager.h [new file with mode: 0644]
sope-appserver/WEExtensions/WEResourceManager.m [new file with mode: 0644]
sope-appserver/WEExtensions/WEStringTable.h [new file with mode: 0644]
sope-appserver/WEExtensions/WEStringTable.m [new file with mode: 0644]
sope-appserver/WEExtensions/WEStringTableManager.h [new file with mode: 0644]
sope-appserver/WEExtensions/WEStringTableManager.m [new file with mode: 0644]

index 8f76deb2b30475f836c14efd4b879f9e0fa97dc3..b71582b72ac0bc8bed2c2ffe2a82fa1f0cf59a71 100644 (file)
@@ -1,3 +1,9 @@
+2005-02-17  Helge Hess  <helge.hess@skyrix.com>
+
+       * 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  <znek@mulle-kybernetik.com>
 
        * common.h, JSClipboard.m: changed to use WOContext's new
index 08ccd4a139afc2e8924b0605d6c9ab6eaf7ab5af..a992b17edb678538c7cb0937f069b3c2613ed593 100644 (file)
@@ -52,6 +52,11 @@ libWEExtensions_OBJC_FILES =   \
        WEEpozEditor.m                  \
        WEQualifierConditional.m        \
        WERedirect.m                    \
+       \
+       WEResourceManager.m             \
+       WEResourceKey.m                 \
+       WEStringTable.m                 \
+       WEStringTableManager.m          \
 
 libWEExtensions_SUBPROJECTS += \
        WETableView
index 4651c3e2b66340d1d2fa11e6ddc164530ea8605d..f6b45b2434672ef874ee1e9c93f06970918e2ec0 100644 (file)
@@ -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 (file)
index 0000000..a36913e
--- /dev/null
@@ -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 <Foundation/NSObject.h>
+
+/*
+  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 (file)
index 0000000..3cb50c5
--- /dev/null
@@ -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 (file)
index 0000000..ff9a074
--- /dev/null
@@ -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 <NGObjWeb/WOResourceManager.h>
+
+/*
+  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 <NGObjWeb/WOApplication.h>
+
+@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 (file)
index 0000000..68fbc41
--- /dev/null
@@ -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 (file)
index 0000000..bf7b233
--- /dev/null
@@ -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 <Foundation/NSObject.h>
+
+/*
+  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 (file)
index 0000000..3886b7a
--- /dev/null
@@ -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 (file)
index 0000000..ebb212a
--- /dev/null
@@ -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 <Foundation/NSObject.h>
+
+/*
+  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 <NGObjWeb/WOApplication.h>
+
+@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 (file)
index 0000000..83eb2ff
--- /dev/null
@@ -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) */