New sogo-tool cleanup user feature

pull/228/head
Francis Lachapelle 2016-11-30 15:32:29 -05:00
parent 52cdb71d1f
commit 7705c11cf7
5 changed files with 460 additions and 69 deletions

3
NEWS
View File

@ -1,6 +1,9 @@
3.2.4 (2016-12-DD) 3.2.4 (2016-12-DD)
------------------ ------------------
New features
- [core] new sogo-tool cleanup user feature
Enhancements Enhancements
- [core] added handling of BYSETPOS for BYDAY in recurrence rules - [core] added handling of BYSETPOS for BYDAY in recurrence rules
- [web] added sort by start date for tasks (#3840) - [web] added sort by start date for tasks (#3840)

View File

@ -123,6 +123,7 @@
- (NSException *) deleteContentWithName: (NSString *) _name; - (NSException *) deleteContentWithName: (NSString *) _name;
- (NSException *) deleteAllContent; - (NSException *) deleteAllContent;
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days;
- (NSException *) touchContentWithName: (NSString *) _name; - (NSException *) touchContentWithName: (NSString *) _name;
- (NSException *) deleteFolder; - (NSException *) deleteFolder;
@ -144,6 +145,7 @@
- (void) deleteAclWithSpecification: (EOFetchSpecification *) _fs; - (void) deleteAclWithSpecification: (EOFetchSpecification *) _fs;
- (unsigned int) recordsCountByExcludingDeleted: (BOOL) includeDeleted; - (unsigned int) recordsCountByExcludingDeleted: (BOOL) includeDeleted;
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days;
- (NSCalendarDate *) lastModificationDate; - (NSCalendarDate *) lastModificationDate;

View File

@ -68,7 +68,7 @@ static GCSStringFormatter *stringFormatter = nil;
+ (void) initialize + (void) initialize
{ {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"]; debugOn = [ud boolForKey:@"GCSFolderDebugEnabled"];
doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"]; doLogStore = [ud boolForKey:@"GCSFolderStoreDebugEnabled"];
@ -91,7 +91,7 @@ static GCSStringFormatter *stringFormatter = nil;
NSEnumerator *fields; NSEnumerator *fields;
GCSFieldInfo *field; GCSFieldInfo *field;
NSString *fieldName; NSString *fieldName;
if ((self = [super init])) { if ((self = [super init])) {
folderManager = [_fm retain]; folderManager = [_fm retain];
folderInfo = [_ftype retain]; folderInfo = [_ftype retain];
@ -114,7 +114,7 @@ static GCSStringFormatter *stringFormatter = nil;
if (![fieldName isEqualToString: @"c_name"]) if (![fieldName isEqualToString: @"c_name"])
[contentFieldNames addObject: fieldName]; [contentFieldNames addObject: fieldName];
} }
folderId = [_folderId copy]; folderId = [_folderId copy];
folderName = [[_path lastPathComponent] copy]; folderName = [[_path lastPathComponent] copy];
path = [_path copy]; path = [_path copy];
@ -124,7 +124,7 @@ static GCSStringFormatter *stringFormatter = nil;
folderTypeName = [_ftname copy]; folderTypeName = [_ftname copy];
ofFlags.requiresFolderSelect = 0; ofFlags.requiresFolderSelect = 0;
ofFlags.sameTableForQuick = ofFlags.sameTableForQuick =
[location isEqualTo:quickLocation] ? 1 : 0; [location isEqualTo:quickLocation] ? 1 : 0;
} }
return self; return self;
@ -133,7 +133,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (id) init - (id) init
{ {
return [self initWithPath:nil primaryKey:nil return [self initWithPath:nil primaryKey:nil
folderTypeName:nil folderType:nil folderTypeName:nil folderType:nil
location:nil quickLocation:nil location:nil quickLocation:nil
aclLocation:nil aclLocation:nil
folderManager:nil]; folderManager:nil];
@ -271,13 +271,13 @@ static GCSStringFormatter *stringFormatter = nil;
NSDictionary *ui; NSDictionary *ui;
ui = [NSDictionary dictionaryWithObjectsAndKeys: ui = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:_base], [NSNumber numberWithUnsignedInt:_base],
@"GCSExpectedVersion", @"GCSExpectedVersion",
[NSNumber numberWithUnsignedInt:_store], [NSNumber numberWithUnsignedInt:_store],
@"GCSStoredVersion", @"GCSStoredVersion",
self, @"GCSFolder", self, @"GCSFolder",
nil]; nil];
return [NSException exceptionWithName:@"GCSVersionMismatch" return [NSException exceptionWithName:@"GCSVersionMismatch"
reason:@"Transaction conflict during a GCS modification." reason:@"Transaction conflict during a GCS modification."
userInfo:ui]; userInfo:ui];
@ -369,7 +369,7 @@ static GCSStringFormatter *stringFormatter = nil;
requirement: (GCSTableRequirement) requirement requirement: (GCSTableRequirement) requirement
{ {
NSString *selectedFields; NSString *selectedFields;
if (requirement == bothTableRequired && [fields containsObject: @"c_name"]) if (requirement == bothTableRequired && [fields containsObject: @"c_name"])
selectedFields = [self _dottedFields: fields]; selectedFields = [self _dottedFields: fields];
else else
@ -381,7 +381,7 @@ static GCSStringFormatter *stringFormatter = nil;
- (NSString *) _sqlForQualifier: (EOQualifier *) qualifier - (NSString *) _sqlForQualifier: (EOQualifier *) qualifier
{ {
NSMutableString *ms; NSMutableString *ms;
if (qualifier) if (qualifier)
{ {
ms = [NSMutableString stringWithCapacity:32]; ms = [NSMutableString stringWithCapacity:32];
@ -399,7 +399,7 @@ static GCSStringFormatter *stringFormatter = nil;
if ((count = [_so count]) == 0) if ((count = [_so count]) == 0)
return nil; return nil;
sql = [NSMutableString stringWithCapacity:(count * 16)]; sql = [NSMutableString stringWithCapacity:(count * 16)];
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
EOSortOrdering *so; EOSortOrdering *so;
@ -409,9 +409,9 @@ static GCSStringFormatter *stringFormatter = nil;
so = [_so objectAtIndex:i]; so = [_so objectAtIndex:i];
sel = [so selector]; sel = [so selector];
column = [so key]; column = [so key];
if (i > 0) [sql appendString:@", "]; if (i > 0) [sql appendString:@", "];
if (sel_eq(sel, EOCompareAscending)) { if (sel_eq(sel, EOCompareAscending)) {
[sql appendString:column]; [sql appendString:column];
[sql appendString:@" ASC"]; [sql appendString:@" ASC"];
@ -558,21 +558,21 @@ static GCSStringFormatter *stringFormatter = nil;
error = [channel evaluateExpressionX:sql]; error = [channel evaluateExpressionX:sql];
if (error) if (error)
{ {
[self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@", [self errorWithFormat:@"%s: cannot execute quick-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error]; __PRETTY_FUNCTION__, sql, error];
results = nil; results = nil;
} }
else else
{ {
/* fetch results */ /* fetch results */
results = [NSMutableArray arrayWithCapacity: 64]; results = [NSMutableArray arrayWithCapacity: 64];
attrs = [channel describeResults: NO /* do not beautify names */]; attrs = [channel describeResults: NO /* do not beautify names */];
while ((row = [channel fetchAttributes: attrs withZone: NULL])) while ((row = [channel fetchAttributes: attrs withZone: NULL]))
[results addObject: row]; [results addObject: row];
/* release channels */ /* release channels */
} }
[self releaseChannel: channel]; [self releaseChannel: channel];
// NSLog(@"/running query"); // NSLog(@"/running query");
@ -582,7 +582,7 @@ static GCSStringFormatter *stringFormatter = nil;
[self errorWithFormat:@" could not open storage channel!"]; [self errorWithFormat:@" could not open storage channel!"];
results = nil; results = nil;
} }
return results; return results;
} }
@ -764,7 +764,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute; EOAttribute *attribute;
id value; id value;
unsigned i, count; unsigned i, count;
if (_row == nil || _table == nil) if (_row == nil || _table == nil)
return nil; return nil;
@ -774,7 +774,7 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@"UPDATE "]; [sql appendString:@"UPDATE "];
[sql appendString:_table]; [sql appendString:_table];
[sql appendString:@" SET "]; [sql appendString:@" SET "];
for (i = 0, count = [keys count]; i < count; i++) { for (i = 0, count = [keys count]; i < count; i++) {
fieldName = [keys objectAtIndex:i]; fieldName = [keys objectAtIndex:i];
@ -823,7 +823,7 @@ andAttribute: (EOAttribute *)_attribute
{ {
EOAttribute *attribute; EOAttribute *attribute;
EOEntity *entity; EOEntity *entity;
entity = AUTORELEASE([[EOEntity alloc] init]); entity = AUTORELEASE([[EOEntity alloc] init]);
[entity setName: _name]; [entity setName: _name];
[entity setExternalName: _name]; [entity setExternalName: _name];
@ -841,7 +841,7 @@ andAttribute: (EOAttribute *)_attribute
EOAttribute *attribute; EOAttribute *attribute;
EOEntity *entity; EOEntity *entity;
entity = [self _entityWithName: [self storeTableName]]; entity = [self _entityWithName: [self storeTableName]];
attribute = AUTORELEASE([[EOAttribute alloc] init]); attribute = AUTORELEASE([[EOAttribute alloc] init]);
[attribute setName: @"c_version"]; [attribute setName: @"c_version"];
@ -873,7 +873,7 @@ andAttribute: (EOAttribute *)_attribute
- (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname - (EOSQLQualifier *) _qualifierUsingWhereColumn:(NSString *)_colname
isEqualTo:(id)_value isEqualTo:(id)_value
andColumn:(NSString *)_colname2 andColumn:(NSString *)_colname2
isEqualTo:(id)_value2 isEqualTo:(id)_value2
entity: (EOEntity *)_entity entity: (EOEntity *)_entity
withAdaptor: (EOAdaptor *)_adaptor withAdaptor: (EOAdaptor *)_adaptor
@ -970,7 +970,7 @@ andAttribute: (EOAttribute *)_attribute
NSArray *rows; NSArray *rows;
NSException *error; NSException *error;
/* check preconditions */ /* check preconditions */
if (_name) if (_name)
{ {
if (_content) if (_content)
@ -979,10 +979,10 @@ andAttribute: (EOAttribute *)_attribute
error = nil; error = nil;
nowDate = [NSCalendarDate date]; nowDate = [NSCalendarDate date];
now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]]; now = [NSNumber numberWithUnsignedInt:[nowDate timeIntervalSince1970]];
if (doLogStore) if (doLogStore)
[self logWithFormat:@"should store content: '%@'\n%@", _name, _content]; [self logWithFormat:@"should store content: '%@'\n%@", _name, _content];
rows = [self fetchFields: [NSArray arrayWithObjects: rows = [self fetchFields: [NSArray arrayWithObjects:
@"c_version", @"c_version",
@"c_deleted", @"c_deleted",
@ -1035,16 +1035,16 @@ andAttribute: (EOAttribute *)_attribute
if (quickRow) if (quickRow)
{ {
[quickRow setObject:_name forKey:@"c_name"]; [quickRow setObject:_name forKey:@"c_name"];
if (doLogStore) if (doLogStore)
[self logWithFormat:@" store quick: %@", quickRow]; [self logWithFormat:@" store quick: %@", quickRow];
/* make content row */ /* make content row */
contentRow = [NSMutableDictionary dictionaryWithCapacity:16]; contentRow = [NSMutableDictionary dictionaryWithCapacity:16];
if (ofFlags.sameTableForQuick) if (ofFlags.sameTableForQuick)
[contentRow addEntriesFromDictionary:quickRow]; [contentRow addEntriesFromDictionary:quickRow];
[contentRow setObject:_name forKey:@"c_name"]; [contentRow setObject:_name forKey:@"c_name"];
[contentRow setObject:now forKey:@"c_lastmodified"]; [contentRow setObject:now forKey:@"c_lastmodified"];
if (isNewRecord) if (isNewRecord)
@ -1058,7 +1058,7 @@ andAttribute: (EOAttribute *)_attribute
[NSNumber numberWithInt:([storedVersion intValue] + 1)] [NSNumber numberWithInt:([storedVersion intValue] + 1)]
forKey:@"c_version"]; forKey:@"c_version"];
[contentRow setObject:_content forKey:@"c_content"]; [contentRow setObject:_content forKey:@"c_content"];
/* open channels */ /* open channels */
storeChannel = [self acquireStoreChannel]; storeChannel = [self acquireStoreChannel];
if (storeChannel) if (storeChannel)
@ -1080,14 +1080,14 @@ andAttribute: (EOAttribute *)_attribute
/* we check if we can call directly methods on our adaptor /* we check if we can call directly methods on our adaptor
channel delegate. If not, we generate SQL ourself since it'll 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 */ be a little bit faster and less complex than using GDL to do so */
hasInsertDelegate = [[storeChannel delegate] hasInsertDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)]; respondsToSelector: @selector(adaptorChannel:willInsertRow:forEntity:)];
hasUpdateDelegate = [[storeChannel delegate] hasUpdateDelegate = [[storeChannel delegate]
respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)]; respondsToSelector: @selector(adaptorChannel:willUpdateRow:describedByQualifier:)];
[[quickChannel adaptorContext] beginTransaction]; [[quickChannel adaptorContext] beginTransaction];
[[storeChannel adaptorContext] beginTransaction]; [[storeChannel adaptorContext] beginTransaction];
quickTableEntity = [self _quickTableEntity]; quickTableEntity = [self _quickTableEntity];
storeTableEntity = [self _storeTableEntity]; storeTableEntity = [self _storeTableEntity];
@ -1097,10 +1097,10 @@ andAttribute: (EOAttribute *)_attribute
error = (hasInsertDelegate error = (hasInsertDelegate
? [quickChannel insertRowX: quickRow forEntity: quickTableEntity] ? [quickChannel insertRowX: quickRow forEntity: quickTableEntity]
: [quickChannel : [quickChannel
evaluateExpressionX: [self _generateInsertStatementForRow: quickRow evaluateExpressionX: [self _generateInsertStatementForRow: quickRow
adaptor: [[quickChannel adaptorContext] adaptor] adaptor: [[quickChannel adaptorContext] adaptor]
tableName: [self quickTableName]]]); tableName: [self quickTableName]]]);
if (!error) if (!error)
error = (hasInsertDelegate error = (hasInsertDelegate
? [storeChannel insertRowX: contentRow forEntity: storeTableEntity] ? [storeChannel insertRowX: contentRow forEntity: storeTableEntity]
@ -1138,7 +1138,7 @@ andAttribute: (EOAttribute *)_attribute
andColumn: (*_baseVersion != 0 ? (id)@"c_version" : (id)nil) andColumn: (*_baseVersion != 0 ? (id)@"c_version" : (id)nil)
isEqualTo: (*_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: *_baseVersion] : (NSNumber *)nil)]]); isEqualTo: (*_baseVersion != 0 ? [NSNumber numberWithUnsignedInt: *_baseVersion] : (NSNumber *)nil)]]);
} }
if (error) if (error)
{ {
[[storeChannel adaptorContext] rollbackTransaction]; [[storeChannel adaptorContext] rollbackTransaction];
@ -1193,19 +1193,19 @@ andAttribute: (EOAttribute *)_attribute
NSException *error; NSException *error;
NSString *delsql; NSString *delsql;
NSCalendarDate *nowDate; NSCalendarDate *nowDate;
/* check preconditions */ /* check preconditions */
if (_name == nil) { if (_name == nil) {
return [NSException exceptionWithName:@"GCSDeleteException" return [NSException exceptionWithName:@"GCSDeleteException"
reason:@"no content filename was provided" reason:@"no content filename was provided"
userInfo:nil]; userInfo:nil];
} }
if (doLogStore) if (doLogStore)
[self logWithFormat:@"should delete content: '%@'", _name]; [self logWithFormat:@"should delete content: '%@'", _name];
/* open channels */ /* open channels */
if ((storeChannel = [self acquireStoreChannel]) == nil) { if ((storeChannel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open storage channel!"]; [self errorWithFormat:@"could not open storage channel!"];
return nil; return nil;
@ -1242,7 +1242,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId]; delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) { if ((error = [storeChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat: [self errorWithFormat:
@"%s: cannot delete content '%@': %@", @"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, delsql, error]; __PRETTY_FUNCTION__, delsql, error];
} }
else if (!ofFlags.sameTableForQuick) { else if (!ofFlags.sameTableForQuick) {
@ -1256,19 +1256,19 @@ andAttribute: (EOAttribute *)_attribute
delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId]; delsql = [delsql stringByAppendingFormat:@" AND c_folder_id = %@", folderId];
if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) { if ((error = [quickChannel evaluateExpressionX:delsql]) != nil) {
[self errorWithFormat: [self errorWithFormat:
@"%s: cannot delete quick row '%@': %@", @"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, delsql, error]; __PRETTY_FUNCTION__, delsql, error];
/* /*
Note: we now have a "broken" record, needs to be periodically GCed by Note: we now have a "broken" record, needs to be periodically GCed by
a script! a script!
*/ */
} }
} }
/* release channels and return */ /* release channels and return */
[adaptorCtx commitTransaction]; [adaptorCtx commitTransaction];
[self releaseChannel:storeChannel]; [self releaseChannel:storeChannel];
if (!ofFlags.sameTableForQuick) { if (!ofFlags.sameTableForQuick) {
[[quickChannel adaptorContext] commitTransaction]; [[quickChannel adaptorContext] commitTransaction];
[self releaseChannel:quickChannel]; [self releaseChannel:quickChannel];
@ -1307,7 +1307,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self storeTableName]]; query = [NSString stringWithFormat: @"DELETE FROM %@", [self storeTableName]];
error = [storeChannel evaluateExpressionX:query]; error = [storeChannel evaluateExpressionX:query];
if (error) if (error)
[self errorWithFormat: @"%s: cannot delete content '%@': %@", [self errorWithFormat: @"%s: cannot delete content '%@': %@",
__PRETTY_FUNCTION__, query, error]; __PRETTY_FUNCTION__, query, error];
else if (!ofFlags.sameTableForQuick) { else if (!ofFlags.sameTableForQuick) {
/* content row deleted, now delete the quick row */ /* content row deleted, now delete the quick row */
@ -1317,7 +1317,7 @@ andAttribute: (EOAttribute *)_attribute
query = [NSString stringWithFormat: @"DELETE FROM %@", [self quickTableName]]; query = [NSString stringWithFormat: @"DELETE FROM %@", [self quickTableName]];
error = [quickChannel evaluateExpressionX: query]; error = [quickChannel evaluateExpressionX: query];
if (error) if (error)
[self errorWithFormat: @"%s: cannot delete quick row '%@': %@", [self errorWithFormat: @"%s: cannot delete quick row '%@': %@",
__PRETTY_FUNCTION__, query, error]; __PRETTY_FUNCTION__, query, error];
} }
@ -1333,6 +1333,49 @@ andAttribute: (EOAttribute *)_attribute
return error; return error;
} }
- (NSException *) purgeDeletedRecordsBefore: (unsigned int) days
{
NSString *delSql, *table;
EOAdaptorContext *adaptorCtx;
EOAdaptorChannel *channel;
NSCalendarDate *nowDate;
unsigned int delta;
channel = [self acquireStoreChannel];
if ((channel = [self acquireStoreChannel]) == nil)
return [NSException exceptionWithName: @"GCSChannelException"
reason: @"could not open storage channel!"
userInfo: nil];
adaptorCtx = [channel adaptorContext];
[adaptorCtx beginTransaction];
table = [self storeTableName];
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta > [nowDate timeIntervalSince1970])
return [NSException exceptionWithName: @"GCSArgumentException"
reason: @"number of days is too high!"
userInfo: nil];
delta = [nowDate timeIntervalSince1970] - delta;
if ([GCSFolderManager singleStoreMode])
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_folder_id = %@ AND c_deleted = 1 AND c_lastmodified < %u",
table, folderId, delta];
else
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
@" WHERE c_deleted = 1 AND c_lastmodified < %u",
table, delta];
[channel evaluateExpressionX: delSql];
[[channel adaptorContext] commitTransaction];
[self releaseChannel: channel];
return nil;
}
- (NSException *) touchContentWithName: (NSString *) _name - (NSException *) touchContentWithName: (NSString *) _name
{ {
NSString *touchSql, *table; NSString *touchSql, *table;
@ -1389,14 +1432,14 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *channel; EOAdaptorChannel *channel;
NSString *delsql; NSString *delsql;
NSString *table; NSString *table;
/* open channels */ /* open channels */
if ((channel = [self acquireStoreChannel]) == nil) { if ((channel = [self acquireStoreChannel]) == nil) {
[self errorWithFormat:@"could not open channel!"]; [self errorWithFormat:@"could not open channel!"];
return nil; return nil;
} }
/* delete rows */ /* delete rows */
[[channel adaptorContext] beginTransaction]; [[channel adaptorContext] beginTransaction];
table = [self storeTableName]; table = [self storeTableName];
@ -1423,7 +1466,7 @@ andAttribute: (EOAttribute *)_attribute
delsql = [@"DROP TABLE " stringByAppendingString: table]; delsql = [@"DROP TABLE " stringByAppendingString: table];
[channel evaluateExpressionX:delsql]; [channel evaluateExpressionX:delsql];
} }
[[channel adaptorContext] commitTransaction]; [[channel adaptorContext] commitTransaction];
[self releaseChannel:channel]; [self releaseChannel:channel];
@ -1437,7 +1480,7 @@ andAttribute: (EOAttribute *)_attribute
EOAdaptorChannel *quickChannel; EOAdaptorChannel *quickChannel;
EOAdaptorContext *adaptorCtx; EOAdaptorContext *adaptorCtx;
NSException *error; NSException *error;
quickChannel = [self acquireQuickChannel]; quickChannel = [self acquireQuickChannel];
adaptorCtx = [quickChannel adaptorContext]; adaptorCtx = [quickChannel adaptorContext];
[adaptorCtx beginTransaction]; [adaptorCtx beginTransaction];
@ -1500,15 +1543,15 @@ andAttribute: (EOAttribute *)_attribute
NSArray *attrs; NSArray *attrs;
NSMutableArray *results; NSMutableArray *results;
NSDictionary *row; NSDictionary *row;
qualifier = [_fs qualifier]; qualifier = [_fs qualifier];
sortOrderings = [_fs sortOrderings]; sortOrderings = [_fs sortOrderings];
#if 0 #if 0
[self logWithFormat:@"FETCH: %@", _flds]; [self logWithFormat:@"FETCH: %@", _flds];
[self logWithFormat:@" MATCH: %@", _q]; [self logWithFormat:@" MATCH: %@", _q];
#endif #endif
/* generate SQL */ /* generate SQL */
sql = [NSMutableString stringWithCapacity:256]; sql = [NSMutableString stringWithCapacity:256];
@ -1536,34 +1579,34 @@ andAttribute: (EOAttribute *)_attribute
[sql appendString:@" LIMIT "]; // count [sql appendString:@" LIMIT "]; // count
[sql appendString:@" OFFSET "]; // index from 0 [sql appendString:@" OFFSET "]; // index from 0
#endif #endif
/* open channel */ /* open channel */
if ((channel = [self acquireAclChannel]) == nil) { if ((channel = [self acquireAclChannel]) == nil) {
[self errorWithFormat:@"could not open acl channel!"]; [self errorWithFormat:@"could not open acl channel!"];
return nil; return nil;
} }
/* run SQL */ /* run SQL */
if ((error = [channel evaluateExpressionX:sql]) != nil) { if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error]; __PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel]; [self releaseChannel:channel];
return nil; return nil;
} }
/* fetch results */ /* fetch results */
results = [NSMutableArray arrayWithCapacity:64]; results = [NSMutableArray arrayWithCapacity:64];
attrs = [channel describeResults:NO /* do not beautify names */]; attrs = [channel describeResults:NO /* do not beautify names */];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[results addObject:row]; [results addObject:row];
/* release channels */ /* release channels */
[self releaseChannel: channel]; [self releaseChannel: channel];
return results; return results;
} }
@ -1602,7 +1645,7 @@ andAttribute: (EOAttribute *)_attribute
NSException *error; NSException *error;
NSMutableString *sql; NSMutableString *sql;
NSString *qSql; NSString *qSql;
sql = [NSMutableString stringWithCapacity:256]; sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"DELETE FROM "]; [sql appendString:@"DELETE FROM "];
[sql appendString:[self aclTableName]]; [sql appendString:[self aclTableName]];
@ -1620,16 +1663,16 @@ andAttribute: (EOAttribute *)_attribute
[self errorWithFormat:@"could not open acl channel!"]; [self errorWithFormat:@"could not open acl channel!"];
return; return;
} }
/* run SQL */ /* run SQL */
[[channel adaptorContext] beginTransaction]; [[channel adaptorContext] beginTransaction];
if ((error = [channel evaluateExpressionX:sql]) != nil) { if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@", [self errorWithFormat:@"%s: cannot execute acl-fetch SQL '%@': %@",
__PRETTY_FUNCTION__, sql, error]; __PRETTY_FUNCTION__, sql, error];
[self releaseChannel:channel]; [self releaseChannel:channel];
return; return;
} }
[[channel adaptorContext] commitTransaction]; [[channel adaptorContext] commitTransaction];
[self releaseChannel:channel]; [self releaseChannel:channel];
} }
@ -1666,13 +1709,62 @@ andAttribute: (EOAttribute *)_attribute
{ {
error = [channel evaluateExpressionX: sqlString]; error = [channel evaluateExpressionX: sqlString];
if (error) if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@", [self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error]; __PRETTY_FUNCTION__, sqlString, error];
else else
{ {
attrs = [channel describeResults: NO]; attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL]; row = [channel fetchAttributes: attrs withZone: NULL];
count = [[row objectForKey: @"cnt"] unsignedIntValue]; count = [[row objectForKey: @"CNT"] unsignedIntValue];
[channel cancelFetch];
}
[self releaseChannel: channel];
}
return count;
}
- (unsigned int) recordsCountDeletedBefore: (unsigned int) days
{
EOAdaptorChannel *channel;
NSArray *attrs;
NSCalendarDate *nowDate;
NSDictionary *row;
NSException *error;
NSMutableString *sqlString;
unsigned int delta, count;
count = 0;
nowDate = [NSCalendarDate date];
delta = days * 24 * 60 * 60;
if (delta < [nowDate timeIntervalSince1970])
delta = [nowDate timeIntervalSince1970] - delta;
else
delta = 0;
if ([GCSFolderManager singleStoreMode])
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@"
@" WHERE c_folder_id = %@ AND c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], folderId, delta];
else
sqlString = [NSMutableString stringWithFormat:
@"SELECT COUNT(*) AS CNT FROM %@"
@" WHERE c_lastmodified < %u AND c_deleted = 1",
[self storeTableName], delta];
channel = [self acquireStoreChannel];
if (channel)
{
error = [channel evaluateExpressionX: sqlString];
if (error)
[self errorWithFormat: @"%s: cannot execute SQL '%@': %@",
__PRETTY_FUNCTION__, sqlString, error];
else
{
attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL];
count = [[row objectForKey: @"CNT"] unsignedIntValue];
[channel cancelFetch]; [channel cancelFetch];
} }
[self releaseChannel: channel]; [self releaseChannel: channel];
@ -1716,7 +1808,7 @@ andAttribute: (EOAttribute *)_attribute
- (NSString *)description { - (NSString *)description {
NSMutableString *ms; NSMutableString *ms;
id tmp; id tmp;
ms = [NSMutableString stringWithCapacity:256]; ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])]; [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
@ -1729,7 +1821,7 @@ andAttribute: (EOAttribute *)_attribute
if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp]; if ((tmp = [self folderTypeName])) [ms appendFormat:@" type=%@", tmp];
if ((tmp = [self location])) if ((tmp = [self location]))
[ms appendFormat:@" loc=%@", [tmp absoluteString]]; [ms appendFormat:@" loc=%@", [tmp absoluteString]];
[ms appendString:@">"]; [ms appendString:@">"];
return ms; return ms;
} }

