From 0756141222e56a91a53b3823f590ceb8bb815a88 Mon Sep 17 00:00:00 2001 From: helge Date: Mon, 28 Jun 2004 15:14:55 +0000 Subject: [PATCH] git-svn-id: http://svn.opengroupware.org/SOGo/trunk@67 d1b88da0-ebda-0310-925b-ed51d893ca5b --- OGoContentStore/OCSChannelManager.m | 13 ++ OGoContentStore/OCSFolderManager.h | 17 +- OGoContentStore/OCSFolderManager.m | 302 +++++++++++++++++++++++++++- OGoContentStore/README | 5 +- OGoContentStore/ocs_ls.m | 41 ++++ OGoContentStore/sql/folderinfo.psql | 16 +- 6 files changed, 384 insertions(+), 10 deletions(-) diff --git a/OGoContentStore/OCSChannelManager.m b/OGoContentStore/OCSChannelManager.m index 286d2bd2..d7a09d09 100644 --- a/OGoContentStore/OCSChannelManager.m +++ b/OGoContentStore/OCSChannelManager.m @@ -27,10 +27,23 @@ #include #include "common.h" +/* + TODO: + - implemented pooling + - auto-close channels which are very old?! + (eg missing release due to an exception) +*/ + @implementation OCSChannelManager static BOOL debugOn = YES; ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"OCSChannelManagerDebugEnabled"]; +} + + (NSString *)adaptorNameForURLScheme:(NSString *)_scheme { // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL72 return @"PostgreSQL72"; diff --git a/OGoContentStore/OCSFolderManager.h b/OGoContentStore/OCSFolderManager.h index 23a826bd..af9db959 100644 --- a/OGoContentStore/OCSFolderManager.h +++ b/OGoContentStore/OCSFolderManager.h @@ -32,7 +32,7 @@ model and manage the tables required for a folder. */ -@class NSURL; +@class NSString, NSArray, NSURL; @class OCSChannelManager; @interface OCSFolderManager : NSObject @@ -52,6 +52,21 @@ - (BOOL)canConnect; +/* handling folder names */ + +- (NSString *)internalNameFromPath:(NSString *)_path; +- (NSArray *)internalNamesFromPath:(NSString *)_path; +- (NSString *)pathFromInternalName:(NSString *)_name; + +/* operations */ + +- (BOOL)folderExistsAtPath:(NSString *)_path; +- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag; + +/* cache management */ + +- (void)reset; + @end #endif /* __OGoContentStore_OCSFolderManager_H__ */ diff --git a/OGoContentStore/OCSFolderManager.m b/OGoContentStore/OCSFolderManager.m index 15ba7ba9..a0c9fb80 100644 --- a/OGoContentStore/OCSFolderManager.m +++ b/OGoContentStore/OCSFolderManager.m @@ -26,15 +26,32 @@ #include "common.h" #include +/* + Required database schema: + + + path + path1, path2, path3... [quickPathCount times] + folderName + + TODO: + - add a local cache? +*/ + @implementation OCSFolderManager static OCSFolderManager *fm = nil; -static BOOL debugOn = YES; +static BOOL debugOn = NO; +static BOOL debugSQLGen = NO; +static int quickPathCount = 4; +static NSArray *emptyArray = nil; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - debugOn = [ud boolForKey:@"OCSFolderManagerDebugEnabled"]; + debugOn = [ud boolForKey:@"OCSFolderManagerDebugEnabled"]; + debugSQLGen = [ud boolForKey:@"OCSFolderManagerSQLDebugEnabled"]; + emptyArray = [[NSArray alloc] init]; } + (id)defaultFolderManager { @@ -89,6 +106,10 @@ static BOOL debugOn = YES; return self->folderInfoLocation; } +- (NSString *)folderInfoTableName { + return [[self folderInfoLocation] ocsTableName]; +} + /* checking connection */ - (EOAdaptorChannel *)acquireOpenChannel { @@ -118,9 +139,9 @@ static BOOL debugOn = YES; /* check whether table exists */ - sql = [@"SELECT COUNT(*) FROM " stringByAppendingString: - [[self folderInfoLocation] ocsTableName]]; - sql = [sql stringByAppendingString:@" WHERE 1=2"]; + sql = @"SELECT COUNT(*) FROM "; + sql = [sql stringByAppendingString:[self folderInfoTableName]]; + sql = [sql stringByAppendingString:@" WHERE 1 = 2"]; ex = [[[channel evaluateExpressionX:sql] retain] autorelease]; [channel cancelFetch]; @@ -131,6 +152,277 @@ static BOOL debugOn = YES; return ex != nil ? NO : YES; } +- (NSArray *)performSQL:(NSString *)_sql { + EOAdaptorChannel *channel; + NSException *ex; + NSMutableArray *rows; + NSDictionary *row; + NSArray *attrs; + + /* acquire channel */ + + if ((channel = [self acquireOpenChannel]) == nil) { + if (debugOn) [self debugWithFormat:@"could not acquire channel!"]; + return nil; + } + if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel]; + + /* run SQL */ + + if ((ex = [channel evaluateExpressionX:_sql])) { + [self releaseChannel:channel]; + return nil; + } + + /* fetch results */ + + attrs = [channel describeResults]; + rows = [NSMutableArray arrayWithCapacity:16]; + while ((row = [channel fetchAttributes:attrs withZone:NULL])) + [rows addObject:row]; + + [self releaseChannel:channel]; + return rows; +} + +/* path SQL */ + +- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names + exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs +{ + /* generates a WHERE qualifier for matching the "quick" entries */ + NSMutableString *sql; + unsigned i, count; + + if ((count = [_names count]) == 0) { + [self debugWithFormat:@"WARNING(%s): passed in empty name array!", + __PRETTY_FUNCTION__]; + return @"1 = 2"; + } + + sql = [NSMutableString stringWithCapacity:(count * 8)]; + for (i = 0; i < quickPathCount; i++) { + NSString *pathColumn; + unsigned char buf[32]; + + sprintf(buf, "\"path%i\"", (i + 1)); + pathColumn = [[NSString alloc] initWithCString:buf]; + + /* Note: the AND addition must be inside the if's for non-exact stuff */ + + if (i < count) { + /* exact match, regular column */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + [sql appendFormat:@" = '%@'", [_names objectAtIndex:i]]; + } + else if (_beExact) { + /* exact match, ensure that all additional quick-cols are NULL */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + [sql appendString:@" IS NULL"]; + //[self logWithFormat:@"BE EXACT, NULL columns"]; + } + else if (_directSubs) { + /* fetch immediate subfolders */ + if ([sql length] > 0) [sql appendString:@" AND "]; + [sql appendString:pathColumn]; + if (i == count) { + /* if it is a direct subfolder, the next path cannot be empty */ + [sql appendString:@" IS NOT NULL"]; + //[self logWithFormat:@"DIRECT SUBS, first level"]; + } + else { + /* but for 'direct' subfolders, all following things must be empty */ + [sql appendString:@" IS NULL"]; + //[self logWithFormat:@"DIRECT SUBS, lower level"]; + } + } + + [pathColumn release]; + } + + if (_beExact && (count > quickPathCount)) { + [sql appendString:@" AND \"folderName\" = '"]; + [sql appendString:[_names lastObject]]; + [sql appendString:@"'"]; + } + + return sql; +} + +- (NSString *)generateSQLPathFetchForInternalNames:(NSArray *)_names + exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs +{ + /* fetches the 'path' subset for a given quick-names */ + NSMutableString *sql; + NSString *ws; + + ws = [self generateSQLWhereForInternalNames:_names + exactMatch:_beExact orDirectSubfolderMatch:_directSubs]; + if ([ws length] == 0) + return nil; + + sql = [NSMutableString stringWithCapacity:256]; + [sql appendString:@"SELECT \"path\" FROM "]; + [sql appendString:[self folderInfoTableName]]; + [sql appendString:@" WHERE "]; + [sql appendString:ws]; + if (debugSQLGen) [self logWithFormat:@"PathFetch-SQL: %@", sql]; + return sql; +} + +/* handling folder names */ + +- (BOOL)_isStandardizedPath:(NSString *)_path { + if (![_path isAbsolutePath]) return NO; + if ([_path rangeOfString:@".."].length > 0) return NO; + if ([_path rangeOfString:@"~"].length > 0) return NO; + if ([_path rangeOfString:@"//"].length > 0) return NO; + return YES; +} + +- (NSString *)internalNameFromPath:(NSString *)_path { + // TODO: ensure proper path and SQL escaping! + + if (![self _isStandardizedPath:_path]) { + [self debugWithFormat:@"%s: not a standardized path: '%@'", + __PRETTY_FUNCTION__, _path]; + return nil; + } + + if ([_path hasSuffix:@"/"] && [_path length] > 1) + _path = [_path substringToIndex:([_path length] - 1)]; + + return _path; +} +- (NSArray *)internalNamesFromPath:(NSString *)_path { + NSString *fname; + NSArray *fnames; + + if ((fname = [self internalNameFromPath:_path]) == nil) + return nil; + + if ([fname hasPrefix:@"/"]) + fname = [fname substringFromIndex:1]; + + fnames = [fname componentsSeparatedByString:@"/"]; + if ([fnames count] == 0) + return nil; + + return fnames; +} +- (NSString *)pathFromInternalName:(NSString *)_name { + /* for incomplete pathes, like '/Users/helge/' */ + return _name; +} +- (NSString *)pathPartFromInternalName:(NSString *)_name { + /* for incomplete pathes, like 'Users/' */ + return _name; +} + +- (BOOL)folderExistsAtPath:(NSString *)_path { + NSString *fname; + NSArray *fnames, *records; + NSString *sql; + unsigned count; + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return NO; + } + + sql = [self generateSQLPathFetchForInternalNames:fnames + exactMatch:YES orDirectSubfolderMatch:NO]; + if ([sql length] == 0) { + [self debugWithFormat:@"got no SQL for names: %@", fnames]; + return NO; + } + + if ((records = [self performSQL:sql]) == nil) { + [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", + __PRETTY_FUNCTION__, sql]; + return NO; + } + + if ((count = [records count]) == 0) + return NO; + + fname = [self internalNameFromPath:_path]; + if (count == 1) { + NSString *sname; + + sname = [[records objectAtIndex:0] objectForKey:@"path"]; + return [fname isEqualToString:sname]; + } + + [self logWithFormat:@"records: %@", records]; + + return NO; +} + +- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_recursive{ + NSMutableArray *result; + NSString *fname; + NSArray *fnames, *records; + NSString *sql; + unsigned i, count; + + if ((fnames = [self internalNamesFromPath:_path]) == nil) { + [self debugWithFormat:@"got no internal names for path: '%@'", _path]; + return nil; + } + + sql = [self generateSQLPathFetchForInternalNames:fnames + exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)]; + if ([sql length] == 0) { + [self debugWithFormat:@"got no SQL for names: %@", fnames]; + return nil; + } + + if ((records = [self performSQL:sql]) == nil) { + [self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'", + __PRETTY_FUNCTION__, sql]; + return nil; + } + + if ((count = [records count]) == 0) + return emptyArray; + + result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)]; + + fname = [self internalNameFromPath:_path]; + fname = [fname stringByAppendingString:@"/"]; /* add slash */ + for (i = 0; i < count; i++) { + NSString *sname, *spath; + + sname = [[records objectAtIndex:0] objectForKey:@"path"]; + if (![sname hasPrefix:fname]) /* does not match at all ... */ + continue; + + /* strip prefix and following slash */ + sname = [sname substringFromIndex:[fname length]]; + spath = [self pathPartFromInternalName:sname]; + + if (_recursive) { + if ([spath length] > 0) [result addObject:spath]; + } + else { + /* direct children only, so exclude everything with a slash */ + if ([sname rangeOfString:@"/"].length == 0 && [spath length] > 0) + [result addObject:spath]; + } + } + + return result; +} + +/* cache management */ + +- (void)reset { + /* does nothing in the moment, but we need a way to signal refreshes */ +} + /* debugging */ - (BOOL)isDebuggingEnabled { diff --git a/OGoContentStore/README b/OGoContentStore/README index f04e2f61..58588bd3 100644 --- a/OGoContentStore/README +++ b/OGoContentStore/README @@ -44,7 +44,10 @@ Defaults OCSFolderInfoURL - the DB URL where the folder-info table is located eg: http://OGo:OGo@localhost/test/folder_info - OCSFolderManagerDebugEnabled - enable folder-manager debug logs + OCSFolderManagerDebugEnabled - enable folder-manager debug logs + OCSFolderManagerSQLDebugEnabled - enable folder-manager SQL gen debug logs + + OCSChannelManagerDebugEnabled - enable channel debug pooling logs [PGDebugEnabled] - enable PostgreSQL adaptor debugging diff --git a/OGoContentStore/ocs_ls.m b/OGoContentStore/ocs_ls.m index 8c52ccb1..bd5d7309 100644 --- a/OGoContentStore/ocs_ls.m +++ b/OGoContentStore/ocs_ls.m @@ -3,8 +3,12 @@ #include #include "common.h" +@class NSUserDefaults; +@class OCSFolderManager; + @interface Tool : NSObject { + NSUserDefaults *ud; OCSFolderManager *folderManager; } @@ -16,18 +20,48 @@ - (id)init { if ((self = [super init])) { + self->ud = [[NSUserDefaults standardUserDefaults] retain]; self->folderManager = [[OCSFolderManager defaultFolderManager] retain]; } return self; } - (void)dealloc { + [self->ud release]; [self->folderManager release]; [super dealloc]; } /* operation */ +- (int)runOnPath:(NSString *)_path { + NSArray *subfolders; + unsigned i, count; + + [self logWithFormat:@"ls path: '%@'", _path]; + +#if 0 + if (![self->folderManager folderExistsAtPath:_path]) + [self logWithFormat:@"folder does not exist: '%@'", _path]; +#endif + + subfolders = [self->folderManager + listSubFoldersAtPath:_path + recursive:[ud boolForKey:@"r"]]; + if (subfolders == nil) { + [self logWithFormat:@"cannot list folder: '%@'", _path]; + return 1; + } + + for (i = 0, count = [subfolders count]; i < count; i++) { + printf("%s\n", [[subfolders objectAtIndex:i] cString]); + } + return 0; +} + - (int)run { + NSEnumerator *e; + NSString *path; + [self logWithFormat:@"manager: %@", self->folderManager]; if (![self->folderManager canConnect]) { @@ -35,6 +69,13 @@ return 1; } + e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] + objectEnumerator]; + [e nextObject]; // skip tool name + + while ((path = [e nextObject])) + [self runOnPath:path]; + return 0; } + (int)run { diff --git a/OGoContentStore/sql/folderinfo.psql b/OGoContentStore/sql/folderinfo.psql index 508d5e10..4aa12d85 100644 --- a/OGoContentStore/sql/folderinfo.psql +++ b/OGoContentStore/sql/folderinfo.psql @@ -2,6 +2,8 @@ -- -- (C) 2004 SKYRIX Software AG -- +-- TODO: +-- add a unique constraints on path CREATE SEQUENCE SOGo_folder_info_seq; @@ -11,14 +13,22 @@ CREATE TABLE SOGo_folder_info ( NOT NULL PRIMARY KEY, -- the primary key path VARCHAR(255) NOT NULL, -- the full path to the folder 'xyz/Cal' - rootName VARCHAR(255) NOT NULL, -- just the root path (for fast queries) + path1 VARCHAR(255) NOT NULL, -- individual parts (for fast queries) + path2 VARCHAR(255) NULL, -- individual parts (for fast queries) + path3 VARCHAR(255) NULL, -- individual parts (for fast queries) + path4 VARCHAR(255) NULL, -- individual parts (for fast queries) folderName VARCHAR(255) NOT NULL, -- last path component location VARCHAR(2048) NOT NULL, -- URL to database of the folder folderType VARCHAR(255) NOT NULL -- the folder type ... ); INSERT INTO SOGo_folder_info - ( path, rootname, foldername, location, foldertype ) + ( path, path1, path2, path3, path4, foldername, location, foldertype ) VALUES - ( '/Users/helge/Calendar', 'helge', 'Calendar', + ( '/Users/helge/Calendar', + 'Users', + 'helge', + 'Calendar', + NULL, + 'Calendar', 'http://OGo:OGo@localhost/test', 'Appointment' ); -- 2.39.5