/*
- Copyright (C) 2004-2005 SKYRIX Software AG
+ Copyright (C) 2004-2007 SKYRIX Software AG
This file is part of OpenGroupware.org.
static NSString *GCSPathRecordName = @"c_path";
static NSString *GCSGenericFolderTypeName = @"Container";
static const char *GCSPathColumnPattern = "c_path%i";
+static NSCharacterSet *asciiAlphaNumericCS = nil;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderManagerDebugEnabled"];
debugSQLGen = [ud boolForKey:@"GCSFolderManagerSQLDebugEnabled"];
emptyArray = [[NSArray alloc] init];
+ if (!asciiAlphaNumericCS)
+ {
+ asciiAlphaNumericCS
+ = [NSCharacterSet characterSetWithCharactersInString:
+ @"0123456789"
+ @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ @"abcdefghijklmnopqrstuvwxyz"];
+ [asciiAlphaNumericCS retain];
+ }
}
+ (id)defaultFolderManager {
if ((self = [super init])) {
self->channelManager = [[GCSChannelManager defaultChannelManager] retain];
self->folderInfoLocation = [_url retain];
+ self->folderNamePrefix = nil;
if ([[self folderInfoTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
[self->nameToType release];
[self->folderInfoLocation release];
[self->channelManager release];
+ [self->folderNamePrefix release];
[super dealloc];
}
/* accessors */
+- (void) setFolderNamePrefix:(NSString *)_folderNamePrefix
+{
+ ASSIGN(self->folderNamePrefix, _folderNamePrefix);
+}
+
+- (NSString *) folderNamePrefix
+{
+ return self->folderNamePrefix;
+}
+
- (NSURL *)folderInfoLocation {
return self->folderInfoLocation;
}
GCSFolderType *folderType;
NSString *folderTypeName, *locationString, *folderName, *path;
NSNumber *folderId;
- NSURL *location, *quickLocation;
+ NSURL *location, *quickLocation, *aclLocation;
if (_record == nil) return nil;
[self logWithFormat:@"WARNING(%s): missing quick location in record: %@",
__PRETTY_FUNCTION__, _record];
}
+
+ locationString = [_record objectForKey:@"c_acl_location"];
+ aclLocation = [locationString isNotNull]
+ ? [NSURL URLWithString:locationString]
+ : nil;
folder = [[GCSFolder alloc] initWithPath:path primaryKey:folderId
folderTypeName:folderTypeName
folderType:folderType
location:location quickLocation:quickLocation
+ aclLocation:aclLocation
folderManager:self];
return [folder autorelease];
}
[sql appendString:@"SELECT "];
[sql appendString:@"c_folder_id, "];
[sql appendString:@"c_path, "];
- [sql appendString:@"c_location, c_quick_location, "];
- [sql appendString:@"c_folder_type"];
+ [sql appendString:@"c_location, c_quick_location, c_acl_location,"];
+ [sql appendString:@" c_folder_type"];
[sql appendString:@" FROM "];
[sql appendString:[self folderInfoTableName]];
[sql appendString:@" WHERE "];
return [self folderForRecord:record];
}
+- (NSString *)sqlCreateWithTableName: (NSString *)_tabName {
+ return [NSString stringWithFormat: @"CREATE TABLE %@ (\n"
+ @" c_name VARCHAR (256) NOT NULL,\n"
+ @" c_content VARCHAR (100000) NOT NULL,\n"
+ @" c_creationdate INT4 NOT NULL,\n"
+ @" c_lastmodified INT4 NOT NULL,\n"
+ @" c_version INT4 NOT NULL\n"
+ @")",
+ _tabName];
+}
+
+- (NSString *)sqlAclCreateWithTableName: (NSString *)_tabName {
+ return [NSString stringWithFormat: @"CREATE TABLE %@ (\n"
+ @" c_uid VARCHAR (256) NOT NULL,\n"
+ @" c_object VARCHAR (256) NOT NULL,\n"
+ @" c_role VARCHAR (80) NOT NULL\n"
+ @")",
+ _tabName];
+}
+
+- (NSString *)baseTableNameForFolderAtPath:(NSString *)_path {
+ NSMutableString *fixedPath;
+ unsigned int count, max;
+ unichar currentChar;
+
+ fixedPath = [NSMutableString new];
+ [fixedPath autorelease];
+
+ if (self->folderNamePrefix != nil)
+ [fixedPath appendString: self->folderNamePrefix];
+
+ max = [_path length];
+ for (count = 0; count < max; count++) {
+ currentChar = [_path characterAtIndex: count];
+ if ([asciiAlphaNumericCS characterIsMember: currentChar])
+ [fixedPath appendFormat: @"%Lc", currentChar];
+ else
+ [fixedPath appendString: @"_"];
+ }
+
+ return (([fixedPath length] < 49)
+ ? (NSString *)fixedPath : [fixedPath substringToIndex: 49]);
+}
+
+- (NSString *)finalizedTableNameForBaseName:(NSString *)_baseName
+ atBaseURL:(NSString *)_baseURL
+ withChannel:(EOAdaptorChannel *)_channel {
+ NSString *potentialName, *sqlTestFormat, *sqlTest;
+ unsigned int count;
+
+ potentialName = _baseName;
+ sqlTestFormat = [NSString stringWithFormat: @"SELECT * FROM %@"
+ @" WHERE c_location = '%@/%%@'"
+ @" OR c_quick_location = '%@/%%@_quick'"
+ @" OR c_acl_location = '%@/%%@_acl'",
+ [self folderInfoTableName],
+ _baseURL, _baseURL, _baseURL];
+
+ sqlTest = [NSString stringWithFormat: sqlTestFormat,
+ potentialName, potentialName, potentialName];
+ count = 0;
+ while ([[self performSQL: sqlTest] isNotEmpty]) {
+ count++;
+ potentialName = [NSString stringWithFormat: @"%@%d", _baseName, count];
+ sqlTest = [NSString stringWithFormat: sqlTestFormat,
+ potentialName, potentialName, potentialName];
+ }
+
+ return potentialName;
+}
+
- (NSException *)createFolderOfType:(NSString *)_type atPath:(NSString *)_path{
- // TODO: implement folder create
- GCSFolderType *ftype;
-
+ // TBD: badly broken, needs to be wrapped in a transaction.
+ // TBD: would be best to perform all operations as a single SQL statement.
+ GCSFolderType *ftype;
+ NSString *tableName, *quickTableName, *aclTableName;
+ NSString *baseURL, *pathElement;
+ EOAdaptorChannel *channel;
+ NSEnumerator *pathElements;
+ NSMutableArray *paths;
+ NSException *error;
+ NSString *sql;
+
+ // TBD: fix SQL injection issue!
+ sql = [NSString stringWithFormat: @"SELECT * FROM %@ WHERE c_path = '%@'",
+ [self folderInfoTableName], _path];
+ if ([[self performSQL: sql] isNotEmpty]) {
+ return [NSException exceptionWithName:@"GCSExitingFolder"
+ reason:@"a folder already exists at that path"
+ userInfo:nil];
+ }
if ((ftype = [self folderTypeWithName:_type]) == nil) {
return [NSException exceptionWithName:@"GCSMissingFolderType"
- reason:@"missing folder type"
+ reason:@"missing folder type"userInfo:nil];
+ }
+ if ((channel = [self acquireOpenChannel]) == nil) {
+ return [NSException exceptionWithName:@"GCSNoChannel"
+ reason:@"could not open channel"
userInfo:nil];
}
- [self logWithFormat:@"create folder of type: %@", ftype];
+ tableName = [self baseTableNameForFolderAtPath: _path];
+ baseURL
+ = [[folderInfoLocation absoluteString] stringByDeletingLastPathComponent];
+ tableName = [self finalizedTableNameForBaseName: tableName
+ atBaseURL: baseURL withChannel: channel];
+ quickTableName = [tableName stringByAppendingString: @"_quick"];
+ aclTableName = [tableName stringByAppendingString: @"_acl"];
+
+ sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil)
+ ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+
+ sql = [@"DROP TABLE " stringByAppendingString:tableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil)
+ ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+
+ sql = [@"DROP TABLE " stringByAppendingString:aclTableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil)
+ ; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
+
+ sql = [self sqlCreateWithTableName: tableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil)
+ return error;
+
+ sql = [ftype sqlQuickCreateWithTableName: quickTableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil) {
+ /* 'rollback' TBD: wrap in proper tx */
+ sql = [@"DROP TABLE " stringByAppendingString:tableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil) {
+ [self warnWithFormat:@"failed to drop freshly created table: %@",
+ tableName];
+ }
+
+ return error;
+ }
+
+ sql = [self sqlAclCreateWithTableName: aclTableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil) {
+ /* 'rollback' TBD: wrap in proper tx */
+ sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil) {
+ [self warnWithFormat:@"failed to drop freshly created table: %@",
+ tableName];
+ }
+ sql = [@"DROP TABLE " stringByAppendingString:tableName];
+ if ((error = [channel evaluateExpressionX:sql]) != nil) {
+ [self warnWithFormat:@"failed to drop freshly created table: %@",
+ tableName];
+ }
+
+ return error;
+ }
- return [NSException exceptionWithName:@"NotYetImplemented"
- reason:@"no money, no time, ..."
- userInfo:nil];
+ paths = [[NSMutableArray alloc] initWithCapacity: 5];
+
+ pathElements = [[_path componentsSeparatedByString: @"/"] objectEnumerator];
+ while ((pathElement = [pathElements nextObject]) != nil) {
+ NSString *p = [[NSString alloc] initWithFormat: @"'%@'", pathElement];
+ [paths addObject: p];
+ [p release]; p = nil;
+ }
+
+ while ([paths count] < 5)
+ [paths addObject: @"NULL"];
+
+ // TBD: fix SQL injection issues
+ sql = [NSString stringWithFormat: @"INSERT INTO %@"
+ @" (c_path, c_path1, c_path2, c_path3, c_path4,"
+ @" c_foldername, c_location, c_quick_location,"
+ @" c_acl_location, c_folder_type)"
+ @" VALUES ('%@', %@, %@, %@, %@, '%@', '%@/%@',"
+ @" '%@/%@', '%@/%@', '%@')",
+ [self folderInfoTableName], _path,
+ [paths objectAtIndex: 1], [paths objectAtIndex: 2],
+ [paths objectAtIndex: 3], [paths objectAtIndex: 4],
+ [_path lastPathComponent],
+ baseURL, tableName,
+ baseURL, quickTableName,
+ baseURL, aclTableName,
+ _type];
+ if ((error = [channel evaluateExpressionX:sql]) != nil)
+ return error;
+
+ [paths release]; paths = nil;
+ [self releaseChannel: channel];
+
+ return nil;
+}
+
+- (NSException *)deleteFolderAtPath:(NSString *)_path {
+ GCSFolder *folder;
+ NSArray *fnames;
+ NSString *sql, *ws;
+ EOAdaptorChannel *channel;
+ NSException *ex;
+
+ if ((folder = [self folderAtPath:_path]) == nil) {
+ return [NSException exceptionWithName:@"GCSMissingFolder"
+ reason:@"missing folder"
+ userInfo:nil];
+ }
+
+ if ((fnames = [self internalNamesFromPath:_path]) == nil) {
+ [self debugWithFormat:@"got no internal names for path: '%@'", _path];
+ return nil;
+ }
+
+ ws = [self generateSQLWhereForInternalNames:fnames
+ exactMatch:YES orDirectSubfolderMatch:NO];
+
+ sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@",
+ [self folderInfoTableName], ws];
+ if ((channel = [self acquireOpenChannel]) == nil) {
+ return [NSException exceptionWithName:@"GCSNoChannel"
+ reason:@"could not "
+ userInfo:nil];
+ }
+
+ if ((ex = [channel evaluateExpressionX:sql]) != nil) {
+ [self releaseChannel:channel];
+ return ex;
+ }
+
+ [self releaseChannel:channel];
+
+ return [folder deleteFolder];
}
/* folder types */