]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@67 d1b88da0-ebda-0310-925b-ed51d8...
authorhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 28 Jun 2004 15:14:55 +0000 (15:14 +0000)
committerhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 28 Jun 2004 15:14:55 +0000 (15:14 +0000)
OGoContentStore/OCSChannelManager.m
OGoContentStore/OCSFolderManager.h
OGoContentStore/OCSFolderManager.m
OGoContentStore/README
OGoContentStore/ocs_ls.m
OGoContentStore/sql/folderinfo.psql

index 286d2bd2d53aa8945e30aaf9fb9c8439c8def0a3..d7a09d09b6361c630e4071d439f23eaaeedef398 100644 (file)
 #include <GDLAccess/EOAdaptorChannel.h>
 #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";
index 23a826bda45e0c8531c3255618015b05da483290..af9db9596274f9ffeddaa25761932a490f85ddf9 100644 (file)
@@ -32,7 +32,7 @@
   model and manage the tables required for a folder.
 */
 
-@class NSURL;
+@class NSString, NSArray, NSURL;
 @class OCSChannelManager;
 
 @interface OCSFolderManager : NSObject
 
 - (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__ */
index 15ba7ba9742eb48d29f2b58678b3677d64ce241f..a0c9fb805a86f61fd03b4787be38b751c055f221 100644 (file)
 #include "common.h"
 #include <GDLAccess/EOAdaptorChannel.h>
 
+/*
+  Required database schema:
+  
+    <arbitary table>
+      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 {
index f04e2f61fecdab4d08687b15a6ded090a45559cb..58588bd3c82f34c417fadf1e885fcb9fcec3f541 100644 (file)
@@ -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
 
index 8c52ccb19e0111025e7a28826264d442d07c20e1..bd5d7309c4ae203bdbbe7c631419c5a869da87ec 100644 (file)
@@ -3,8 +3,12 @@
 #include <OGoContentStore/OCSFolderManager.h>
 #include "common.h"
 
+@class NSUserDefaults;
+@class OCSFolderManager;
+
 @interface Tool : NSObject
 {
+  NSUserDefaults   *ud;
   OCSFolderManager *folderManager;
 }
 
 
 - (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]) {
     return 1;
   }
   
+  e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults] 
+                       objectEnumerator];
+  [e nextObject]; // skip tool name
+  
+  while ((path = [e nextObject]))
+    [self runOnPath:path];
+  
   return 0;
 }
 + (int)run {
index 508d5e1017f50925c53871c1c97b5a4de68b096a..4aa12d8513c1e4871c8fe5d1864d596bd03d0e78 100644 (file)
@@ -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' );