View File

@ -20,6 +20,7 @@ $(SOGO_TOOL)_OBJC_FILES += \
SOGoToolRemove.m \ SOGoToolRemove.m \
SOGoToolRemoveDoubles.m \ SOGoToolRemoveDoubles.m \
SOGoToolRenameUser.m \ SOGoToolRenameUser.m \
SOGoToolCleanupUser.m \
SOGoToolRestore.m \ SOGoToolRestore.m \
SOGoToolCreateFolder.m \ SOGoToolCreateFolder.m \
SOGoToolUserPreferences.m \ SOGoToolUserPreferences.m \

View File

@ -0,0 +1,293 @@
/* SOGoToolCleanup.m - this file is part of SOGo
*
* Copyright (C) 2016 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
#import <GDLAccess/EOAdaptorChannel.h>
#import <GDLContentStore/GCSChannelManager.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <GDLContentStore/GCSFolder.h>
#import <GDLContentStore/NSURL+GCS.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoSystemDefaults.h>
#import "SOGoTool.h"
@interface SOGoToolCleanup : SOGoTool
{
NSArray *usersToCleanup;
unsigned int days;
}
@end
@implementation SOGoToolCleanup
+ (NSString *) command
{
return @"cleanup";
}
+ (NSString *) description
{
return @"cleanup deleted elements of user(s)";
}
- (id) init
{
if ((self = [super init]))
{
usersToCleanup = nil;
days = 0;
}
return self;
}
- (void) dealloc
{
[usersToCleanup release];
[super dealloc];
}
- (void) usage
{
fprintf (stderr, "cleanup [days] [user]...\n\n"
" days the age of deleted records to purge in days\n"
" user the user to purge the records or ALL for everybody\n\n"
"Example: sogo-tool cleanup jdoe\n");
}
- (BOOL) fetchUserIDs: (NSArray *) users
{
NSAutoreleasePool *pool;
SOGoUserManager *lm;
NSDictionary *infos;
NSString *user;
id allUsers;
int count, max;
lm = [SOGoUserManager sharedUserManager];
pool = [[NSAutoreleasePool alloc] init];
max = [users count];
user = [users objectAtIndex: 0];
if (max == 1 && [user isEqualToString: @"ALL"])
{
GCSFolderManager *fm;
GCSChannelManager *cm;
NSURL *folderLocation;
EOAdaptorChannel *fc;
NSArray *attrs;
NSMutableArray *allSqlUsers;
NSString *sql;
fm = [GCSFolderManager defaultFolderManager];
cm = [fm channelManager];
folderLocation = [fm folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
allSqlUsers = [NSMutableArray new];
sql = [NSString stringWithFormat: @"SELECT DISTINCT c_path2 FROM %@",
[folderLocation gcsTableName]];
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
while ((infos = [fc fetchAttributes: attrs withZone: NULL]))
{
user = [infos objectForKey: @"c_path2"];
if (user)
[allSqlUsers addObject: user];
}
[cm releaseChannel: fc];
users = allSqlUsers;
max = [users count];
}
}
allUsers = [NSMutableArray new];
for (count = 0; count < max; count++)
{
if (count > 0 && count%100 == 0)
{
DESTROY(pool);
pool = [[NSAutoreleasePool alloc] init];
}
user = [users objectAtIndex: count];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
{
// We haven't found the user based on the GCS table name
// Let's try to strip the domain part and search again.
// This can happen when using SOGoEnableDomainBasedUID (YES)
// but login in SOGo using a UID without domain (DomainLessLogin gets set)
NSRange r;
r = [user rangeOfString: @"@"];
if (r.location != NSNotFound)
{
user = [user substringToIndex: r.location];
infos = [lm contactInfosForUserWithUIDorEmail: user];
if (infos)
[allUsers addObject: infos];
else
NSLog (@"user '%@' unknown", user);
}
else
NSLog (@"user '%@' unknown", user);
}
}
[allUsers autorelease];
ASSIGN (usersToCleanup, allUsers);
DESTROY(pool);
return ([usersToCleanup count] > 0);
}
- (BOOL) parseArguments
{
BOOL rc;
NSRange usersRange;
int max;
max = [arguments count];
if (max > 1)
{
days = [[arguments objectAtIndex: 0] intValue];
usersRange.location = 1;
usersRange.length = max - 1;
rc = [self fetchUserIDs: [arguments subarrayWithRange: usersRange]];
}
else
{
[self usage];
rc = NO;
}
return rc;
}
- (BOOL) cleanupFolder: (NSString *) folder
withFM: (GCSFolderManager *) fm
{
GCSFolder *gcsFolder;
NSException *error;
BOOL rc;
unsigned int count;
gcsFolder = [fm folderAtPath: folder];
count = [gcsFolder recordsCountDeletedBefore: days];
error = nil;
if (count > 0)
error = [gcsFolder purgeDeletedRecordsBefore: days];
if (error)
{
NSLog(@"Unable to purge records of folder %@", folder);
rc = NO;
}
else
{
NSLog(@"Purged %u records from folder %@", count, folder);
rc = YES;
}
return rc;
}
- (BOOL) cleanupUserFolders: (NSString *) uid
{
GCSFolderManager *fm;
NSArray *folders;
int count, max;
NSString *basePath, *folder;
fm = [GCSFolderManager defaultFolderManager];
basePath = [NSString stringWithFormat: @"/Users/%@", uid];
folders = [fm listSubFoldersAtPath: basePath recursive: YES];
max = [folders count];
for (count = 0; count < max; count++)
{
folder = [NSString stringWithFormat: @"%@/%@", basePath, [folders objectAtIndex: count]];
//NSLog (@"folder %d: %@", count, folder);
[self cleanupFolder: folder withFM: fm];
}
return YES;
}
- (BOOL) cleanupUser: (NSDictionary *) theUser
{
NSString *gcsUID, *domain;
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
domain = [theUser objectForKey: @"c_domain"];
gcsUID = [theUser objectForKey: @"c_uid"];
if ([sd enableDomainBasedUID] && [gcsUID rangeOfString: @"@"].location == NSNotFound)
gcsUID = [NSString stringWithFormat: @"%@@%@", gcsUID, domain];
return [self cleanupUserFolders: gcsUID];
}
- (BOOL) proceed
{
NSAutoreleasePool *pool;
int count, max;
BOOL rc;
rc = YES;
pool = [NSAutoreleasePool new];
max = [usersToCleanup count];
for (count = 0; rc && count < max; count++)
{
rc = [self cleanupUser: [usersToCleanup objectAtIndex: count]];
if ((count % 10) == 0)
[pool emptyPool];
}
[pool release];
return rc;
}
- (BOOL) run
{
return ([self parseArguments] && [self proceed]);
}
@end