/* 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 #include #include #include #include #include #include #include "common.h" @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"; } /* lookup resources */ - (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 NSBundle *bundle; NSString *path; if (debugOn) [self debugWithFormat:@"lookup resource: '%@'", _name]; /* determine product bundle or explicitly requested framework/bundle */ if ([_frameworkName length] > 0) { if ([_frameworkName hasPrefix:@"/"]) { bundle = [bm bundleWithPath:_frameworkName]; } else { bundle = [bm bundleWithName: [_frameworkName stringByDeletingPathExtension] type:[_frameworkName pathExtension]]; } if (bundle == nil) { [self debugWithFormat:@"WARNING: missing bundle for framework: '%@'", _frameworkName]; goto fallback; } } else { if ((bundle = [self->product bundle]) == nil) { [self debugWithFormat:@"WARNING: missing bundle for product: %@", self->product]; goto fallback; } } if (debugOn) [self debugWithFormat:@" bundle: %@", bundle]; /* lookup resource in bundle */ path = [bundle pathForResource:[_name stringByDeletingPathExtension] ofType:[_name pathExtension] inDirectory:nil forLocalizations:_languages]; if (path != nil) { if (debugOn) [self debugWithFormat:@" => found: %@", path]; return path; } if (debugOn) [self debugWithFormat:@" resource not found in bundle ..."]; /* fall back to WOResourceManager lookup */ fallback: return [super 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 [[[WOApplication application] resourceManager] webServerResourcesPath]; } - (NSString *)urlForResourceNamed:(NSString *)_name inFramework:(NSString *)_frameworkName languages:(NSArray *)_languages request:(WORequest *)_request { NSString *resource = nil, *tmp; NSString *path = nil, *sbase; unsigned len; NSString *url; if (debugOn) [self debugWithFormat:@"lookup url: '%@'", _name]; if (_languages == nil) _languages = [_request browserLanguages]; resource = [self pathForResourceNamed:_name inFramework:_frameworkName languages:_languages]; #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY if ([resource rangeOfString:@"/Contents/"].length > 0) { resource = [resource stringByReplacingString:@"/Contents" withString:@""]; } #endif #if 0 tmp = [resource stringByStandardizingPath]; if (tmp) resource = tmp; #endif if (resource == nil) { if (debugOn) { [self debugWithFormat:@" => not found '%@' (fw=%@,langs=%@)", _name, _frameworkName, [_languages componentsJoinedByString:@","]]; } return nil; } 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]; if (debugOn) [self debugWithFormat:@" => '%@'", url]; return url; } - (WOElement *)templateWithName:(NSString *)_name languages:(NSArray *)_languages { [self logWithFormat:@"lookup template with name '%@' (languages=%@)", _name, [_languages componentsJoinedByString:@","]]; return [super templateWithName:_name languages:_languages]; } /* 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 hasSession] ? [(WOSession *)[_ctx session] languages] : [[(id )_ctx request] browserLanguages]; p = [self pathForResourceNamed:_key inFramework:[b bundlePath] languages:languages]; if (p == nil) { [self logWithFormat:@"ERROR: did not find product resource: %@", _key]; return nil; } /* load data */ if ((data = [NSData dataWithContentsOfMappedFile:p]) == nil) { [self logWithFormat:@"ERROR: 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 logWithFormat: @"WARNING: 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 */