--- /dev/null
+/*
+ Copyright (C) 2004 SKYRIX Software AG
+
+ This file is part of OpenGroupware.org.
+
+ OGo 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.
+
+ OGo 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.
+*/
+// $Id$
+
+#include "EOQualifier+OCS.h"
+#include "common.h"
+
+@implementation EOQualifier(OCS)
+
+- (void)_appendAndQualifier:(EOAndQualifier *)_q
+ toString:(NSMutableString *)_ms
+{
+ // TODO: move to EOQualifier category
+ NSArray *qs;
+ unsigned i, count;
+
+ qs = [_q qualifiers];
+ if ((count = [qs count]) == 0)
+ return;
+
+ for (i = 0; i < count; i++) {
+ if (i != 0) [_ms appendString:@" AND "];
+ if (count > 1) [_ms appendString:@"("];
+ [[qs objectAtIndex:i] _ocsAppendToString:_ms];
+ if (count > 1) [_ms appendString:@")"];
+ }
+}
+- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q
+ toString:(NSMutableString *)_ms
+{
+ id val;
+
+ [_ms appendString:[_q key]];
+
+ if ((val = [_q value])) {
+ SEL op = [_q selector];
+
+ if ([val isNotNull]) {
+ if (sel_eq(op, EOQualifierOperatorEqual))
+ [_ms appendString:@" = "];
+ else if (sel_eq(op, EOQualifierOperatorNotEqual))
+ [_ms appendString:@" != "];
+ else if (sel_eq(op, EOQualifierOperatorLessThan))
+ [_ms appendString:@" < "];
+ else if (sel_eq(op, EOQualifierOperatorGreaterThan))
+ [_ms appendString:@" > "];
+ else if (sel_eq(op, EOQualifierOperatorLessThanOrEqualTo))
+ [_ms appendString:@" <= "];
+ else if (sel_eq(op, EOQualifierOperatorGreaterThanOrEqualTo))
+ [_ms appendString:@" >= "];
+ else if (sel_eq(op, EOQualifierOperatorLike))
+ [_ms appendString:@" LIKE "];
+ else {
+ [self logWithFormat:@"ERROR(%s): unsupported operation for null: %@",
+ __PRETTY_FUNCTION__, NSStringFromSelector(op)];
+ }
+
+ if ([val isKindOfClass:[NSNumber class]])
+ [_ms appendString:[val stringValue]];
+ else if ([val isKindOfClass:[NSString class]]) {
+ [_ms appendString:@"'"];
+ [_ms appendString:val];
+ [_ms appendString:@"'"];
+ }
+ else {
+ [self logWithFormat:@"ERROR(%s): unsupported value class: %@",
+ __PRETTY_FUNCTION__, NSStringFromClass([val class])];
+ }
+ }
+ else {
+ if (sel_eq(op, EOQualifierOperatorEqual))
+ [_ms appendString:@" IS NULL"];
+ else if (sel_eq(op, EOQualifierOperatorEqual))
+ [_ms appendString:@" IS NOT NULL"];
+ else {
+ [self logWithFormat:@"ERROR(%s): invalid operation for null: %@",
+ __PRETTY_FUNCTION__, NSStringFromSelector(op)];
+ }
+ }
+ }
+ else
+ [_ms appendString:@" IS NULL"];
+}
+
+- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms {
+ if (_q == nil) return;
+
+ if ([_q isKindOfClass:[EOAndQualifier class]])
+ [self _appendAndQualifier:(id)_q toString:_ms];
+ else if ([_q isKindOfClass:[EOKeyValueQualifier class]])
+ [self _appendKeyValueQualifier:(id)_q toString:_ms];
+ else
+ NSLog(@"ERROR: unknown qualifier: %@", _q);
+}
+
+- (void)_ocsAppendToString:(NSMutableString *)_ms {
+ [self _appendQualifier:self toString:_ms];
+}
+
+@end /* EOQualifier(OCS) */
#include "OCSChannelManager.h"
#include "NSURL+OCS.h"
+#include "EOAdaptorChannel+OCS.h"
#include <GDLAccess/EOAdaptor.h>
#include <GDLAccess/EOAdaptorContext.h>
#include <GDLAccess/EOAdaptorChannel.h>
@implementation OCSChannelManager
-static BOOL debugOn = YES;
-static BOOL debugPools = YES;
-static int ChannelExpireAge = 180;
+static BOOL debugOn = NO;
+static BOOL debugPools = NO;
+static int ChannelExpireAge = 180;
+static NSTimeInterval ChannelCollectionTimer = 5 * 60;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"OCSChannelManagerDebugEnabled"];
debugPools = [ud boolForKey:@"OCSChannelManagerPoolDebugEnabled"];
+
ChannelExpireAge = [[ud objectForKey:@"OCSChannelExpireAge"] intValue];
if (ChannelExpireAge < 1)
ChannelExpireAge = 180;
+
+ ChannelCollectionTimer =
+ [[ud objectForKey:@"OCSChannelCollectionTimer"] intValue];
+ if (ChannelCollectionTimer < 1)
+ ChannelCollectionTimer = 5*60;
}
+ (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
self->urlToAdaptor = [[NSMutableDictionary alloc] initWithCapacity:4];
self->availableChannels = [[NSMutableArray alloc] initWithCapacity:16];
self->busyChannels = [[NSMutableArray alloc] initWithCapacity:16];
+
+ self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
+ ChannelCollectionTimer
+ target:self selector:@selector(_garbageCollect:)
+ userInfo:nil repeats:YES] retain];
}
return self;
}
[handle release];
return;
}
-
- [self logWithFormat:
- @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
+
+ if (debugPools) {
+ [self logWithFormat:
+ @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
+ }
/* not reusing channel */
[handle release]; handle = nil;
return result;
}
+/* collect old channels */
+
+- (void)_garbageCollect:(NSTimer *)_timer {
+ NSMutableArray *handlesToRemove;
+ unsigned i, count;
+
+ if ((count = [self->availableChannels count]) == 0)
+ /* no available channels */
+ return;
+
+ /* collect channels to expire */
+
+ handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4];
+ for (i = 0; i < count; i++) {
+ OCSChannelHandle *handle;
+
+ handle = [self->availableChannels objectAtIndex:i];
+ if (![[handle channel] isOpen]) {
+ [handlesToRemove addObject:handle];
+ continue;
+ }
+ if ([handle age] > ChannelExpireAge) {
+ [handlesToRemove addObject:handle];
+ continue;
+ }
+ }
+
+ /* remove channels */
+ count = [handlesToRemove count];
+ if (debugPools)
+ [self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count];
+ for (i = 0; i < count; i++) {
+ OCSChannelHandle *handle;
+
+ handle = [[self->availableChannels objectAtIndex:i] retain];
+ [self->availableChannels removeObject:handle];
+ if ([[handle channel] isOpen])
+ [[handle channel] closeChannel];
+ [handle release];
+ }
+}
+
/* debugging */
- (BOOL)isDebuggingEnabled {
#include "OCSFieldExtractor.h"
#include "NSURL+OCS.h"
#include "EOAdaptorChannel+OCS.h"
+#include "EOQualifier+OCS.h"
#include "common.h"
@implementation OCSFolder
-static BOOL debugOn = YES;
+static BOOL debugOn = YES;
+static BOOL doLogStore = YES;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
- debugOn = [ud boolForKey:@"OCSFolderDebugEnabled"];
+ debugOn = [ud boolForKey:@"OCSFolderDebugEnabled"];
+ doLogStore = [ud boolForKey:@"OCSFolderStoreDebugEnabled"];
}
- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
recursive:YES];
}
-- (NSString *)fetchContentWithName:(NSString *)_name {
+- (id)_fetchValueOfColumn:(NSString *)_col attributeName:(NSString *)_attrName
+ inContentWithName:(NSString *)_name
+{
EOAdaptorChannel *channel;
NSException *error;
NSDictionary *row;
/* generate SQL */
- sql = @"SELECT \"c_content\" FROM ";
+ sql = @"SELECT ";
+ sql = [sql stringByAppendingString:_col];
+ sql = [sql stringByAppendingString:@" FROM "];
sql = [sql stringByAppendingString:[self storeTableName]];
sql = [sql stringByAppendingString:@" WHERE \"c_name\" = '"];
sql = [sql stringByAppendingString:_name];
sql = [sql stringByAppendingString:@"'"];
/* run SQL */
-
+
if ((error = [channel evaluateExpressionX:sql]) != nil) {
- [self logWithFormat:@"ERROR(%s): cannot execute blob-fetch SQL '%@': %@",
+ [self logWithFormat:@"ERROR(%s): cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel];
return nil;
result = nil;
attrs = [channel describeResults];
if ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) {
- result = [[[row objectForKey:@"cContent"] copy] autorelease];
+ result = [[[row objectForKey:_attrName] copy] autorelease];
if (![result isNotNull]) result = nil;
[channel cancelFetch];
}
return result;
}
+- (NSNumber *)versionOfContentWithName:(NSString *)_name {
+ return [self _fetchValueOfColumn:@"c_version" attributeName:@"cVersion"
+ inContentWithName:_name];
+}
+
+- (NSString *)fetchContentWithName:(NSString *)_name {
+ return [self _fetchValueOfColumn:@"c_content" attributeName:@"cContent"
+ inContentWithName:_name];
+}
+
+/* writing content */
+
+- (NSString *)_formatRowValue:(id)_value {
+ if (![_value isNotNull])
+ return @"NULL";
+
+ if ([_value isKindOfClass:[NSString class]])
+ return [NSString stringWithFormat:@"'%@'", _value];
+
+ if ([_value isKindOfClass:[NSNumber class]])
+ return [_value stringValue];
+
+ if ([_value isKindOfClass:[NSCalendarDate class]]) {
+ /* be smart ... convert to timestamp */
+ return [NSString stringWithFormat:@"%i", [_value timeIntervalSince1970]];
+ }
+
+ [self logWithFormat:@"cannot handle value class: %@", [_value class]];
+ return nil;
+}
+
+- (NSString *)_generateInsertStatementForRow:(NSDictionary *)_row
+ tableName:(NSString *)_table
+{
+ // TODO: move to NSDictionary category?
+ NSMutableString *sql;
+ NSArray *keys;
+ unsigned i, count;
+
+ if (_row == nil || _table == nil)
+ return nil;
+
+ keys = [_row allKeys];
+
+ sql = [NSMutableString stringWithCapacity:512];
+ [sql appendString:@"INSERT INTO "];
+ [sql appendString:_table];
+ [sql appendString:@" ("];
+
+ for (i = 0, count = [keys count]; i < count; i++) {
+ if (i != 0) [sql appendString:@", "];
+ [sql appendString:[keys objectAtIndex:i]];
+ }
+
+ [sql appendString:@") VALUES ("];
+
+ for (i = 0, count = [keys count]; i < count; i++) {
+ id value;
+
+ if (i != 0) [sql appendString:@", "];
+ value = [_row objectForKey:[keys objectAtIndex:i]];
+ value = [self _formatRowValue:value];
+ [sql appendString:value];
+ }
+
+ [sql appendString:@")"];
+ return sql;
+}
+
+- (NSString *)_generateUpdateStatementForRow:(NSDictionary *)_row
+ tableName:(NSString *)_table
+ whereColumn:(NSString *)_colname isEqualTo:(id)_value
+{
+ // TODO: move to NSDictionary category?
+ NSMutableString *sql;
+ NSArray *keys;
+ unsigned i, count;
+
+ if (_row == nil || _table == nil)
+ return nil;
+
+ keys = [_row allKeys];
+
+ sql = [NSMutableString stringWithCapacity:512];
+ [sql appendString:@"UPDATE "];
+ [sql appendString:_table];
+
+ [sql appendString:@" SET "];
+ for (i = 0, count = [keys count]; i < count; i++) {
+ id value;
+
+ value = [_row objectForKey:[keys objectAtIndex:i]];
+ value = [self _formatRowValue:value];
+
+ if (i != 0) [sql appendString:@", "];
+ [sql appendString:[keys objectAtIndex:i]];
+ [sql appendString:@" = "];
+ [sql appendString:value];
+ }
+
+ [sql appendString:@" WHERE "];
+ [sql appendString:_colname];
+ [sql appendString:@" = "];
+ [sql appendString:[self _formatRowValue:_value]];
+
+ return sql;
+}
+
- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name {
EOAdaptorChannel *storeChannel, *quickChannel;
- NSMutableDictionary *quickRow;
+ NSMutableDictionary *quickRow, *contentRow;
OCSFieldExtractor *extractor;
+ NSException *error;
+ NSNumber *storedVersion;
+ BOOL isNewRecord;
+ NSCalendarDate *nowDate;
+ NSNumber *now;
+ NSString *qsql, *bsql;
- [self logWithFormat:@"SHOULD store content:\n%@", _content];
+ error = nil;
+ nowDate = [NSCalendarDate date];
+ now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
- /* extract info */
+ if (doLogStore)
+ [self logWithFormat:@"SHOULD store content: %@\n%@", _name, _content];
+
+ storedVersion = [self versionOfContentWithName:_name];
+ if (doLogStore)
+ [self logWithFormat:@" version: %@", storedVersion];
+ isNewRecord = [storedVersion isNotNull] ? NO : YES;
+
+ /* extract quick info */
extractor = [self->folderInfo quickExtractor];
quickRow = [extractor extractQuickFieldsFromContent:_content];
-
- [self logWithFormat:@"SHOULD store quick: %@", quickRow];
+ [quickRow setObject:_name forKey:@"c_name"];
+
+ if (doLogStore)
+ [self logWithFormat:@" store quick: %@", quickRow];
+
+ /* make content row */
+
+ contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
+ [contentRow setObject:_name forKey:@"c_name"];
+ if (isNewRecord) [contentRow setObject:now forKey:@"c_creationdate"];
+ [contentRow setObject:now forKey:@"c_lastmodified"];
+ if (isNewRecord)
+ [contentRow setObject:[NSNumber numberWithInt:0] forKey:@"c_version"];
+ else {
+ [contentRow setObject:[NSNumber numberWithInt:[storedVersion intValue]]
+ forKey:@"c_version"];
+ }
+ [contentRow setObject:_content forKey:@"c_content"];
/* open channels */
}
// TODO: gen SQL, execute in transactions
+ if (isNewRecord) { /* insert */
+ qsql = [self _generateInsertStatementForRow:quickRow
+ tableName:[self quickTableName]];
+ bsql = [self _generateInsertStatementForRow:contentRow
+ tableName:[self storeTableName]];
+
+ if ((error = [storeChannel evaluateExpressionX:bsql]) != nil) {
+ [self logWithFormat:@"ERROR(%s): cannot insert content '%@': %@",
+ __PRETTY_FUNCTION__, bsql, error];
+ }
+ else if ((error = [quickChannel evaluateExpressionX:qsql]) != nil) {
+ NSString *delsql;
+ NSException *delErr;
+
+ [self logWithFormat:@"ERROR(%s): cannot insert quick '%@': %@",
+ __PRETTY_FUNCTION__, qsql, error];
+
+ delsql = [@"DELETE FROM " stringByAppendingString:[self storeTableName]];
+ delsql = [delsql stringByAppendingString:@" WHERE c_name="];
+ delsql = [delsql stringByAppendingString:[self _formatRowValue:_name]];
+ if ((delErr = [storeChannel evaluateExpressionX:delsql]) != nil) {
+ [self logWithFormat:
+ @"ERROR(%s): cannot delete content '%@' after quick-fail: %@",
+ __PRETTY_FUNCTION__, delsql, error];
+ }
+ }
+ }
+ else { /* update */
+ qsql = [self _generateUpdateStatementForRow:quickRow
+ tableName:[self quickTableName]
+ whereColumn:@"c_name" isEqualTo:_name];
+ bsql = [self _generateUpdateStatementForRow:contentRow
+ tableName:[self storeTableName]
+ whereColumn:@"c_name" isEqualTo:_name];
+ if ((error = [storeChannel evaluateExpressionX:bsql]) != nil) {
+ [self logWithFormat:@"ERROR(%s): cannot update content '%@': %@",
+ __PRETTY_FUNCTION__, bsql, error];
+ }
+ else if ((error = [quickChannel evaluateExpressionX:qsql]) != nil) {
+ [self logWithFormat:@"ERROR(%s): cannot update quick '%@': %@",
+ __PRETTY_FUNCTION__, qsql, error];
+ }
+ }
[self releaseChannel:storeChannel];
[self releaseChannel:quickChannel];
- return [NSException exceptionWithName:@"NotYetImplemented"
- reason:@"no time, no money, ..."
- userInfo:nil];
+ return error;
}
- (NSString *)columnNameForFieldName:(NSString *)_fieldName {
/* SQL generation */
-- (void)_appendAndQualifier:(EOAndQualifier *)_q
- toString:(NSMutableString *)_ms
-{
- NSArray *qs;
- unsigned i, count;
-
- qs = [_q qualifiers];
- if ((count = [qs count]) == 0)
- return;
-
- for (i = 0; i < count; i++) {
- if (i != 0) [_ms appendString:@" AND "];
- if (count > 1) [_ms appendString:@"("];
- [self _appendQualifier:[qs objectAtIndex:i] toString:_ms];
- if (count > 1) [_ms appendString:@")"];
- }
-}
-- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q
- toString:(NSMutableString *)_ms
-{
- id val;
-
- [_ms appendString:[_q key]];
-
- if ((val = [_q value])) {
- SEL op = [_q selector];
-
- if ([val isNotNull]) {
- if (sel_eq(op, EOQualifierOperatorEqual))
- [_ms appendString:@" = "];
- else if (sel_eq(op, EOQualifierOperatorNotEqual))
- [_ms appendString:@" != "];
- else if (sel_eq(op, EOQualifierOperatorLessThan))
- [_ms appendString:@" < "];
- else if (sel_eq(op, EOQualifierOperatorGreaterThan))
- [_ms appendString:@" > "];
- else if (sel_eq(op, EOQualifierOperatorLessThanOrEqualTo))
- [_ms appendString:@" <= "];
- else if (sel_eq(op, EOQualifierOperatorGreaterThanOrEqualTo))
- [_ms appendString:@" >= "];
- else if (sel_eq(op, EOQualifierOperatorLike))
- [_ms appendString:@" LIKE "];
- else {
- [self logWithFormat:@"ERROR(%s): unsupported operation for null: %@",
- __PRETTY_FUNCTION__, NSStringFromSelector(op)];
- }
-
- if ([val isKindOfClass:[NSNumber class]])
- [_ms appendString:[val stringValue]];
- else if ([val isKindOfClass:[NSString class]]) {
- [_ms appendString:@"'"];
- [_ms appendString:val];
- [_ms appendString:@"'"];
- }
- else {
- [self logWithFormat:@"ERROR(%s): unsupported value class: %@",
- __PRETTY_FUNCTION__, NSStringFromClass([val class])];
- }
- }
- else {
- if (sel_eq(op, EOQualifierOperatorEqual))
- [_ms appendString:@" IS NULL"];
- else if (sel_eq(op, EOQualifierOperatorEqual))
- [_ms appendString:@" IS NOT NULL"];
- else {
- [self logWithFormat:@"ERROR(%s): invalid operation for null: %@",
- __PRETTY_FUNCTION__, NSStringFromSelector(op)];
- }
- }
- }
- else
- [_ms appendString:@" IS NULL"];
-}
-- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms {
- if (_q == nil) return;
-
- if ([_q isKindOfClass:[EOAndQualifier class]])
- [self _appendAndQualifier:(id)_q toString:_ms];
- else if ([_q isKindOfClass:[EOKeyValueQualifier class]])
- [self _appendKeyValueQualifier:(id)_q toString:_ms];
- else
- NSLog(@"ERROR: unknown qualifier: %@", _q);
-}
- (NSString *)generateSQLForQualifier:(EOQualifier *)_q {
NSMutableString *ms;
if (_q == nil) return nil;
ms = [NSMutableString stringWithCapacity:32];
- [self _appendQualifier:_q toString:ms];
+ [_q _ocsAppendToString:ms];
return ms;
}