/* Copyright (C) 2002-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 "SoProductResourceManager.h" #include "SoProduct.h" #include "SoObject.h" #include "SoClassSecurityInfo.h" #include "SoProductRegistry.h" #include #include #include #include #include #include #include "common.h" /* How is resource lookup supposed to work? First, we need to determine whether the resource being asked for is a resource contained in the product bundle. If thats the case, this resource manager will take of it. If not, there are two options: a) the resource is from a different product, so we direct lookup to that b) the resource is a "global" resource, so we ask the WOApplication manager */ @interface WOResourceManager(UsedPrivates) - (NSString *)webServerResourcesPath; - (NSString *)resourcesPath; @end @implementation SoProductResourceManager static NGBundleManager *bm = nil; static BOOL debugOn = NO; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; bm = [[NGBundleManager defaultBundleManager] retain]; debugOn = [ud boolForKey:@"SoProductResourceManagerDebugEnabled"]; } - (id)initWithProduct:(SoProduct *)_product { if ((self = [super initWithPath:[[_product bundle] bundlePath]])) { self->product = _product; } return self; } /* containment */ - (void)detachFromContainer { self->product = nil; } - (id)container { return self->product; } - (NSString *)nameInContainer { return @"Resources"; } - (WOResourceManager *)fallbackResourceManager { WOResourceManager *rm; rm = [[WOApplication application] resourceManager]; return (rm == self) ? nil : rm; /* avoid recursion */ } /* lookup resources */ - (NSBundle *)bundleForFrameworkName:(NSString *)_fwName { NSBundle *bundle; if ([_fwName length] == 0) { if ((bundle = [self->product bundle]) == nil) [self warnWithFormat:@"missing bundle for product: %@", self->product]; return bundle; } if ([_fwName hasPrefix:@"/"]) { bundle = [bm bundleWithPath:_fwName]; } else { bundle = [bm bundleWithName:[_fwName stringByDeletingPathExtension] type:[_fwName pathExtension]]; } if (bundle == nil) [self warnWithFormat:@"missing bundle for framework: '%@'", _fwName]; return bundle; } - (WOResourceManager *)resourceManagerForResourceNamed:(NSString *)_name inFramework:(NSString *)_frameworkName { SoProduct *bproduct; NSBundle *bundle; /* determine product bundle or explicitly requested framework/bundle */ if ([_frameworkName length] == 0) return self; if ((bundle = [self bundleForFrameworkName:_frameworkName]) == nil) return nil; bproduct = [[SoProductRegistry sharedProductRegistry] productForBundle:bundle]; if (debugOn) { [self debugWithFormat: @" fwname: '%@'\n bundle: '%@'\n product: '%@'", _frameworkName, bundle, bproduct]; } return (bproduct == self->product) ? self : (id)[bproduct resourceManager]; } - (NSString *)primaryLookupPathForResourceNamed:(NSString *)_name languages:(NSArray *)_l { NSString *path; path = [[self->product bundle] pathForResource:[_name stringByDeletingPathExtension] ofType:[_name pathExtension] inDirectory:nil languages:_l]; if (debugOn && path == nil) { [self debugWithFormat:@" resource %@/%@ not found in bundle: %@", _name, [_l componentsJoinedByString:@","], [self->product bundle]]; } return path; } - (NSString *)pathForResourceNamed:(NSString *)_name inFramework:(NSString *)_frameworkName languages:(NSArray *)_languages { // TODO: should we do acquisition? (hm, don't think so!, done in lookup) // but maybe we should not fall back to WOApplication resources WOResourceManager *rm; NSString *path; if (debugOn) [self debugWithFormat:@"lookup resource: '%@'", _name]; /* determine resource manager to be actually used */ rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName]; /* lookup resource */ if (rm == self) { path = [self primaryLookupPathForResourceNamed:_name languages:_languages]; } else if (rm != nil) { /* delegate lookup to resource manager of other products */ path = [rm pathForResourceNamed:_name inFramework:_frameworkName languages:_languages]; if (debugOn && path == nil) [self debugWithFormat:@" resource %@ not found in rm: %@", _name, rm]; } else path = nil; if (path != nil) { if (debugOn) [self debugWithFormat:@" => found: %@", path]; return path; } /* fall back to global resource manager */ return [[self fallbackResourceManager] pathForResourceNamed:_name inFramework:_frameworkName languages:_languages]; } /* generate URL for resources (eg filename binding in WOImage) */ - (NSString *)webServerResourcesPath { /* to avoid warning that WebServerResources path does not exist ... */ return [[self fallbackResourceManager] webServerResourcesPath]; } - (NSString *)urlForProductRelativeResourcePath:(NSString *)resource { NSString *tmp; NSString *path = nil, *sbase; unsigned len; NSString *url; sbase = self->base; tmp = [sbase commonPrefixWithString:resource options:0]; len = [tmp length]; path = [sbase substringFromIndex:len]; tmp = [resource substringFromIndex:len]; if (([path length] > 0) && ![tmp hasPrefix:@"/"] && ![tmp hasPrefix:@"\\"]) path = [path stringByAppendingString:@"/"]; path = [path stringByAppendingString:tmp]; #ifdef __WIN32__ { NSArray *cs; cs = [path componentsSeparatedByString:@"\\"]; path = [cs componentsJoinedByString:@"/"]; } #endif if (path == nil) return nil; if ([path hasPrefix:@"/Resources/"]) path = [path substringFromIndex:11]; else if ([path hasPrefix:@"Resources/"]) path = [path substringFromIndex:10]; /* Note: cannot use -stringByAppendingPathComponent: on OSX! */ url = [self baseURLInContext:[[WOApplication application] context]]; if (debugOn) [self debugWithFormat:@" base: '%@'", url]; if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"]; url = [url stringByAppendingString:path]; return url; } - (NSString *)fixupResourcePath:(NSString *)resource { #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY if ([resource rangeOfString:@"/Contents/"].length > 0) { resource = [resource stringByReplacingString:@"/Contents" withString:@""]; } #endif #if 0 if ((tmp = [resource stringByStandardizingPath]) != nil) resource = tmp; #endif return resource; } - (NSString *)urlForResourceNamed:(NSString *)_name inFramework:(NSString *)_frameworkName languages:(NSArray *)_languages request:(WORequest *)_request { WOResourceManager *rm; NSString *url; if (debugOn) [self debugWithFormat:@"lookup url: '%@'", _name]; if (_languages == nil) _languages = [_request browserLanguages]; /* determine resource manager to be actually used */ rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName]; /* lookup resource */ url = nil; if (rm == self) { /* handle it directly */ NSString *resource; resource = [self primaryLookupPathForResourceNamed:_name languages:_languages]; if (debugOn) [self debugWithFormat:@" resource: %@", resource]; resource = [self fixupResourcePath:resource]; if (debugOn) [self debugWithFormat:@" resource to URL: %@", resource]; if (resource != nil) { url = [self urlForProductRelativeResourcePath:resource]; } else if (debugOn) { [self debugWithFormat:@" => not found '%@' (fw=%@,langs=%@)", _name, _frameworkName, [_languages componentsJoinedByString:@","]]; } } else if (rm != nil) { /* delegate to other product */ if (debugOn) [self debugWithFormat:@" lookup with rm: %@", rm]; url = [rm urlForResourceNamed:_name inFramework:_frameworkName languages:_languages request:_request]; } if (url == nil) { /* fallback */ rm = [self fallbackResourceManager]; if (debugOn) [self debugWithFormat:@" fallback: %@", rm]; url = [rm urlForResourceNamed:_name inFramework:_frameworkName languages:_languages request:_request]; } if (debugOn) [self debugWithFormat:@" => '%@'", url]; return url; } - (NSString *)pathToComponentNamed:(NSString *)_name inFramework:(NSString *)_fwname languages:(NSArray *)_langs { NSString *p; p = [super pathToComponentNamed:_name inFramework:_fwname languages:_langs]; if (![p isNotNull] || [p length] == 0 ) { [self logWithFormat:@"LOOKUP FAILED: %@", _name]; p = [[self fallbackResourceManager] pathToComponentNamed:_name inFramework:_fwname languages:_langs]; [self logWithFormat:@" PARENT SAID: %@", p]; } return p; } - (WOElement *)templateWithName:(NSString *)_name languages:(NSArray *)_languages { WOResourceManager *arm; WOElement *e; if (debugOn) { [self logWithFormat:@"lookup template with name '%@' (languages=%@)", _name, [_languages componentsJoinedByString:@","]]; } if ((e = [super templateWithName:_name languages:_languages]) != nil) { if (debugOn) [self logWithFormat:@" found: %@", e]; return e; } arm = [self fallbackResourceManager]; if (arm == self) return nil; if (debugOn) [self logWithFormat:@" lookup in parent RM: %@", arm]; if ((e = [arm templateWithName:_name languages:_languages]) != nil) { if (debugOn) [self logWithFormat:@" found: %@", e]; return e; } if (debugOn) [self logWithFormat:@"did not find template %@", _name]; return nil; } /* resource manager as a SoObject */ - (NSString *)mimeTypeForExtension:(NSString *)_ext { // TODO: HACK, move to some object NSString *ctype = nil; if ([_ext isEqualToString:@"css"]) ctype = @"text/css"; else if ([_ext isEqualToString:@"gif"]) ctype = @"image/gif"; else if ([_ext isEqualToString:@"jpg"]) ctype = @"image/jpeg"; else if ([_ext isEqualToString:@"png"]) ctype = @"image/png"; else if ([_ext isEqualToString:@"html"]) ctype = @"text/html"; else if ([_ext isEqualToString:@"xml"]) ctype = @"text/xml"; else if ([_ext isEqualToString:@"txt"]) ctype = @"text/plain"; else if ([_ext isEqualToString:@"js"]) ctype = @"application/x-javascript"; else if ([_ext isEqualToString:@"xhtml"]) ctype = @"application/xhtml+xml"; return ctype; } - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag { WOResponse *r; NSBundle *b; NSString *p, *pe, *ctype; NSData *data; NSArray *languages = nil; /* TODO: add support for languages (eg English.lproj/ok.gif) ! */ /* check whether the resource is made public */ if (![self->product isPublicResource:_key]) { [self debugWithFormat:@"key '%@' is not declared a public resource.",_key]; return nil; } if ((b = [self->product bundle]) == nil) { [self debugWithFormat:@"product has no bundle for lookup of %@", _key]; return nil; } pe = [_key pathExtension]; /* ask resource-manager (self) for path */ languages = [_ctx resourceLookupLanguages]; p = [self pathForResourceNamed:_key inFramework:[b bundlePath] languages:languages]; if (p == nil) { [self errorWithFormat:@"did not find product resource: %@", _key]; return nil; } /* load data */ if ((data = [NSData dataWithContentsOfMappedFile:p]) == nil) { [self errorWithFormat:@"failed to load product resource: %@", _key]; return nil; } /* and deliver as a complete response */ r = [(id)_ctx response]; [r setStatus:200 /* OK */]; [r setContent:data]; if ((ctype = [self mimeTypeForExtension:pe]) == nil) { [self warnWithFormat:@"did not recognize extension '%@', " @"delivering as application/octet-stream.", pe]; ctype = @"application/octet-stream"; } { NSDate *expDate = nil; NSString *str = nil; expDate = [[NSDate alloc] initWithTimeInterval:(60 * 60 * 1) /* 1 hour */ sinceDate:[NSDate date]]; str = [expDate descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S GMT" timeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"] locale:nil]; [r setHeader:str forKey:@"expires"]; [expDate release]; } [r setHeader:ctype forKey:@"content-type"]; return r; } /* debugging */ - (BOOL)isDebuggingEnabled { return debugOn; } - (NSString *)loggingPrefix { return [NSString stringWithFormat:@"[RM:%@]", [self->product productName]]; } /* description */ - (NSString *)description { NSMutableString *str; str = [NSMutableString stringWithCapacity:64]; [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self]; [str appendFormat:@" product='%@'", [self->product productName]]; [str appendString:@">"]; return str; } @end /* SoProductResourceManager */