sogo/SOPE/GDLContentStore/GCSFolderManager.m

1102 lines
30 KiB
Objective-C

/*
Copyright (C) 2006-2014 Inverse inc.
Copyright (C) 2004-2007 SKYRIX Software AG
This file is part of SOGo.
SOGo 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.
SOGo 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 OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSCharacterSet.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSProcessInfo.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSUserDefaults.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <GDLAccess/EOAdaptorContext.h>
#import <NGExtensions/NGResourceLocator.h>
#import "GCSChannelManager.h"
#import "EOAdaptorChannel+GCS.h"
#import "GCSAlarmsFolder.h"
#import "GCSFolder.h"
#import "GCSFolderType.h"
#import "GCSSessionsFolder.h"
#import "GCSSpecialQueries.h"
#import "NSURL+GCS.h"
#import "GCSFolderManager.h"
/*
Required database schema:
<arbitary table>
c_path
c_path1, path2, path3... [quickPathCount times]
c_foldername
TODO:
- add a local cache?
*/
@implementation GCSFolderManager
static GCSFolderManager *fm = nil;
static BOOL debugOn = NO;
static BOOL debugSQLGen = NO;
static BOOL debugPathTraversal = NO;
static int quickPathCount = 4;
static int randInc = 0;
static NSArray *emptyArray = nil;
#if 0
static NSString *GCSPathColumnName = @"c_path";
static NSString *GCSTypeColumnName = @"c_folder_type";
static NSString *GCSTypeRecordName = @"c_folder_type";
#endif
static NSString *GCSPathRecordName = @"c_path";
static NSString *GCSGenericFolderTypeName = @"Container";
static const char *GCSPathColumnPattern = "c_path%i";
static NSCharacterSet *asciiAlphaNumericCS = nil;
static BOOL _singleStoreMode = NO;
+ (void) initialize
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
int seed;
seed = ((unsigned int) [[NSDate date] timeIntervalSince1970]
+ [[NSProcessInfo processInfo] processIdentifier]);
srand (seed);
debugOn = [ud boolForKey: @"GCSFolderManagerDebugEnabled"];
debugSQLGen = [ud boolForKey: @"GCSFolderManagerSQLDebugEnabled"];
emptyArray = [[NSArray alloc] init];
if (!asciiAlphaNumericCS)
{
asciiAlphaNumericCS
= [NSCharacterSet characterSetWithCharactersInString:
@"0123456789"
@"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@"abcdefghijklmnopqrstuvwxyz"];
[asciiAlphaNumericCS retain];
}
if ([ud stringForKey: @"OCSStoreURL"] &&
[ud stringForKey: @"OCSAclURL"] &&
[ud stringForKey: @"OCSCacheFolderURL"])
_singleStoreMode = YES;
}
+ (BOOL) singleStoreMode
{
return _singleStoreMode;
}
+ (id)defaultFolderManager {
NSString *s;
NSURL *infoUrl;
NSURL *storeUrl;
NSURL *aclUrl;
NSURL *cacheFolderUrl;
if (!fm)
{
s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSFolderInfoURL"];
if ([s length] == 0) {
NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not configured.",
__PRETTY_FUNCTION__);
return nil;
}
if ((infoUrl = [NSURL URLWithString:s]) == nil) {
NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not a valid URL: '%@'",
__PRETTY_FUNCTION__, s);
return nil;
}
if (_singleStoreMode)
{
s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSStoreURL"];
if ([s length] == 0) {
NSLog(@"ERROR(%s): default 'OCSStoreURL' is not configured.",
__PRETTY_FUNCTION__);
return nil;
}
if ((storeUrl = [NSURL URLWithString:s]) == nil) {
NSLog(@"ERROR(%s): default 'OCSStoreURL' is not a valid URL: '%@'",
__PRETTY_FUNCTION__, s);
return nil;
}
s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSAclURL"];
if ([s length] == 0) {
NSLog(@"ERROR(%s): default 'OCSAclURL' is not configured.",
__PRETTY_FUNCTION__);
return nil;
}
if ((aclUrl = [NSURL URLWithString:s]) == nil) {
NSLog(@"ERROR(%s): default 'OCSAclURL' is not a valid URL: '%@'",
__PRETTY_FUNCTION__, s);
return nil;
}
s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSCacheFolderURL"];
if ([s length] == 0) {
NSLog(@"ERROR(%s): default 'OCSCacheFolderURL' is not configured.",
__PRETTY_FUNCTION__);
return nil;
}
if ((cacheFolderUrl = [NSURL URLWithString:s]) == nil) {
NSLog(@"ERROR(%s): default 'OCSCacheFolderURL' is not a valid URL: '%@'",
__PRETTY_FUNCTION__, s);
return nil;
}
}
else
{
storeUrl = nil;
aclUrl = nil;
cacheFolderUrl = nil;
}
if ((fm = [[self alloc] initWithFolderInfoLocation: infoUrl
andStoreLocation: storeUrl
andAclLocation: aclUrl
andCacheFolderLocation: cacheFolderUrl]) == nil) {
NSLog(@"ERROR(%s): could not create folder manager with URLs: '%@', '%@', '%@'",
__PRETTY_FUNCTION__, [infoUrl absoluteString],
[storeUrl absoluteString], [aclUrl absoluteString]);
return nil;
}
if (debugOn)
[self debugWithFormat:@"Note: setup default manager at: %@", infoUrl];
}
return fm;
}
- (NSDictionary *) loadDefaultFolderTypes: (NSString *) driver
{
NSMutableDictionary *typeMap;
NSArray *types;
unsigned i, count;
types = [[GCSFolderType resourceLocator] lookupAllFilesWithExtension:@"ocs"
doReturnFullPath:NO];
if ((count = [types count]) == 0) {
[self logWithFormat:@"Note: no GCS folder types found."];
return nil;
}
typeMap = [NSMutableDictionary dictionaryWithCapacity:count];
[self debugWithFormat:@"Note: loading %d GCS folder types:", count];
for (i = 0, count = [types count]; i < count; i++) {
NSString *type;
GCSFolderType *typeObject;
type = [[types objectAtIndex:i] stringByDeletingPathExtension];
// We skip type files associated to drivers directly, since
// [GCSFolderType folderTypeWithName:driver] will handle the
// mojo directly
if ([type rangeOfString: [NSString stringWithFormat: @"-%@", driver]].length)
continue;
typeObject = [GCSFolderType folderTypeWithName: type
driver: driver];
[self debugWithFormat:@" %@: %s",
type, [typeObject isNotNull] ? "OK" : "FAIL"];
[typeMap setObject: typeObject forKey:type];
}
return typeMap;
}
- (id)initWithFolderInfoLocation: (NSURL *)_infoUrl
andStoreLocation: (NSURL *)_storeUrl
andAclLocation: (NSURL *)_aclUrl
andCacheFolderLocation: (NSURL *)_cacheFolderUrl {
if (_infoUrl == nil) {
[self logWithFormat:@"ERROR(%s): missing folder info url!",
__PRETTY_FUNCTION__];
[self release];
return nil;
}
if (_singleStoreMode)
{
if (_storeUrl == nil) {
[self logWithFormat:@"ERROR(%s): missing folder store url!",
__PRETTY_FUNCTION__];
[self release];
return nil;
}
if (_aclUrl == nil) {
[self logWithFormat:@"ERROR(%s): missing folder acl url!",
__PRETTY_FUNCTION__];
[self release];
return nil;
}
if (_cacheFolderUrl == nil) {
[self logWithFormat:@"ERROR(%s): missing cache folder url!",
__PRETTY_FUNCTION__];
[self release];
return nil;
}
}
if ((self = [super init])) {
channelManager = [[GCSChannelManager defaultChannelManager] retain];
folderInfoLocation = [_infoUrl retain];
if (_singleStoreMode)
{
storeLocation = [_storeUrl retain];
aclLocation = [_aclUrl retain];
cacheFolderLocation = [_cacheFolderUrl retain];
}
else
{
storeLocation = nil;
aclLocation = nil;
cacheFolderLocation = nil;
}
if ([[self folderInfoTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
__PRETTY_FUNCTION__, [_infoUrl absoluteString]];
[self release];
return nil;
}
if (_singleStoreMode && [[self storeTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
__PRETTY_FUNCTION__, [_storeUrl absoluteString]];
[self release];
return nil;
}
if (_singleStoreMode && [[self aclTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
__PRETTY_FUNCTION__, [_aclUrl absoluteString]];
[self release];
return nil;
}
if (_singleStoreMode && [[self cacheFolderTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
__PRETTY_FUNCTION__, [_cacheFolderUrl absoluteString]];
[self release];
return nil;
}
/* register default folder types */
nameToType = [[self loadDefaultFolderTypes: [_infoUrl scheme]] copy];
}
return self;
}
- (void) dealloc
{
[nameToType release];
[folderInfoLocation release];
[channelManager release];
[super dealloc];
}
/* accessors */
- (NSURL *)folderInfoLocation {
return folderInfoLocation;
}
- (NSString *)folderInfoTableName {
return [[self folderInfoLocation] gcsTableName];
}
- (NSURL *)storeLocation {
return storeLocation;
}
- (NSString *)storeTableName {
return [[self storeLocation] gcsTableName];
}
- (NSURL *)aclLocation {
return aclLocation;
}
- (NSString *)aclTableName {
return [[self aclLocation] gcsTableName];
}
- (NSURL *)cacheFolderLocation {
return cacheFolderLocation;
}
- (NSString *)cacheFolderTableName {
return [[self cacheFolderLocation] gcsTableName];
}
/* connection */
- (GCSChannelManager *)channelManager {
return channelManager;
}
- (EOAdaptorChannel *)acquireOpenChannel {
EOAdaptorChannel *ch;
ch = [[self channelManager] acquireOpenChannelForURL:
[self folderInfoLocation]];
return ch;
}
- (void)releaseChannel:(EOAdaptorChannel *)_channel {
if (debugOn) [self debugWithFormat:@"releasing channel: %@", _channel];
[[self channelManager] releaseChannel:_channel];
}
- (BOOL)canConnect {
return [[self channelManager] canConnect:[self folderInfoLocation]];
}
- (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]) != nil) {
[self logWithFormat:@"ERROR(%s): cannot execute\n SQL '%@':\n %@",
__PRETTY_FUNCTION__, _sql, ex];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
attrs = [channel describeResults:NO /* do not beautify names */];
rows = [NSMutableArray arrayWithCapacity:16];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[rows addObject:row];
[self releaseChannel:channel];
return rows;
}
/* row factory */
- (GCSFolder *)folderForRecord:(NSDictionary *)_record {
GCSFolder *folder;
GCSFolderType *folderType;
NSString *folderTypeName, *locationString, *folderName, *path;
NSNumber *folderId;
NSURL *location, *quickLocation, *acl_location;
if (_record == nil) return nil;
folderTypeName = [_record objectForKey:@"c_folder_type"];
if (![folderTypeName isNotNull]) {
[self logWithFormat:@"ERROR(%s): missing type in folder: %@",
__PRETTY_FUNCTION__, _record];
return nil;
}
if ((folderType = [self folderTypeWithName:folderTypeName]) == nil) {
[self logWithFormat:
@"ERROR(%s): could not resolve type '%@' of folder: %@",
__PRETTY_FUNCTION__,
folderTypeName, [_record valueForKey:@"c_path"]];
return nil;
}
folderId = [_record objectForKey:@"c_folder_id"];
folderName = [_record objectForKey:@"c_path"];
path = [self pathFromInternalName:folderName];
if (!_singleStoreMode) {
locationString = [_record objectForKey:@"c_location"];
location = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
if (location == nil) {
[self logWithFormat:@"ERROR(%s): missing folder location in record: %@",
__PRETTY_FUNCTION__, _record];
return nil;
}
locationString = [_record objectForKey:@"c_quick_location"];
quickLocation = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
if (quickLocation == nil) {
[self logWithFormat:@"WARNING(%s): missing quick location in record: %@",
__PRETTY_FUNCTION__, _record];
}
locationString = [_record objectForKey:@"c_acl_location"];
acl_location = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
} else {
location = nil;
quickLocation = nil;
acl_location = nil;
}
folder = [[GCSFolder alloc] initWithPath:path primaryKey:folderId
folderTypeName:folderTypeName
folderType:folderType
location:location quickLocation:quickLocation
aclLocation:acl_location
folderManager:self];
return [folder autorelease];
}
- (GCSAlarmsFolder *) alarmsFolder
{
return [GCSAlarmsFolder alarmsFolderWithFolderManager: self];
}
/* sessions */
- (GCSSessionsFolder *)sessionsFolder
{
return [GCSSessionsFolder sessionsFolderWithFolderManager: self];
}
- (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;
char buf[32];
sprintf(buf, GCSPathColumnPattern, (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"];
if (debugPathTraversal) [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"];
if (debugPathTraversal)
[self logWithFormat:@"DIRECT SUBS, first level"];
}
else {
/* but for 'direct' subfolders, all following things must be empty */
[sql appendString:@" IS NULL"];
if (debugPathTraversal)
[self logWithFormat:@"DIRECT SUBS, lower level"];
}
}
[pathColumn release];
}
if (_beExact && (count > quickPathCount)) {
[sql appendString:@" AND c_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 c_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
{
NSString *name;
// TODO: ensure proper path and SQL escaping!
if ([self _isStandardizedPath:_path])
{
if ([_path hasSuffix:@"/"] && [_path length] > 1)
name = [_path substringToIndex: ([_path length] - 1)];
else
name = _path;
}
else
{
[self debugWithFormat:@"%s: not a standardized path: '%@'",
__PRETTY_FUNCTION__, _path];
name = nil;
}
return name;
}
- (NSArray *) internalNamesFromPath: (NSString *) _path
{
NSString *fname;
NSArray *fnames;
fname = [self internalNameFromPath: _path];
if (fname)
{
if ([fname hasPrefix:@"/"])
fname = [fname substringFromIndex: 1];
fnames = [fname componentsSeparatedByString:@"/"];
if ([fnames count] == 0)
fnames = nil;
}
else
fnames = 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;
}
- (NSDictionary *) filterRecords: (NSArray *) _records
forPath: (NSString *) _path
{
NSString *name;
NSDictionary *record, *matchRecord;
NSString *recName;
unsigned int i, count;
matchRecord = nil;
if (_records)
{
name = [self internalNameFromPath: _path];
if (name)
{
count = [_records count];
i = 0;
while (!matchRecord && i < count)
{
record = [_records objectAtIndex: i];
recName = [record objectForKey: GCSPathRecordName];
#if 0
[self logWithFormat:@"check '%@' vs '%@' (%@)...",
name, recName, [_records objectAtIndex:i]];
#endif
if ([name isEqualToString: recName])
matchRecord = record;
else
i++;
}
}
}
return matchRecord;
}
- (BOOL) folderExistsAtPath: (NSString *) _path
{
NSString *fname, *sname, *sql;
NSArray *fnames, *records;
unsigned int count;
NSDictionary *record;
BOOL result;
result = NO;
fnames = [self internalNamesFromPath: _path];
if (fnames)
{
sql = [self generateSQLPathFetchForInternalNames: fnames
exactMatch: YES
orDirectSubfolderMatch: NO];
if ([sql length])
{
records = [self performSQL: sql];
if (records)
{
count = [records count];
if (count)
{
fname = [self internalNameFromPath: _path];
if (count == 1)
{
record = [records objectAtIndex: 0];
sname = [record objectForKey: GCSPathRecordName];
result = [fname isEqualToString: sname];
}
else
[self logWithFormat: @"records: %@", records];
}
}
else
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
}
else
[self debugWithFormat:@"got no SQL for names: %@", fnames];
}
else
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return result;
}
- (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++) {
NSDictionary *record;
NSString *sname, *spath;
record = [records objectAtIndex:i];
sname = [record objectForKey:GCSPathRecordName];
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;
}
- (NSDictionary *) recordAtPath: (NSString *) _path
{
NSMutableString *sql;
NSArray *fnames, *records;
NSString *ws;
NSDictionary *record;
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return nil;
}
/* generate SQL to fetch folder attributes */
ws = [self generateSQLWhereForInternalNames:fnames
exactMatch:YES orDirectSubfolderMatch:NO];
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT "];
[sql appendString:@"c_folder_id, "];
[sql appendString:@"c_path, "];
[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 "];
[sql appendString:ws];
if (debugSQLGen) [self logWithFormat:@"folderAtPath: %@", sql];
/* fetching */
if ((records = [self performSQL:sql]) == nil) {
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
return nil;
}
// TODO: need to filter on path
// required when we start to have deeper hierarchies
// => isn't that already done below?
if ([records count] != 1) {
if ([records count] == 0) {
[self debugWithFormat:@"found no records for path: '%@'", _path];
return nil;
}
[self logWithFormat:@"ERROR(%s): more than one row for path: '%@'",
__PRETTY_FUNCTION__, _path];
return nil;
}
if ((record = [self filterRecords:records forPath:_path]) == nil) {
[self debugWithFormat:@"found no record for path: '%@'", _path];
return nil;
}
return record;
}
- (GCSFolder *) folderAtPath: (NSString *) _path
{
return [self folderForRecord: [self recordAtPath: _path]];
}
- (NSString *) baseTableNameWithUID: (NSString *) _uid
{
unichar currentChar;
unsigned int count, max, done;
NSMutableString *newUID;
newUID = [NSMutableString stringWithString: @"sogo"];
max = [_uid length];
done = 0;
count = 0;
while (done < 8 && count < max)
{
currentChar = [_uid characterAtIndex: count];
if ([asciiAlphaNumericCS characterIsMember: currentChar])
{
[newUID appendFormat: @"%c", currentChar];
done++;
}
count++;
}
randInc++;
return [NSString stringWithFormat: @"%@%.3x%.8x",
newUID, randInc & 0xfff, (unsigned int) rand()];
}
- (NSException *) _reallyCreateFolderWithName: (NSString *) folderName
andFolderType: (NSString *) folderType
andType: (GCSFolderType *) ftype
andChannel: (EOAdaptorChannel *) channel
atPath: (NSString *) path
{
NSString *baseURL, *tableName, *quickTableName, *aclTableName, *createQuery, *sql;
GCSSpecialQueries *specialQuery;
EOAdaptorContext *aContext;
NSMutableArray *paths;
NSException *error;
NSRange range;
paths = [NSMutableArray arrayWithArray: [path componentsSeparatedByString: @"/"]];
while ([paths count] < 5)
[paths addObject: @"NULL"];
aContext = [channel adaptorContext];
[aContext beginTransaction];
tableName = [self baseTableNameWithUID: [paths objectAtIndex: 2]];
quickTableName = [tableName stringByAppendingString: @"_quick"];
aclTableName = [tableName stringByAppendingString: @"_acl"];
// TBD: fix SQL injection issues.
// We no longer call stringByDeletingLastPathComponent since, since GNUstep 1.22,
// it'll replace // characters in the URL with /, so mysql:// becomes mysql:/
// This is to conform with recent Apple changes.
baseURL = [folderInfoLocation absoluteString];
range = [baseURL rangeOfString: @"/" options: NSBackwardsSearch];
if (range.location != NSNotFound)
baseURL = [baseURL substringToIndex: range.location];
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],
[folderName stringByReplacingString: @"'" withString: @"''"],
baseURL, tableName,
baseURL, quickTableName,
baseURL, aclTableName,
folderType];
error = [channel evaluateExpressionX: sql];
if (!_singleStoreMode && !error)
{
specialQuery = [channel specialQueries];
createQuery = [specialQuery createFolderTableWithName: tableName];
error = [channel evaluateExpressionX: createQuery];
if (!error)
{
sql = [ftype sqlQuickCreateWithTableName: quickTableName];
error = [channel evaluateExpressionX: sql];
if (!error)
{
createQuery = [specialQuery
createFolderACLTableWithName: aclTableName];
error = [channel evaluateExpressionX: createQuery];
}
}
}
if (error)
[aContext rollbackTransaction];
else
[aContext commitTransaction];
return error;
}
- (NSException *) createFolderOfType: (NSString *) _type
withName: (NSString*) _name
atPath: (NSString *) _path
{
// TBD: would be best to perform all operations as a single SQL statement.
GCSFolderType *ftype;
EOAdaptorChannel *channel;
NSException *error;
// TBD: fix SQL injection issue!
if ([self folderExistsAtPath: _path])
error = [NSException exceptionWithName: @"GCSExitingFolder"
reason: @"a folder already exists at that path"
userInfo: nil];
else
{
ftype = [self folderTypeWithName:_type];
if (ftype)
{
channel = [self acquireOpenChannel];
if (channel)
{
error = [self _reallyCreateFolderWithName: _name
andFolderType: _type
andType: ftype andChannel: channel
atPath: _path];
if (error && [self folderExistsAtPath: _path])
error = nil;
[self releaseChannel: channel];
}
else
error = [NSException exceptionWithName: @"GCSNoChannel"
reason: @"could not open channel"
userInfo: nil];
}
else
error = [NSException exceptionWithName: @"GCSMissingFolderType"
reason: @"missing folder type"
userInfo: nil];
}
return error;
}
- (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];
}
[[channel adaptorContext] beginTransaction];
if ((ex = [channel evaluateExpressionX:sql]) != nil) {
[self releaseChannel:channel];
return ex;
}
[[channel adaptorContext] commitTransaction];
[self releaseChannel:channel];
return [folder deleteFolder];
}
/* folder types */
- (GCSFolderType *) folderTypeWithName: (NSString *) _name
{
NSString *specificName;
GCSFolderType *type;
if ([_name length] == 0)
_name = GCSGenericFolderTypeName;
specificName = [NSString stringWithFormat: @"%@-%@",
_name, [folderInfoLocation scheme]];
type = [nameToType objectForKey: [specificName lowercaseString]];
if (!type)
type = [nameToType objectForKey:[_name lowercaseString]];
return type;
}
/* cache management */
- (void) reset
{
/* does nothing in the moment, but we need a way to signal refreshes */
}
/* debugging */
- (BOOL) isDebuggingEnabled
{
return debugOn;
}
/* description */
- (NSString *) description
{
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[ms appendFormat:@" url=%@", [folderInfoLocation absoluteString]];
[ms appendFormat:@" channel-manager=0x%p", [self channelManager]];
[ms appendString:@">"];
return ms;
}
@end /* GCSFolderManager */