Monotone-Parent: 89b88978ce7a84af4dd6e2e230ede1c3ef700068

Monotone-Revision: c29fcc5ce939d41d4b8a3795a566530292ec2f0f

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2009-03-17T15:58:25
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2009-03-17 15:58:25 +00:00
parent b65ae175c3
commit 57830dbb88
2 changed files with 191 additions and 172 deletions

View File

@ -1,3 +1,11 @@
2009-03-17 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* GCSFolder.m ([GCSFolder
-writeContent:_contenttoName:_namebaseVersion:_baseVersion]):
reorganized method to centralize error management, enabling us to
remove the "CHECKERROR" macro. The method was also not closing
channels whenever an error occured.
2008-09-22 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* GCSFolder.m ([GCSFolder

View File

@ -50,15 +50,6 @@ typedef enum {
bothTableRequired = 3
} GCSTableRequirement;
#define CHECKERROR() \
if (error) { \
[[storeChannel adaptorContext] rollbackTransaction]; \
[[quickChannel adaptorContext] rollbackTransaction]; \
[self logWithFormat:@"ERROR(%s): cannot %s content : %@", \
__PRETTY_FUNCTION__, isNewRecord ? "insert" : "update", error]; \
return error; \
} \
@implementation GCSFolder
static BOOL debugOn = NO;
@ -815,182 +806,202 @@ static NSArray *contentFieldNames = nil;
NSMutableDictionary *quickRow, *contentRow;
NSDictionary *currentRow;
GCSFieldExtractor *extractor;
NSException *error;
NSNumber *storedVersion;
BOOL isNewRecord, hasInsertDelegate, hasUpdateDelegate;
NSCalendarDate *nowDate;
NSNumber *now;
EOEntity *quickTableEntity, *storeTableEntity;
NSArray *rows;
NSException *error;
/* check preconditions */
if (_name == nil) {
return [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content filename was provided"
userInfo:nil];
}
if (_content == nil) {
return [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content was provided"
userInfo:nil];
}
/* run */
error = nil;
nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
rows = [self fetchFields: [NSArray arrayWithObjects:
@"c_version",
@"c_deleted",
nil]
fetchSpecification: [self _simpleFetchSpecificationWith:
@"c_name"
andValue: _name]
ignoreDeleted: NO];
if ([rows count])
if (_name)
{
currentRow = [rows objectAtIndex: 0];
storedVersion = [currentRow objectForKey: @"c_version"];
if (doLogStore)
[self logWithFormat:@" version: %@", storedVersion];
if ([[currentRow objectForKey: @"c_deleted"] intValue] > 0)
if (_content)
{
[self _purgeRecordWithName: _name];
isNewRecord = YES;
}
/* run */
error = nil;
nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
rows = [self fetchFields: [NSArray arrayWithObjects:
@"c_version",
@"c_deleted",
nil]
fetchSpecification: [self _simpleFetchSpecificationWith:
@"c_name"
andValue: _name]
ignoreDeleted: NO];
if ([rows count])
{
currentRow = [rows objectAtIndex: 0];
storedVersion = [currentRow objectForKey: @"c_version"];
if (doLogStore)
[self logWithFormat:@" version: %@", storedVersion];
if ([[currentRow objectForKey: @"c_deleted"] intValue] > 0)
{
[self _purgeRecordWithName: _name];
isNewRecord = YES;
}
else
isNewRecord = NO;
}
else
{
storedVersion = nil;
isNewRecord = YES;
}
/* check whether sequence matches */
/* use version = 0 to override check */
if (_baseVersion == 0
|| _baseVersion == [storedVersion unsignedIntValue])
{
/* extract quick info */
extractor = [folderInfo quickExtractor];
quickRow = [extractor extractQuickFieldsFromContent:_content];
if (quickRow)
{
[quickRow setObject:_name forKey:@"c_name"];
if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow];
/* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow];
[contentRow setObject:_name forKey:@"c_name"];
[contentRow setObject:now forKey:@"c_lastmodified"];
if (isNewRecord)
{
[contentRow setObject:now forKey:@"c_creationdate"];
[contentRow setObject:[NSNumber numberWithInt:0]
forKey:@"c_version"];
}
else // TODO: increase version?
[contentRow setObject:
[NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"];
[contentRow setObject:_content forKey:@"c_content"];
/* open channels */
storeChannel = [self acquireStoreChannel];
if (storeChannel)
{
if (!ofFlags.sameTableForQuick)
{
quickChannel = [self acquireQuickChannel];
if (!quickChannel)
{
[self errorWithFormat:@"%s: could not open quick channel!",
__PRETTY_FUNCTION__];
[self releaseChannel:storeChannel];
return nil;
}
}
/* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll
be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[quickChannel adaptorContext] beginTransaction];
[[storeChannel adaptorContext] beginTransaction];
quickTableEntity = [self _quickTableEntity];
storeTableEntity = [self _storeTableEntity];
if (isNewRecord)
{
if (!ofFlags.sameTableForQuick)
error = (hasInsertDelegate
? [quickChannel insertRowX: quickRow forEntity: quickTableEntity]
: [quickChannel
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
tableName: [self quickTableName]]]);
if (!error)
error = (hasInsertDelegate
? [storeChannel insertRowX: contentRow forEntity: storeTableEntity]
: [storeChannel
evaluateExpressionX: [self _generateInsertStatementForRow: contentRow
tableName: [self storeTableName]]]);
}
else
{
if (!ofFlags.sameTableForQuick)
error = (hasUpdateDelegate
? [quickChannel updateRowX: quickRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name"
isEqualTo: _name andColumn: nil isEqualTo: nil
entity: quickTableEntity]]
: [quickChannel evaluateExpressionX: [self _generateUpdateStatementForRow: quickRow
tableName: [self quickTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: nil isEqualTo: nil]]);
if (!error)
error = (hasUpdateDelegate
? [storeChannel updateRowX: contentRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt:_baseVersion] : (NSNumber *)nil)
entity: storeTableEntity]]
: [storeChannel evaluateExpressionX: [self _generateUpdateStatementForRow: contentRow tableName:[self storeTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: _baseVersion] : (NSNumber *)nil)]]);
}
if (error)
{
[[storeChannel adaptorContext] rollbackTransaction];
[[quickChannel adaptorContext] rollbackTransaction];
[self logWithFormat:
@"ERROR(%s): cannot %s content : %@", __PRETTY_FUNCTION__,
isNewRecord ? "insert" : "update",
error];
}
else
{
[[storeChannel adaptorContext] commitTransaction];
[[quickChannel adaptorContext] commitTransaction];
}
[self releaseChannel: storeChannel];
if (!ofFlags.sameTableForQuick)
[self releaseChannel: quickChannel];
}
else
[self errorWithFormat:@"%s: could not open storage channel!",
__PRETTY_FUNCTION__];
}
else
error = [self errorExtractorReturnedNoQuickRow:extractor
forContent:_content];
}
else /* version mismatch (concurrent update) */
error = [self errorVersionMismatchBetweenStoredVersion:
[storedVersion unsignedIntValue]
andExpectedVersion: _baseVersion];
}
else
isNewRecord = NO;
error = [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content was provided"
userInfo:nil];
}
else
{
storedVersion = nil;
isNewRecord = YES;
}
/* check whether sequence matches */
if (_baseVersion != 0 /* use 0 to override check */) {
if (_baseVersion != [storedVersion unsignedIntValue]) {
/* version mismatch (concurrent update) */
return [self errorVersionMismatchBetweenStoredVersion:
[storedVersion unsignedIntValue]
andExpectedVersion:_baseVersion];
}
}
/* extract quick info */
extractor = [folderInfo quickExtractor];
if ((quickRow = [extractor extractQuickFieldsFromContent:_content]) == nil) {
return [self errorExtractorReturnedNoQuickRow:extractor
forContent:_content];
}
[quickRow setObject:_name forKey:@"c_name"];
if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow];
/* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow];
[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 {
// TODO: increase version?
[contentRow setObject:
[NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"];
}
[contentRow setObject:_content forKey:@"c_content"];
/* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"%s: could not open storage channel!",
__PRETTY_FUNCTION__];
return nil;
}
if (!ofFlags.sameTableForQuick) {
if ((quickChannel = [self acquireQuickChannel]) == nil) {
[self errorWithFormat:@"%s: could not open quick channel!",
__PRETTY_FUNCTION__];
[self releaseChannel:storeChannel];
return nil;
}
}
/* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll
be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[quickChannel adaptorContext] beginTransaction];
[[storeChannel adaptorContext] beginTransaction];
quickTableEntity = [self _quickTableEntity];
storeTableEntity = [self _storeTableEntity];
if (isNewRecord) {
if (!ofFlags.sameTableForQuick) {
error = (hasInsertDelegate
? [quickChannel insertRowX: quickRow forEntity: quickTableEntity]
: [quickChannel
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
tableName: [self quickTableName]]]);
CHECKERROR();
}
error = (hasInsertDelegate
? [storeChannel insertRowX: contentRow forEntity: storeTableEntity]
: [storeChannel
evaluateExpressionX: [self _generateInsertStatementForRow: contentRow
tableName: [self storeTableName]]]);
CHECKERROR();
}
else {
if (!ofFlags.sameTableForQuick) {
error = (hasUpdateDelegate
? [quickChannel updateRowX: quickRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name"
isEqualTo: _name andColumn: nil isEqualTo: nil
entity: quickTableEntity]]
: [quickChannel evaluateExpressionX: [self _generateUpdateStatementForRow: quickRow
tableName: [self quickTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: nil isEqualTo: nil]]);
CHECKERROR();
}
error = (hasUpdateDelegate
? [storeChannel updateRowX: contentRow
describedByQualifier: [self _qualifierUsingWhereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt:_baseVersion] : (NSNumber *)nil)
entity: storeTableEntity]]
: [storeChannel evaluateExpressionX: [self _generateUpdateStatementForRow: contentRow tableName:[self storeTableName]
whereColumn: @"c_name" isEqualTo: _name
andColumn: (_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: _baseVersion] : (NSNumber *)nil)]]);
CHECKERROR();
}
[[storeChannel adaptorContext] commitTransaction];
[[quickChannel adaptorContext] commitTransaction];
[self releaseChannel: storeChannel];
if (!ofFlags.sameTableForQuick) [self releaseChannel: quickChannel];
error = [NSException exceptionWithName:@"GCSStoreException"
reason:@"no content filename was provided"
userInfo:nil];
return error;
}