mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-04 19:15:27 +00:00
merge of '1998a97de11b63460cd6ecf4448985e926b5d6d7'
and '3556596e54bd1f81160e5c10120b46259ec240f8' Monotone-Parent: 1998a97de11b63460cd6ecf4448985e926b5d6d7 Monotone-Parent: 3556596e54bd1f81160e5c10120b46259ec240f8 Monotone-Revision: ac5f8b237dc599527e40ae6ad5333633c8dfffe1 Monotone-Author: flachapelle@inverse.ca Monotone-Date: 2009-06-02T19:04:01 Monotone-Branch: ca.inverse.sogo
This commit is contained in:
@@ -1,3 +1,16 @@
|
||||
2009-06-02 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* Tools/sogo-contacts-removedoubles.m: new maintenance utility
|
||||
that removes double records in specified addressbooks.
|
||||
|
||||
* Tools/sogo-contacts-checkdoubles.m: new maintenance utility that
|
||||
helps determine which contact folders need cleanup.
|
||||
|
||||
2009-06-02 Cyril Robert <crobert@inverse.ca>
|
||||
|
||||
* SoObjects/Appointments/SOGoAppointmentFolder.m (_enforceTimeLimitOnFilter):
|
||||
Rewrote algorithm, fixed cyclic events issue.
|
||||
|
||||
2009-06-02 Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
|
||||
* Updated the documentation (version change, Russian
|
||||
@@ -19,6 +32,10 @@
|
||||
|
||||
* UI/WebServerResources/UIxContactsUserFolders.js (onSearchFormSubmit):
|
||||
Fixed encoding issue/crash.
|
||||
* SoObjects/Appointments/SOGoAppointmentFolder.m (_parseCalendarFilter):
|
||||
Added support for default SOGoDAVCalendarStartTimeLimit, which is the
|
||||
maximum number of days for dav queries.
|
||||
Added: _enforceTimeLimitOnFilter, _getMaxStartDate and _getStartTimeLimit
|
||||
|
||||
2009-05-30 Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
2009-06-02 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* GCSFolder.m ([GCSFolder -recordsCountByExcludingDeleted:]): new
|
||||
method that returns the amount of records in a GCS folder.
|
||||
|
||||
2009-03-24 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* GCSFolderType.m ([GCSFolderType +folderTypeWithName:_typeName]):
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
- (void) deleteAclMatchingQualifier: (EOQualifier *) _q;
|
||||
- (void) deleteAclWithSpecification: (EOFetchSpecification *) _fs;
|
||||
|
||||
- (unsigned int) recordsCountByExcludingDeleted: (BOOL) includeDeleted;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* __GDLContentStore_GCSFolder_H__ */
|
||||
|
||||
@@ -1291,6 +1291,43 @@ static NSArray *contentFieldNames = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (unsigned int) recordsCountByExcludingDeleted: (BOOL) excludeDeleted
|
||||
{
|
||||
NSMutableString *sqlString;
|
||||
EOAdaptorChannel *channel;
|
||||
NSException *error;
|
||||
NSDictionary *row;
|
||||
unsigned int count;
|
||||
NSArray *attrs;
|
||||
|
||||
count = 0;
|
||||
|
||||
sqlString = [NSMutableString stringWithFormat:
|
||||
@"SELECT COUNT(*) AS CNT FROM %@",
|
||||
[self storeTableName]];
|
||||
if (excludeDeleted)
|
||||
[sqlString appendString: @" WHERE (c_deleted != 1 OR c_deleted IS NULL)"];
|
||||
|
||||
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];
|
||||
}
|
||||
[self releaseChannel: channel];
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* description */
|
||||
|
||||
- (NSString *)description {
|
||||
|
||||
@@ -475,11 +475,11 @@ static NSArray *reportQueryFields = nil;
|
||||
}
|
||||
|
||||
- (NSArray *) bareFetchFields: (NSArray *) fields
|
||||
from: (NSCalendarDate *) startDate
|
||||
to: (NSCalendarDate *) endDate
|
||||
title: (NSString *) title
|
||||
component: (id) component
|
||||
additionalFilters: (NSString *) filters
|
||||
from: (NSCalendarDate *) startDate
|
||||
to: (NSCalendarDate *) endDate
|
||||
title: (NSString *) title
|
||||
component: (id) component
|
||||
additionalFilters: (NSString *) filters
|
||||
{
|
||||
EOQualifier *qualifier;
|
||||
GCSFolder *folder;
|
||||
@@ -880,29 +880,31 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
while ((currentRecord = [ma nextObject]))
|
||||
{
|
||||
accessClass
|
||||
= [[currentRecord objectForKey: @"c_classification"] intValue];
|
||||
= [[currentRecord objectForKey: @"c_classification"] intValue];
|
||||
role = roles[accessClass];
|
||||
if (!role)
|
||||
{
|
||||
fullRole = [self roleForComponentsWithAccessClass: accessClass
|
||||
forUser: uid];
|
||||
if ([fullRole length] > 9)
|
||||
role = [fullRole substringFromIndex: 9];
|
||||
roles[accessClass] = role;
|
||||
}
|
||||
{
|
||||
fullRole = [self roleForComponentsWithAccessClass: accessClass
|
||||
forUser: uid];
|
||||
if ([fullRole length] > 9)
|
||||
role = [fullRole substringFromIndex: 9];
|
||||
roles[accessClass] = role;
|
||||
}
|
||||
if ([role isEqualToString: @"DAndTViewer"])
|
||||
[currentRecord removeObjectsForKeys: stripFields];
|
||||
[currentRecord removeObjectsForKeys: stripFields];
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: this method should make use of bareFetchFields instead and only keep
|
||||
its "intelligence" part for handling protected infos and recurrent
|
||||
events... */
|
||||
- (NSArray *) fetchFields: (NSArray *) _fields
|
||||
from: (NSCalendarDate *) _startDate
|
||||
to: (NSCalendarDate *) _endDate
|
||||
title: (NSString *) title
|
||||
component: (id) _component
|
||||
additionalFilters: (NSString *) filters
|
||||
includeProtectedInformation: (BOOL) _includeProtectedInformation;
|
||||
|
||||
from: (NSCalendarDate *) _startDate
|
||||
to: (NSCalendarDate *) _endDate
|
||||
title: (NSString *) title
|
||||
component: (id) _component
|
||||
additionalFilters: (NSString *) filters
|
||||
includeProtectedInformation: (BOOL) _includeProtectedInformation
|
||||
{
|
||||
EOQualifier *qualifier;
|
||||
GCSFolder *folder;
|
||||
@@ -929,11 +931,11 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
if (_startDate)
|
||||
{
|
||||
if (_endDate)
|
||||
endDate = _endDate;
|
||||
endDate = _endDate;
|
||||
else
|
||||
endDate = [NSCalendarDate distantFuture];
|
||||
endDate = [NSCalendarDate distantFuture];
|
||||
r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate
|
||||
endDate: endDate];
|
||||
endDate: endDate];
|
||||
dateSqlString = [self _sqlStringRangeFrom: _startDate to: endDate];
|
||||
}
|
||||
else
|
||||
@@ -996,14 +998,14 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
|
||||
records = [folder fetchFields: fields matchingQualifier: qualifier];
|
||||
if (records)
|
||||
{
|
||||
if (r)
|
||||
records = [self fixupCyclicRecords: records fetchRange: r];
|
||||
if (ma)
|
||||
[ma addObjectsFromArray: records];
|
||||
else
|
||||
ma = [NSMutableArray arrayWithArray: records];
|
||||
}
|
||||
{
|
||||
if (r)
|
||||
records = [self fixupCyclicRecords: records fetchRange: r];
|
||||
if (ma)
|
||||
[ma addObjectsFromArray: records];
|
||||
else
|
||||
ma = [NSMutableArray arrayWithArray: records];
|
||||
}
|
||||
}
|
||||
if (!ma)
|
||||
{
|
||||
@@ -1224,6 +1226,74 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
[r appendString: @"</D:href><D:status>HTTP/1.1 404 Not Found</D:status></D:response>"];
|
||||
}
|
||||
|
||||
- (int) _getStartTimeLimit
|
||||
{
|
||||
NSUserDefaults *ud;
|
||||
int interval;
|
||||
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
interval = [ud integerForKey: @"SOGoDAVCalendarStartTimeLimit"];
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) _getMaxStartDate
|
||||
{
|
||||
NSCalendarDate *tmp, *rc = NULL;
|
||||
int interval;
|
||||
|
||||
interval = [self _getStartTimeLimit];
|
||||
if (interval > 0)
|
||||
{
|
||||
tmp = [NSCalendarDate date];
|
||||
rc = [tmp addTimeInterval: interval * -86400];
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (void) _enforceTimeLimitOnFilter: (NSMutableDictionary *) filter
|
||||
{
|
||||
NSCalendarDate *start, *end, *now;
|
||||
int limit, interval, intervalStart, intervalEnd;
|
||||
|
||||
start = [filter objectForKey: @"start"];
|
||||
end = [filter objectForKey: @"end"];
|
||||
now = [NSCalendarDate date];
|
||||
limit = [self _getStartTimeLimit];
|
||||
interval = ([end timeIntervalSinceDate: start] / 86400);
|
||||
|
||||
if (limit > 0 && interval > limit)
|
||||
{
|
||||
if ([now compare: start] == NSOrderedDescending
|
||||
&& [now compare: end] == NSOrderedAscending)
|
||||
{
|
||||
intervalStart = [now timeIntervalSinceDate: start] / 86400;
|
||||
intervalEnd = [end timeIntervalSinceDate: now] / 86400;
|
||||
if (intervalStart > limit / 2)
|
||||
{
|
||||
start = [now addTimeInterval: (limit / 2) * -86400];
|
||||
[filter setObject: start forKey: @"start"];
|
||||
}
|
||||
if (intervalEnd > limit / 2)
|
||||
{
|
||||
end = [now addTimeInterval: (limit / 2) * 86400];
|
||||
[filter setObject: end forKey: @"end"];
|
||||
}
|
||||
}
|
||||
else if ([now compare: end] == NSOrderedDescending)
|
||||
{
|
||||
start = [end addTimeInterval: limit * -86400];
|
||||
[filter setObject: start forKey: @"start"];
|
||||
}
|
||||
else if ([now compare: start] == NSOrderedAscending)
|
||||
{
|
||||
end = [start addTimeInterval: limit * 86400];
|
||||
[filter setObject: end forKey: @"end"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) _appendTimeRange: (id <DOMElement>) timeRangeElement
|
||||
toFilter: (NSMutableDictionary *) filter
|
||||
{
|
||||
@@ -1233,6 +1303,22 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
[filter setObject: parsedDate forKey: @"start"];
|
||||
parsedDate = [[timeRangeElement attribute: @"end"] asCalendarDate];
|
||||
[filter setObject: parsedDate forKey: @"end"];
|
||||
|
||||
[self _enforceTimeLimitOnFilter: filter];
|
||||
}
|
||||
|
||||
- (void) _addDateRangeLimitToFilter: (NSMutableDictionary *) filter
|
||||
{
|
||||
NSCalendarDate *now;
|
||||
int limit;
|
||||
|
||||
now = [NSCalendarDate date];
|
||||
limit = [self _getStartTimeLimit];
|
||||
|
||||
[filter setObject: [now addTimeInterval: (limit / 2) * -86400]
|
||||
forKey: @"start"];
|
||||
[filter setObject: [now addTimeInterval: (limit / 2) * 86400]
|
||||
forKey: @"end"];
|
||||
}
|
||||
|
||||
#warning This method lacks support for timeranges
|
||||
@@ -1264,6 +1350,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
id <DOMElement> parentNode;
|
||||
id <DOMNodeList> elements;
|
||||
NSString *componentName;
|
||||
NSCalendarDate *maxStart;
|
||||
|
||||
parentNode = (id <DOMElement>) [filterElement parentNode];
|
||||
if ([[parentNode tagName] isEqualToString: @"comp-filter"]
|
||||
@@ -1275,12 +1362,20 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
elements = [filterElement getElementsByTagName: @"time-range"];
|
||||
if ([elements length])
|
||||
[self _appendTimeRange: [elements objectAtIndex: 0]
|
||||
toFilter: filterData];
|
||||
toFilter: filterData];
|
||||
|
||||
elements = [filterElement getElementsByTagName: @"prop-filter"];
|
||||
if ([elements length])
|
||||
[self _appendPropertyFilter: [elements objectAtIndex: 0]
|
||||
toFilter: filterData];
|
||||
toFilter: filterData];
|
||||
|
||||
if (![filterData objectForKey: @"start"])
|
||||
{
|
||||
maxStart = [self _getMaxStartDate];
|
||||
if (maxStart)
|
||||
[self _addDateRangeLimitToFilter: filterData];
|
||||
}
|
||||
[filterData setObject: [NSNumber numberWithBool: NO] forKey: @"iscycle"];
|
||||
}
|
||||
else
|
||||
filterData = nil;
|
||||
@@ -1306,25 +1401,37 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
children = [[propList objectAtIndex: count] childNodes];
|
||||
max2 = [children length];
|
||||
for (count2 = 0; count2 < max2; count2++)
|
||||
{
|
||||
currentChild = [children objectAtIndex: count2];
|
||||
if ([currentChild conformsToProtocol: @protocol (DOMElement)])
|
||||
{
|
||||
flatProperty = [NSString stringWithFormat: @"{%@}%@",
|
||||
[currentChild namespaceURI],
|
||||
[currentChild nodeName]];
|
||||
[properties addObject: flatProperty];
|
||||
}
|
||||
}
|
||||
{
|
||||
currentChild = [children objectAtIndex: count2];
|
||||
if ([currentChild conformsToProtocol: @protocol (DOMElement)])
|
||||
{
|
||||
flatProperty = [NSString stringWithFormat: @"{%@}%@",
|
||||
[currentChild namespaceURI],
|
||||
[currentChild nodeName]];
|
||||
[properties addObject: flatProperty];
|
||||
}
|
||||
}
|
||||
|
||||
// while ([children hasChildNodes])
|
||||
// [properties addObject: [children next]];
|
||||
// while ([children hasChildNodes])
|
||||
// [properties addObject: [children next]];
|
||||
}
|
||||
// NSLog (@"/parseRequestProperties: %@", [NSDate date]);
|
||||
// NSLog (@"/parseRequestProperties: %@", [NSDate date]);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
- (NSDictionary *) _makeCyclicFilterFrom: (NSDictionary *) filter
|
||||
{
|
||||
NSMutableDictionary *rc;
|
||||
|
||||
rc = [NSMutableDictionary dictionaryWithDictionary: filter];
|
||||
[rc removeObjectForKey: @"start"];
|
||||
[rc removeObjectForKey: @"end"];
|
||||
[rc setObject: sharedYes forKey: @"iscycle"];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
|
||||
{
|
||||
id <DOMNodeList> children;
|
||||
@@ -1343,7 +1450,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
node = [children objectAtIndex: count];
|
||||
filter = [self _parseCalendarFilter: node];
|
||||
if (filter)
|
||||
[filters addObject: filter];
|
||||
{
|
||||
[filters addObject: filter];
|
||||
[filters addObject: [self _makeCyclicFilterFrom: filter]];
|
||||
}
|
||||
}
|
||||
// NSLog (@"/parseCalendarFilter: %@", [NSDate date]);
|
||||
|
||||
@@ -1351,17 +1461,17 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
}
|
||||
|
||||
- (NSString *) _additionalFilterKey: (NSString *) key
|
||||
value: (NSString *) value
|
||||
value: (NSString *) value
|
||||
{
|
||||
NSString *filterString;
|
||||
|
||||
if ([value length])
|
||||
{
|
||||
if ([value isEqualToString: @"NULL"])
|
||||
filterString = [NSString stringWithFormat: @"(%@ = '')", key];
|
||||
filterString = [NSString stringWithFormat: @"(%@ = '')", key];
|
||||
else
|
||||
filterString
|
||||
= [NSString stringWithFormat: @"(%@ like '%%%@%%')", key, value];
|
||||
filterString
|
||||
= [NSString stringWithFormat: @"(%@ like '%%%@%%')", key, value];
|
||||
}
|
||||
else
|
||||
filterString = [NSString stringWithFormat: @"(%@ != '')", key];
|
||||
@@ -1376,6 +1486,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
NSString *currentKey, *keyField, *filterString;
|
||||
static NSArray *fields = nil;
|
||||
NSMutableArray *filters;
|
||||
NSNumber *cycle;
|
||||
|
||||
#warning the list of fields should be taken from the .ocs description file
|
||||
if (!fields)
|
||||
@@ -1390,11 +1501,21 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
{
|
||||
keyField = [NSString stringWithFormat: @"c_%@", currentKey];
|
||||
if ([fields containsObject: keyField])
|
||||
{
|
||||
filterString = [self _additionalFilterKey: keyField
|
||||
value: [filter objectForKey: currentKey]];
|
||||
[filters addObject: filterString];
|
||||
}
|
||||
{
|
||||
filterString
|
||||
= [self _additionalFilterKey: keyField
|
||||
value: [filter objectForKey: currentKey]];
|
||||
[filters addObject: filterString];
|
||||
}
|
||||
}
|
||||
|
||||
// Exception for iscycle
|
||||
cycle = [filter objectForKey: @"iscycle"];
|
||||
if (cycle)
|
||||
{
|
||||
filterString = [NSString stringWithFormat: @"(c_iscycle = '%d')",
|
||||
[cycle intValue]];
|
||||
[filters addObject: filterString];
|
||||
}
|
||||
|
||||
if ([filters count])
|
||||
@@ -1473,10 +1594,9 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (void) _appendComponentProperties: (NSString **) properties
|
||||
matchingFilters: (NSArray *) filters
|
||||
toResponse: (WOResponse *) response
|
||||
matchingFilters: (NSArray *) filters
|
||||
toResponse: (WOResponse *) response
|
||||
{
|
||||
NSArray *apts, *fields;
|
||||
NSDictionary *currentFilter;
|
||||
@@ -1495,8 +1615,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
if ([*properties caseInsensitiveCompare: @"{DAV:}getetag"] == NSOrderedSame &&
|
||||
!*(properties+1))
|
||||
fields = [NSArray arrayWithObjects: @"c_name", @"c_creationdate",
|
||||
@"c_lastmodified", @"c_version",
|
||||
@"c_component", nil];
|
||||
@"c_lastmodified", @"c_version",
|
||||
@"c_component", nil];
|
||||
else
|
||||
fields = reportQueryFields;
|
||||
|
||||
@@ -1505,20 +1625,22 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
{
|
||||
additionalFilters = [self _composeAdditionalFilters: currentFilter];
|
||||
//NSLog(@"query");
|
||||
/* TODO: we should invoke bareFetchField:... twice and compute the
|
||||
recurrent events properly instead of using _makeCyclicFilterFrom: */
|
||||
apts = [self bareFetchFields: fields
|
||||
from: [currentFilter objectForKey: @"start"]
|
||||
to: [currentFilter objectForKey: @"end"]
|
||||
title: [currentFilter objectForKey: @"title"]
|
||||
component: [currentFilter objectForKey: @"name"]
|
||||
additionalFilters: additionalFilters];
|
||||
from: [currentFilter objectForKey: @"start"]
|
||||
to: [currentFilter objectForKey: @"end"]
|
||||
title: [currentFilter objectForKey: @"title"]
|
||||
component: [currentFilter objectForKey: @"name"]
|
||||
additionalFilters: additionalFilters];
|
||||
//NSLog(@"adding properties");
|
||||
max = [apts count];
|
||||
buffer = [[NSMutableString alloc] initWithCapacity: max*512];
|
||||
for (count = 0; count < max; count++)
|
||||
[self appendObject: [apts objectAtIndex: count]
|
||||
properties: properties
|
||||
withBaseURL: baseURL
|
||||
toBuffer: buffer];
|
||||
[self appendObject: [apts objectAtIndex: count]
|
||||
properties: properties
|
||||
withBaseURL: baseURL
|
||||
toBuffer: buffer];
|
||||
//NSLog(@"done");
|
||||
[response appendContentString: buffer];
|
||||
[buffer release];
|
||||
@@ -1547,7 +1669,8 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
properties = [[self _parseRequestedProperties: documentElement]
|
||||
asPointersOfObjects];
|
||||
[self _appendComponentProperties: properties
|
||||
matchingFilters: [self _parseCalendarFilters: documentElement]
|
||||
matchingFilters: [self _parseCalendarFilters:
|
||||
documentElement]
|
||||
toResponse: r];
|
||||
[r appendContentString:@"</D:multistatus>"];
|
||||
free (properties);
|
||||
@@ -1672,15 +1795,15 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
record = [records objectAtIndex: count];
|
||||
recordURL = [cnames objectForKey: [record objectForKey: @"c_name"]];
|
||||
if (recordURL)
|
||||
[components setObject: record forKey: recordURL];
|
||||
[components setObject: record forKey: recordURL];
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
- (void) _appendComponentProperties: (NSString **) properties
|
||||
matchingURLs: (id <DOMNodeList>) refs
|
||||
toResponse: (WOResponse *) response
|
||||
matchingURLs: (id <DOMNodeList>) refs
|
||||
toResponse: (WOResponse *) response
|
||||
{
|
||||
NSObject <DOMElement> *element;
|
||||
NSDictionary *currentComponent, *components;
|
||||
@@ -1708,13 +1831,13 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
{
|
||||
currentComponent = [components objectForKey: [urls objectAtIndex: count]];
|
||||
if (currentComponent)
|
||||
[self appendObject: currentComponent
|
||||
properties: properties
|
||||
withBaseURL: baseURL
|
||||
toBuffer: buffer];
|
||||
[self appendObject: currentComponent
|
||||
properties: properties
|
||||
withBaseURL: baseURL
|
||||
toBuffer: buffer];
|
||||
else
|
||||
[self appendMissingObjectRef: currentURL
|
||||
toBuffer: buffer];
|
||||
[self appendMissingObjectRef: currentURL
|
||||
toBuffer: buffer];
|
||||
}
|
||||
[response appendContentString: buffer];
|
||||
[buffer release];
|
||||
|
||||
@@ -1667,7 +1667,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property)
|
||||
{
|
||||
currentValue = [setProps objectForKey: currentProp];
|
||||
exception = [self performSelector: methodSel
|
||||
withObject: currentValue];
|
||||
withObject: currentValue];
|
||||
}
|
||||
else
|
||||
exception
|
||||
|
||||
@@ -915,15 +915,15 @@ _timeValue (NSString *key)
|
||||
- (SOGoUserFolder *) homeFolderInContext: (id) context
|
||||
{
|
||||
return [[WOApplication application] lookupName: [self login]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
}
|
||||
|
||||
- (SOGoAppointmentFolders *) calendarsFolderInContext: (WOContext *) context
|
||||
{
|
||||
return [[self homeFolderInContext: context] lookupName: @"Calendar"
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
}
|
||||
|
||||
- (SOGoAppointmentFolder *)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# GNUstep makefile
|
||||
|
||||
include ../config.make
|
||||
include $(GNUSTEP_MAKEFILES)/common.make
|
||||
include ../Version
|
||||
|
||||
SOGO_CHECKDOUBLES = sogo-contacts-checkdoubles
|
||||
$(SOGO_CHECKDOUBLES)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS)
|
||||
$(SOGO_CHECKDOUBLES)_OBJC_FILES += \
|
||||
sogo-contacts-checkdoubles.m
|
||||
|
||||
SOGO_REMOVEDOUBLES = sogo-contacts-removedoubles
|
||||
$(SOGO_REMOVEDOUBLES)_INSTALL_DIR = $(SOGO_ADMIN_TOOLS)
|
||||
$(SOGO_REMOVEDOUBLES)_OBJC_FILES += \
|
||||
sogo-contacts-removedoubles.m
|
||||
|
||||
TOOL_NAME = $(SOGO_CHECKDOUBLES) $(SOGO_REMOVEDOUBLES)
|
||||
|
||||
-include GNUmakefile.preamble
|
||||
include $(GNUSTEP_MAKEFILES)/tool.make
|
||||
-include GNUmakefile.postamble
|
||||
@@ -0,0 +1,20 @@
|
||||
# compile settings
|
||||
|
||||
ADDITIONAL_CPPFLAGS += \
|
||||
-DSOGO_MAJOR_VERSION=$(MAJOR_VERSION) \
|
||||
-DSOGO_MINOR_VERSION=$(MINOR_VERSION) \
|
||||
-DSOGO_SUBMINOR_VERSION=$(SUBMINOR_VERSION) \
|
||||
-DSOGO_LIBDIR="\"$(SOGO_LIBDIR)\""
|
||||
|
||||
ADDITIONAL_INCLUDE_DIRS += \
|
||||
-I../SOPE/ -D_GNU_SOURCE
|
||||
|
||||
ADDITIONAL_LIB_DIRS += \
|
||||
-L../SOPE/GDLContentStore/$(GNUSTEP_OBJ_DIR)/ -lGDLContentStore
|
||||
|
||||
SYSTEM_LIB_DIR += -L/usr/local/lib -L/usr/lib
|
||||
|
||||
$(SOGOD)_TOOL_LIBS += \
|
||||
-lGDLContentStore \
|
||||
-lGDLAccess \
|
||||
-lNGExtensions
|
||||
@@ -0,0 +1,187 @@
|
||||
/* sogo-ab-checkdoubles.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* TODO:
|
||||
- NSUserDefaults bootstrapping for using different backends
|
||||
- make sure we don't end up using 3000 different channels because of the
|
||||
amount of tables we need to wander */
|
||||
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
#import <GDLContentStore/GCSFolder.h>
|
||||
|
||||
typedef void (*NSUserDefaultsInitFunction) ();
|
||||
|
||||
static unsigned int ContactsCountWarningLimit = 1000;
|
||||
|
||||
static void
|
||||
NSLogInhibitor (NSString *message)
|
||||
{
|
||||
}
|
||||
|
||||
@interface SOGoDoublesChecker : NSObject
|
||||
|
||||
- (BOOL) run;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SOGoDoublesChecker
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSUserDefaults *ud;
|
||||
NSNumber *warningLimit;
|
||||
|
||||
_NSLog_printf_handler = NSLogInhibitor;
|
||||
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
[ud addSuiteNamed: @"sogod"];
|
||||
warningLimit = [ud objectForKey: @"SOGoContactsCountWarningLimit"];
|
||||
if (warningLimit)
|
||||
ContactsCountWarningLimit = [warningLimit unsignedIntValue];
|
||||
|
||||
fprintf (stderr, "The warning limit for folder records is set at %u\n",
|
||||
ContactsCountWarningLimit);
|
||||
}
|
||||
|
||||
- (void) processIndexResults: (EOAdaptorChannel *) channel
|
||||
withFoM: (GCSFolderManager *) fom
|
||||
{
|
||||
NSArray *attrs;
|
||||
NSDictionary *folderRow;
|
||||
GCSFolder *currentFolder;
|
||||
NSString *folderPath;
|
||||
unsigned int recordsCount;
|
||||
|
||||
attrs = [channel describeResults: NO];
|
||||
while ((folderRow = [channel fetchAttributes: attrs withZone: NULL]))
|
||||
{
|
||||
folderPath = [folderRow objectForKey: @"c_path"];
|
||||
currentFolder = [fom folderAtPath: folderPath];
|
||||
if (currentFolder)
|
||||
{
|
||||
recordsCount = [currentFolder recordsCountByExcludingDeleted: YES];
|
||||
if (recordsCount > ContactsCountWarningLimit)
|
||||
{
|
||||
fprintf (stderr, "'%s' (id: '%s'), of '%s': %u entries\n",
|
||||
[[folderRow objectForKey: @"c_foldername"]
|
||||
cStringUsingEncoding: NSUTF8StringEncoding],
|
||||
[[currentFolder folderName]
|
||||
cStringUsingEncoding: NSUTF8StringEncoding],
|
||||
[[folderRow objectForKey: @"c_path2"]
|
||||
cStringUsingEncoding: NSUTF8StringEncoding],
|
||||
recordsCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
fprintf (stderr, "folder at path '%s' could not be opened\n",
|
||||
[folderPath cStringUsingEncoding: NSUTF8StringEncoding]);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) processWithFoM: (GCSFolderManager *) fom
|
||||
{
|
||||
BOOL rc;
|
||||
NSString *sqlString;
|
||||
GCSChannelManager *cm;
|
||||
EOAdaptorChannel *channel;
|
||||
NSException *ex;
|
||||
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: [fom folderInfoLocation]];
|
||||
if (channel)
|
||||
{
|
||||
sqlString = [NSString stringWithFormat: @"SELECT c_path, c_path2, c_foldername"
|
||||
@" FROM %@"
|
||||
@" WHERE c_folder_type = 'Contact'",
|
||||
[fom folderInfoTableName]];
|
||||
ex = [channel evaluateExpressionX: sqlString];
|
||||
if (ex)
|
||||
{
|
||||
fprintf (stderr, "an exception occured during the fetching of folder names");
|
||||
[ex raise];
|
||||
rc = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = YES;
|
||||
[self processIndexResults: channel withFoM: fom];
|
||||
}
|
||||
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "could not open channel");
|
||||
rc = NO;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) run
|
||||
{
|
||||
GCSFolderManager *fom;
|
||||
BOOL rc;
|
||||
|
||||
fom = [GCSFolderManager defaultFolderManager];
|
||||
if (fom)
|
||||
rc = [self processWithFoM: fom];
|
||||
else
|
||||
rc = NO;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int
|
||||
main (int argc, char **argv, char **env)
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
SOGoDoublesChecker *checker;
|
||||
int rc;
|
||||
|
||||
rc = 0;
|
||||
|
||||
pool = [NSAutoreleasePool new];
|
||||
|
||||
checker = [SOGoDoublesChecker new];
|
||||
if ([checker run])
|
||||
rc = 0;
|
||||
else
|
||||
rc = -1;
|
||||
[checker release];
|
||||
|
||||
[pool release];
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
/* sogo-ab-removedoubles.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* TODO: NSUserDefaults bootstrapping for using different backends */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
#import <GDLAccess/EOAdaptorContext.h>
|
||||
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
#import <GDLContentStore/GCSFolder.h>
|
||||
|
||||
typedef void (*NSUserDefaultsInitFunction) ();
|
||||
|
||||
static unsigned int ContactsCountWarningLimit = 1000;
|
||||
|
||||
@interface SOGoDoublesRemover : NSObject
|
||||
@end
|
||||
|
||||
@implementation SOGoDoublesRemover
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSUserDefaults *ud;
|
||||
NSNumber *warningLimit;
|
||||
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
[ud addSuiteNamed: @"sogod"];
|
||||
warningLimit = [ud objectForKey: @"SOGoContactsCountWarningLimit"];
|
||||
if (warningLimit)
|
||||
ContactsCountWarningLimit = [warningLimit unsignedIntValue];
|
||||
|
||||
NSLog (@"The warning limit for folder records is set at %u",
|
||||
ContactsCountWarningLimit);
|
||||
}
|
||||
|
||||
- (void) feedDoubleEmails: (NSMutableDictionary *) doubleEmails
|
||||
withRecord: (NSDictionary *) record
|
||||
{
|
||||
NSString *recordEmail;
|
||||
NSMutableArray *recordList;
|
||||
|
||||
/* we want to match c_mail case-insensitively */
|
||||
recordEmail = [[record objectForKey: @"c_mail"] uppercaseString];
|
||||
if ([recordEmail length])
|
||||
{
|
||||
recordList = [doubleEmails objectForKey: recordEmail];
|
||||
if (!recordList)
|
||||
{
|
||||
recordList = [NSMutableArray arrayWithCapacity: 5];
|
||||
[doubleEmails setObject: recordList forKey: recordEmail];
|
||||
}
|
||||
[recordList addObject: record];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) cleanupSingleRecords: (NSMutableDictionary *) doubleEmails
|
||||
{
|
||||
NSEnumerator *keys;
|
||||
NSString *currentKey;
|
||||
|
||||
keys = [[doubleEmails allKeys] objectEnumerator];
|
||||
while ((currentKey = [keys nextObject]))
|
||||
{
|
||||
if ([[doubleEmails objectForKey: currentKey] count] < 2)
|
||||
[doubleEmails removeObjectForKey: currentKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *) detectDoubleEmailsFromRecords: (NSArray *) records
|
||||
{
|
||||
NSMutableDictionary *doubleEmails;
|
||||
unsigned int count, max;
|
||||
|
||||
doubleEmails = [NSMutableDictionary dictionaryWithCapacity: [records count]];
|
||||
max = [records count];
|
||||
for (count = 0; count < max; count++)
|
||||
[self feedDoubleEmails: doubleEmails
|
||||
withRecord: [records objectAtIndex: count]];
|
||||
[self cleanupSingleRecords: doubleEmails];
|
||||
|
||||
return doubleEmails;
|
||||
}
|
||||
|
||||
- (void) removeRecord: (NSString *) recordName
|
||||
fromTable: (NSString *) tableName
|
||||
andQuickTable: (NSString *) quickTableName
|
||||
usingChannel: (EOAdaptorChannel *) channel
|
||||
{
|
||||
NSString *delSql;
|
||||
|
||||
/* We remove the records without regards to c_deleted because we really want
|
||||
to recover table space. */
|
||||
|
||||
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
|
||||
@" WHERE c_name = '%@'",
|
||||
tableName, recordName];
|
||||
[channel evaluateExpressionX: delSql];
|
||||
delSql = [NSString stringWithFormat: @"DELETE FROM %@"
|
||||
@" WHERE c_name = '%@'",
|
||||
quickTableName, recordName];
|
||||
[channel evaluateExpressionX: delSql];
|
||||
}
|
||||
|
||||
- (void) removeRecords: (NSArray *) recordNames
|
||||
fromFolder: (GCSFolder *) folder
|
||||
{
|
||||
EOAdaptorChannel *channel;
|
||||
EOAdaptorContext *context;
|
||||
NSString *tableName, *quickTableName, *currentRecordName;
|
||||
NSEnumerator *recordsEnum;
|
||||
|
||||
channel = [folder acquireStoreChannel];
|
||||
context = [channel adaptorContext];
|
||||
[context beginTransaction];
|
||||
|
||||
tableName = [folder storeTableName];
|
||||
quickTableName = [folder quickTableName];
|
||||
|
||||
recordsEnum = [recordNames objectEnumerator];
|
||||
while ((currentRecordName = [recordsEnum nextObject]))
|
||||
[self removeRecord: currentRecordName
|
||||
fromTable: tableName andQuickTable: quickTableName
|
||||
usingChannel: channel];
|
||||
fprintf (stderr, "Removing %d records...\n", [recordNames count]);
|
||||
|
||||
[context commitTransaction];
|
||||
[folder releaseChannel: channel];
|
||||
}
|
||||
|
||||
- (NSArray *) namesOfRecords: (NSArray *) records
|
||||
differentFrom: (unsigned int) keptRecord
|
||||
count: (unsigned int) max
|
||||
{
|
||||
NSMutableArray *recordsToRemove;
|
||||
NSDictionary *currentRecord;
|
||||
unsigned int count;
|
||||
|
||||
recordsToRemove = [NSMutableArray arrayWithCapacity: (max - 1)];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
if (count != keptRecord)
|
||||
{
|
||||
currentRecord = [records objectAtIndex: count];
|
||||
[recordsToRemove
|
||||
addObject: [currentRecord objectForKey: @"c_name"]];
|
||||
}
|
||||
}
|
||||
|
||||
return recordsToRemove;
|
||||
}
|
||||
|
||||
- (NSArray *) records: (NSArray *) records
|
||||
withLowestScores: (unsigned int *) scores
|
||||
count: (unsigned int) max
|
||||
{
|
||||
unsigned int count, highestScore;
|
||||
int highestScoreRecord;
|
||||
|
||||
highestScore = 0;
|
||||
highestScoreRecord = -1;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
if (scores[count] > highestScore)
|
||||
{
|
||||
highestScore = scores[count];
|
||||
highestScoreRecord = count;
|
||||
}
|
||||
}
|
||||
|
||||
if (highestScoreRecord == -1)
|
||||
highestScoreRecord = 0;
|
||||
|
||||
return [self namesOfRecords: records
|
||||
differentFrom: highestScoreRecord
|
||||
count: max];
|
||||
}
|
||||
|
||||
- (unsigned int) mostModifiedRecord: (NSArray *) records
|
||||
count: (unsigned int) max
|
||||
{
|
||||
unsigned int mostModified, count, highestVersion, version;
|
||||
NSNumber *currentVersion;
|
||||
|
||||
mostModified = 0;
|
||||
|
||||
highestVersion = 0;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentVersion
|
||||
= [[records objectAtIndex: count] objectForKey: @"c_version"];
|
||||
version = [currentVersion intValue];
|
||||
if (version > highestVersion)
|
||||
{
|
||||
mostModified = count;
|
||||
highestVersion = version;
|
||||
}
|
||||
}
|
||||
|
||||
return mostModified;
|
||||
}
|
||||
|
||||
- (unsigned int) amountOfFilledQuickFields: (NSDictionary *) record
|
||||
{
|
||||
static NSArray *quickFields = nil;
|
||||
id value;
|
||||
unsigned int amount, count, max;
|
||||
|
||||
amount = 0;
|
||||
|
||||
if (!quickFields)
|
||||
{
|
||||
quickFields = [NSArray arrayWithObjects: @"c_givenname", @"c_cn",
|
||||
@"c_sn", @"c_screenname", @"c_l", @"c_mail",
|
||||
@"c_o", @"c_ou", @"c_telephoneNumber", nil];
|
||||
[quickFields retain];
|
||||
}
|
||||
|
||||
max = [quickFields count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
value = [record objectForKey: [quickFields objectAtIndex: count]];
|
||||
if ([value isKindOfClass: [NSString class]])
|
||||
{
|
||||
if ([value length])
|
||||
amount++;
|
||||
}
|
||||
else if ([value isKindOfClass: [NSNumber class]])
|
||||
amount++;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
- (unsigned int) recordWithTheMostQuickFields: (NSArray *) records
|
||||
count: (unsigned int) max
|
||||
{
|
||||
unsigned int mostQuickFields, count, highestQFields, currentQFields;
|
||||
|
||||
mostQuickFields = 0;
|
||||
|
||||
highestQFields = 0;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
currentQFields
|
||||
= [self amountOfFilledQuickFields: [records objectAtIndex: count]];
|
||||
if (currentQFields > highestQFields)
|
||||
{
|
||||
mostQuickFields = count;
|
||||
highestQFields = currentQFields;
|
||||
}
|
||||
}
|
||||
|
||||
return mostQuickFields;
|
||||
}
|
||||
|
||||
- (unsigned int) linesInContent: (NSString *) content
|
||||
{
|
||||
unsigned int nbrLines;
|
||||
NSArray *lines;
|
||||
|
||||
lines = [content componentsSeparatedByString: @"\n"];
|
||||
nbrLines = [lines count];
|
||||
|
||||
/* sometimes the end line will finish with a CRLF, we fix this */
|
||||
if (![[lines objectAtIndex: nbrLines - 1] length])
|
||||
nbrLines--;
|
||||
|
||||
return nbrLines;
|
||||
}
|
||||
|
||||
- (unsigned int) mostCompleteRecord: (NSArray *) records
|
||||
count: (unsigned int) max
|
||||
{
|
||||
unsigned int mostComplete, count, highestLines, lines;
|
||||
NSString *content;
|
||||
|
||||
mostComplete = 0;
|
||||
|
||||
highestLines = 0;
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
content = [[records objectAtIndex: count] objectForKey: @"c_content"];
|
||||
lines = [self linesInContent: content];
|
||||
if (lines > highestLines)
|
||||
{
|
||||
mostComplete = count;
|
||||
highestLines = lines;
|
||||
}
|
||||
}
|
||||
|
||||
return mostComplete;
|
||||
}
|
||||
|
||||
- (void) assignScores: (unsigned int *) scores
|
||||
toRecords: (NSArray *) records
|
||||
count: (unsigned int) max
|
||||
{
|
||||
unsigned int recordIndex;
|
||||
|
||||
recordIndex = [self mostModifiedRecord: records count: max];
|
||||
(*(scores + recordIndex))++;
|
||||
recordIndex = [self mostCompleteRecord: records count: max];
|
||||
(*(scores + recordIndex)) += 2;
|
||||
recordIndex = [self recordWithTheMostQuickFields: records count: max];
|
||||
(*(scores + recordIndex)) += 3;
|
||||
}
|
||||
|
||||
- (NSArray *) detectRecordsToRemove: (NSDictionary *) records
|
||||
{
|
||||
NSMutableArray *recordsToRemove;
|
||||
NSEnumerator *recordsEnum;
|
||||
NSArray *currentRecords;
|
||||
unsigned int *scores, max;
|
||||
|
||||
recordsToRemove = [NSMutableArray arrayWithCapacity: [records count] * 4];
|
||||
recordsEnum = [[records allValues] objectEnumerator];
|
||||
while ((currentRecords = [recordsEnum nextObject]))
|
||||
{
|
||||
max = [currentRecords count];
|
||||
scores = NSZoneCalloc (NULL, max, sizeof (unsigned int));
|
||||
[self assignScores: scores toRecords: currentRecords count: max];
|
||||
[recordsToRemove addObjectsFromArray: [self records: currentRecords
|
||||
withLowestScores: scores
|
||||
count: max]];
|
||||
NSZoneFree (NULL, scores);
|
||||
}
|
||||
|
||||
return recordsToRemove;
|
||||
}
|
||||
|
||||
- (BOOL) removeDoublesFromFolder: (GCSFolder *) folder
|
||||
{
|
||||
NSArray *fields, *records, *recordsToRemove;
|
||||
BOOL rc;
|
||||
|
||||
fields = [NSArray arrayWithObjects: @"c_name", @"c_givenname", @"c_cn",
|
||||
@"c_sn", @"c_screenname", @"c_l", @"c_mail", @"c_o",
|
||||
@"c_ou", @"c_telephoneNumber", @"c_content", @"c_version",
|
||||
@"c_creationdate", @"c_lastmodified", nil];
|
||||
records = [folder fetchFields: fields fetchSpecification: nil];
|
||||
if (records)
|
||||
{
|
||||
rc = YES;
|
||||
recordsToRemove
|
||||
= [self detectRecordsToRemove:
|
||||
[self detectDoubleEmailsFromRecords: records]];
|
||||
[self removeRecords: recordsToRemove fromFolder: folder];
|
||||
fprintf (stderr, "Removed %d records from %d.\n",
|
||||
[recordsToRemove count], [records count]);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "Unable to fetch required fields from folder.\n");
|
||||
rc = NO;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) processFolder: (NSString *) folderId
|
||||
ofUser: (NSString *) username
|
||||
withFoM: (GCSFolderManager *) fom
|
||||
{
|
||||
NSString *folderPath;
|
||||
GCSFolder *folder;
|
||||
BOOL rc;
|
||||
|
||||
folderPath = [NSString stringWithFormat: @"/Users/%@/Contacts/%@",
|
||||
username, folderId];
|
||||
folder = [fom folderAtPath: folderPath];
|
||||
if (folder)
|
||||
{
|
||||
rc = [self removeDoublesFromFolder: folder];
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "Folder '%s' not found.\n",
|
||||
[folderId cStringUsingEncoding: NSUTF8StringEncoding]);
|
||||
rc = NO;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) runWithFolder: (NSString *) folder
|
||||
andUser: (NSString *) username
|
||||
{
|
||||
GCSFolderManager *fom;
|
||||
BOOL rc;
|
||||
|
||||
fom = [GCSFolderManager defaultFolderManager];
|
||||
if (fom)
|
||||
rc = [self processFolder: folder ofUser: username
|
||||
withFoM: fom];
|
||||
else
|
||||
rc = NO;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void
|
||||
Usage (const char *name)
|
||||
{
|
||||
const char *slash, *start;
|
||||
|
||||
slash = strrchr (name, '/');
|
||||
if (slash)
|
||||
start = slash + 1;
|
||||
else
|
||||
start = name;
|
||||
fprintf (stderr, "Usage: %s USER FOLDER\n\n"
|
||||
" USER the owner of the contact folder\n"
|
||||
" FOLDER the id of the folder to clean up\n",
|
||||
start);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv, char **env)
|
||||
{
|
||||
NSAutoreleasePool *pool;
|
||||
SOGoDoublesRemover *remover;
|
||||
int rc;
|
||||
|
||||
rc = -1;
|
||||
|
||||
pool = [NSAutoreleasePool new];
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
remover = [SOGoDoublesRemover new];
|
||||
if ([remover runWithFolder: [NSString stringWithFormat: @"%s", argv[2]]
|
||||
andUser: [NSString stringWithFormat: @"%s", argv[1]]])
|
||||
rc = 0;
|
||||
[remover release];
|
||||
}
|
||||
else
|
||||
Usage (argv[0]);
|
||||
|
||||
[pool release];
|
||||
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user