From 8aae87afd5ce9cbc0a776ce3a5be6d58db4a364c Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 16 Jan 2008 18:46:32 +0000 Subject: [PATCH] Monotone-Parent: f6d4ca8944c94273bc0dae0565c60e45be9f2ca1 Monotone-Revision: 98896091d472e0ee7990f69f32f5981f5d1dc546 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-01-16T18:46:32 Monotone-Branch: ca.inverse.sogo --- Main/SOGo.m | 4 + SOPE/GDLContentStore/GCSFolder.h | 5 +- SOPE/GDLContentStore/GCSFolder.m | 103 +++--- SOPE/sope-patchset-r1557.diff | 129 +++++--- Scripts/sogo-user.pl | 292 ++++++++++++++++++ .../Appointments/SOGoAppointmentFolder.m | 10 +- .../Appointments/SOGoCalendarComponent.m | 4 +- SoObjects/Contacts/SOGoContactGCSEntry.m | 3 + SoObjects/Mailer/SOGoMailAccounts.m | 2 +- SoObjects/Mailer/SOGoMailObject.m | 2 +- SoObjects/SOGo/GNUmakefile | 2 + SoObjects/SOGo/SOGoContentObject.h | 2 +- SoObjects/SOGo/SOGoContentObject.m | 41 +-- SoObjects/SOGo/SOGoObject.m | 16 +- SoObjects/SOGo/SOGoUser.m | 20 +- .../MailPartViewers/UIxMailPartICalViewer.wox | 2 +- 16 files changed, 508 insertions(+), 129 deletions(-) create mode 100755 Scripts/sogo-user.pl diff --git a/Main/SOGo.m b/Main/SOGo.m index ac530d561..afb19b095 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -42,6 +42,7 @@ #import +#import #import #import #import @@ -56,6 +57,7 @@ @interface SOGo : SoApplication { NSMutableDictionary *localeLUT; + SOGoCache *cache; } - (NSDictionary *) currentLocaleConsideringLanguages:(NSArray *)_langs; @@ -390,7 +392,9 @@ static BOOL debugObjectAllocation = NO; static NSArray *runLoopModes = nil; WOResponse *resp; + cache = [SOGoCache sharedCache]; resp = [super dispatchRequest: _request]; + [SOGoCache killCache]; if (![self isTerminating]) { diff --git a/SOPE/GDLContentStore/GCSFolder.h b/SOPE/GDLContentStore/GCSFolder.h index 73b608cc4..7e5ea61bd 100644 --- a/SOPE/GDLContentStore/GCSFolder.h +++ b/SOPE/GDLContentStore/GCSFolder.h @@ -104,11 +104,8 @@ - (NSArray *)subFolderNames; - (NSArray *)allSubFolderNames; -- (NSNumber *)versionOfContentWithName:(NSString *)_name; -- (NSCalendarDate *)creationDateOfEntryWithName:(NSString *)_name; -- (NSCalendarDate *)lastModificationOfEntryWithName:(NSString *)_name; +- (NSDictionary *) recordOfEntryWithName: (NSString *) name; -- (NSString *)fetchContentWithName:(NSString *)_name; - (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name baseVersion:(unsigned int)_baseVersion; - (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name; diff --git a/SOPE/GDLContentStore/GCSFolder.m b/SOPE/GDLContentStore/GCSFolder.m index f47591e7d..552c322b0 100644 --- a/SOPE/GDLContentStore/GCSFolder.m +++ b/SOPE/GDLContentStore/GCSFolder.m @@ -251,15 +251,14 @@ static GCSStringFormatter *stringFormatter = nil; recursive:YES]; } -- (id) _fetchValueOfColumn: (NSString *)_col - inContentWithName: (NSString *)_name - ignoreDeleted: (BOOL) ignoreDeleted +- (NSDictionary *) _fetchValueOfColumns: (NSArray *) _cols + inContentWithName: (NSString *) _name + ignoreDeleted: (BOOL) ignoreDeleted { EOAdaptorChannel *channel; NSException *error; NSDictionary *row; NSArray *attrs; - NSString *result; NSMutableString *sql; if ((channel = [self acquireStoreChannel]) == nil) { @@ -271,12 +270,13 @@ static GCSStringFormatter *stringFormatter = nil; sql = [NSMutableString stringWithFormat: @"SELECT %@" @" FROM %@" @" WHERE c_name = '%@'", - _col, [self storeTableName], _name]; + [_cols componentsJoinedByString: @","], + [self storeTableName], _name]; if (ignoreDeleted) [sql appendString: @" AND (c_deleted != 1 OR c_deleted IS NULL)"]; /* run SQL */ - + if ((error = [channel evaluateExpressionX:sql]) != nil) { [self errorWithFormat:@"%s: cannot execute SQL '%@': %@", __PRETTY_FUNCTION__, sql, error]; @@ -286,51 +286,64 @@ static GCSStringFormatter *stringFormatter = nil; /* fetch results */ - result = nil; attrs = [channel describeResults:NO /* do not beautify names */]; - if ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil) { - result = [[[row objectForKey:_col] copy] autorelease]; - if (![result isNotNull]) result = nil; + row = [channel fetchAttributes: attrs withZone: NULL]; + if (row) [channel cancelFetch]; - } /* release and return result */ [self releaseChannel:channel]; - return result; + + return row; } -- (NSNumber *)versionOfContentWithName:(NSString *)_name { - return [self _fetchValueOfColumn:@"c_version" inContentWithName:_name - ignoreDeleted: YES]; +- (NSDictionary *) recordOfEntryWithName: (NSString *) name +{ + NSDictionary *row; + NSMutableDictionary *record; + NSArray *columns; + NSString *strValue; + int intValue; + + columns = [NSArray arrayWithObjects: @"c_content", @"c_version", + @"c_creationdate", @"c_lastmodified", nil]; + row = [self _fetchValueOfColumns: columns + inContentWithName: name + ignoreDeleted: YES]; + if (row) + { + record = [NSMutableDictionary dictionaryWithCapacity: 5]; + strValue = [row objectForKey: @"c_content"]; + if (![strValue isNotNull]) + strValue = @""; + [record setObject: strValue forKey: @"c_content"]; + [record setObject: [row objectForKey: @"c_version"] + forKey: @"c_version"]; + intValue = [[row objectForKey: @"c_creationdate"] intValue]; + [record + setObject: [NSCalendarDate dateWithTimeIntervalSince1970: intValue] + forKey: @"c_creationdate"]; + intValue = [[row objectForKey: @"c_lastmodified"] intValue]; + [record + setObject: [NSCalendarDate dateWithTimeIntervalSince1970: intValue] + forKey: @"c_lastmodified"]; + } + else + record = nil; + + return record; } -- (NSCalendarDate *)creationDateOfEntryWithName:(NSString *)_name { - int seconds; +- (NSNumber *) deletionOfEntryWithName: (NSString *) name +{ + NSDictionary *row; - seconds = [[self _fetchValueOfColumn:@"c_creationdate" inContentWithName:_name - ignoreDeleted: YES] intValue]; + row = [self _fetchValueOfColumns: [NSArray arrayWithObject: @"c_deleted"] + inContentWithName: name + ignoreDeleted: NO]; - return [NSCalendarDate dateWithTimeIntervalSince1970: seconds]; -} - -- (NSCalendarDate *)lastModificationOfEntryWithName:(NSString *)_name { - int seconds; - - seconds = [[self _fetchValueOfColumn:@"c_lastmodified" inContentWithName:_name - ignoreDeleted: YES] intValue]; - - return [NSCalendarDate dateWithTimeIntervalSince1970: seconds]; -} - -- (NSNumber *)deletionOfContentWithName:(NSString *)_name { - return [self _fetchValueOfColumn:@"c_deleted" inContentWithName:_name - ignoreDeleted: NO]; -} - -- (NSString *)fetchContentWithName:(NSString *)_name { - return [self _fetchValueOfColumn:@"c_content" inContentWithName:_name - ignoreDeleted: YES]; + return [row objectForKey: @"c_deleted"]; } - (NSDictionary *)fetchContentsOfAllFiles { @@ -571,6 +584,7 @@ static GCSStringFormatter *stringFormatter = nil; { EOAdaptorChannel *storeChannel, *quickChannel; NSMutableDictionary *quickRow, *contentRow; + NSDictionary *currentRow; GCSFieldExtractor *extractor; NSException *error; NSNumber *storedVersion; @@ -598,19 +612,24 @@ static GCSStringFormatter *stringFormatter = nil; if (doLogStore) [self logWithFormat:@"should store content: '%@'\n%@", _name, _content]; - storedVersion = [self versionOfContentWithName:_name]; + currentRow = [self _fetchValueOfColumns: [NSArray arrayWithObjects: + @"c_version", + @"c_deleted", nil] + inContentWithName: _name + ignoreDeleted: NO]; + storedVersion = [currentRow objectForKey: @"c_version"]; if (doLogStore) [self logWithFormat:@" version: %@", storedVersion]; isNewRecord = [storedVersion isNotNull] ? NO : YES; if (!isNewRecord) { - if ([[self deletionOfContentWithName:_name] intValue] > 0) + if ([[currentRow objectForKey: @"c_deleted"] intValue] > 0) { [self _purgeRecordWithName: _name]; isNewRecord = YES; } } - + /* check whether sequence matches */ if (_baseVersion != 0 /* use 0 to override check */) { if (_baseVersion != [storedVersion unsignedIntValue]) { diff --git a/SOPE/sope-patchset-r1557.diff b/SOPE/sope-patchset-r1557.diff index 126d44d38..413caf2e0 100644 --- a/SOPE/sope-patchset-r1557.diff +++ b/SOPE/sope-patchset-r1557.diff @@ -1,3 +1,47 @@ +Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m +=================================================================== +--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (révision 1557) ++++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (copie de travail) +@@ -713,6 +713,39 @@ + return ms; + } + ++/* GCSEOAdaptorChannel protocol */ ++static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \ ++ @" c_name VARCHAR (256) NOT NULL,\n" ++ @" c_content VARCHAR (100000) NOT NULL,\n" ++ @" c_creationdate INT4 NOT NULL,\n" ++ @" c_lastmodified INT4 NOT NULL,\n" ++ @" c_version INT4 NOT NULL,\n" ++ @" c_deleted INT4 NULL\n" ++ @")"); ++static NSString *sqlFolderACLFormat = (@"CREATE TABLE %@ (\n" \ ++ @" c_uid VARCHAR (256) NOT NULL,\n" ++ @" c_object VARCHAR (256) NOT NULL,\n" ++ @" c_role VARCHAR (80) NOT NULL\n" ++ @")"); ++ ++- (NSException *) createGCSFolderTableWithName: (NSString *) tableName ++{ ++ NSString *sql; ++ ++ sql = [NSString stringWithFormat: sqlFolderFormat, tableName]; ++ ++ return [self evaluateExpressionX: sql]; ++} ++ ++- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName ++{ ++ NSString *sql; ++ ++ sql = [NSString stringWithFormat: sqlFolderACLFormat, tableName]; ++ ++ return [self evaluateExpressionX: sql]; ++} ++ + @end /* PostgreSQL72Channel */ + + @implementation PostgreSQL72Channel(PrimaryKeyGeneration) Index: sope-mime/NGImap4/NGImap4Connection.m =================================================================== --- sope-mime/NGImap4/NGImap4Connection.m (révision 1557) @@ -470,11 +514,14 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m =================================================================== --- sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (révision 1557) +++ sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m (copie de travail) -@@ -19,88 +19,30 @@ +@@ -19,88 +19,45 @@ 02111-1307, USA. */ ++#ifdef HAVE_STRNDUP +#define _GNU_SOURCE 1 ++#endif ++ +#include + #include "NGMimeHeaderFieldParser.h" @@ -483,6 +530,18 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m #include "common.h" -#include ++#ifndef HAVE_STRNDUP ++char *strndup(const char *str, size_t len) ++{ ++ char *dup = (char *)malloc(len+1); ++ if (dup) { ++ strncpy(dup,str,len); ++ dup[len]= '\0'; ++ } ++ return dup; ++} ++#endif ++ @implementation NGMimeRFC822DateHeaderFieldParser -static Class CalDateClass = Nil; @@ -567,7 +626,7 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m } /* -@@ -147,162 +89,110 @@ +@@ -147,162 +104,110 @@ } } @@ -815,7 +874,7 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m /* remove leading chars (skip to first digit, the day of the month) */ while (length > 0 && (!isdigit(*bytes))) { bytes++; -@@ -312,7 +202,7 @@ +@@ -312,7 +217,7 @@ if (length == 0) { NSLog(@"WARNING(%s): empty value for header field %@ ..", __PRETTY_FUNCTION__, _field); @@ -824,7 +883,7 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m } // TODO: should be a category on NSCalendarDate -@@ -435,7 +325,7 @@ +@@ -435,7 +340,7 @@ for (pe = bytes; isalnum(*pe) || *pe == '-' || *pe == '+'; pe++) ; *pe = '\0'; @@ -833,7 +892,7 @@ Index: sope-mime/NGMime/NGMimeRFC822DateHeaderFieldParser.m [self logWithFormat: @"WARNING: failed to parse RFC822 timezone: '%s' (value='%@')", bytes, _data]; -@@ -444,9 +334,9 @@ +@@ -444,9 +349,9 @@ /* construct and return */ finished: @@ -897,6 +956,22 @@ Index: sope-mime/NGMime/NGMimeBodyPart.m } - (NSString *)contentId { +Index: sope-mime/NGMime/GNUmakefile.preamble +=================================================================== +--- sope-mime/NGMime/GNUmakefile.preamble (révision 1557) ++++ sope-mime/NGMime/GNUmakefile.preamble (copie de travail) +@@ -5,6 +5,11 @@ + -DLIBRARY_MINOR_VERSION=${MINOR_VERSION} \ + -DLIBRARY_SUBMINOR_VERSION=${SUBMINOR_VERSION} \ + ++ifeq ($(patsubstr GNU/%,glibc,$(shell uname -o)),glibc) ++ADDITIONAL_CPPFLAGS += \ ++ -DHAVE_STRNDUP ++endif ++ + NGMime_INCLUDE_DIRS += \ + -I.. -I../.. \ + -I../../sope-core/NGStreams/ \ Index: sope-mime/NGMime/NGMimeBodyParser.m =================================================================== --- sope-mime/NGMime/NGMimeBodyParser.m (révision 1557) @@ -1127,50 +1202,6 @@ Index: sope-mime/NGMime/NGMimeContentDispositionHeaderFieldGenerator.m } return data; } -Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m -=================================================================== ---- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (révision 1557) -+++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (copie de travail) -@@ -713,6 +713,39 @@ - return ms; - } - -+/* GCSEOAdaptorChannel protocol */ -+static NSString *sqlFolderFormat = (@"CREATE TABLE %@ (\n" \ -+ @" c_name VARCHAR (256) NOT NULL,\n" -+ @" c_content VARCHAR (100000) NOT NULL,\n" -+ @" c_creationdate INT4 NOT NULL,\n" -+ @" c_lastmodified INT4 NOT NULL,\n" -+ @" c_version INT4 NOT NULL,\n" -+ @" c_deleted INT4 NULL\n" -+ @")"); -+static NSString *sqlFolderACLFormat = (@"CREATE TABLE %@ (\n" \ -+ @" c_uid VARCHAR (256) NOT NULL,\n" -+ @" c_object VARCHAR (256) NOT NULL,\n" -+ @" c_role VARCHAR (80) NOT NULL\n" -+ @")"); -+ -+- (NSException *) createGCSFolderTableWithName: (NSString *) tableName -+{ -+ NSString *sql; -+ -+ sql = [NSString stringWithFormat: sqlFolderFormat, tableName]; -+ -+ return [self evaluateExpressionX: sql]; -+} -+ -+- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName -+{ -+ NSString *sql; -+ -+ sql = [NSString stringWithFormat: sqlFolderACLFormat, tableName]; -+ -+ return [self evaluateExpressionX: sql]; -+} -+ - @end /* PostgreSQL72Channel */ - - @implementation PostgreSQL72Channel(PrimaryKeyGeneration) Index: sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m =================================================================== --- sope-core/NGExtensions/FdExt.subproj/NSString+Encoding.m (révision 1557) diff --git a/Scripts/sogo-user.pl b/Scripts/sogo-user.pl new file mode 100755 index 000000000..f5aaf6028 --- /dev/null +++ b/Scripts/sogo-user.pl @@ -0,0 +1,292 @@ +#/usr/bin/perl + +# TODO: +# - partial delete: appointment tables only, contact tables only +# - stats: nb of calendars, nb of events, nb of events with +# participants, nb of recurrent events, nb of contacts, nb of +# addressbooks, nb of contacts, etc + +=head1 NAME + +sogo-user + +=head1 SYNOPSIS + +create-ldap-fburl [options] username ... + + Options: + -help brief help message + -c, --config=FILE read configuration from FILE + -i, --info show information on the user + --delete delete user from SOGo databases + --fburl update Free-Busy URL in LDAP directory + -v, --verbose be verbose + +=head1 DESCRIPTION + +Apply various operations on a user with respect to her/his +SOGo environment. + +=head1 AUTHOR + +=over + +=item Francis Lachapelle + +=head1 COPYRIGHT + +Copyright (c) 2007 Inverse groupe conseil + +This program is available under the GPL. + +=cut + +#use diagnostics; +use strict; +use warnings; + +use Config::IniFiles; +#use Data::Dumper; +use DBI; +use Getopt::Long qw(:config bundling); +#use Log::Log4perl; +use Net::LDAP; +use Pod::Usage; + +use constant { + CONF_FILE => "sogo.conf" + }; + +my $help = ''; +my $configfile = CONF_FILE; +my $info = ''; +my $delete = ''; +my $fburl = ''; +my $verbose = ''; + +GetOptions( + "help|?" => \$help, + "config|c=s" => \$configfile, + "info|i" => \$info, + "delete" => \$delete, + "fburl" => \$fburl, + "verbose|v" => \$verbose +) or pod2usage( -verbose => 1); + +pod2usage( -verbose => 2) if $help; +pod2usage( -verbose => 1) unless ($help || $info || $delete || $fburl); +pod2usage( -verbose => 1) if ($#ARGV < 0); + +my $config; +my $dbh; +my $ldap; + +$config = new Config::IniFiles( -file => $configfile, -nocase => 1); +die "ERROR: Can't read configuration file $configfile: $!\n" unless $config; + +die "ERROR: Missing \"url\" in section [sogo]\n" unless ($config->val('sogo', 'url')); + +# Verify LDAP parameters +foreach ('host', 'port', 'binddn', 'password', 'searchbase', 'uid_attr', 'mail_attr', 'fburl_attr') { + unless ($config->val('ldap', $_)) { + die "ERROR: Missing value for parameter \"$_\" in section [ldap].\n"; + } +} + +# Verify DB parameters +foreach ('uri', 'username', 'password') { + unless ($config->val('database', $_)) { + die "ERROR: Missing value for parameter \"$_\" in section [database].\n"; + } +} + +## +# Functions + +sub initDatabase +{ + $dbh = DBI->connect($config->val('database', 'uri'), + $config->val('database', 'username'), + $config->val('database', 'password'), + { AutoCommit => 1, PrintError => 0 }) or die "ERROR: Can't connect to database: $DBI::errstr\n"; +} + +sub initLdap +{ + $ldap = new Net::LDAP($config->val('ldap', 'host')) + or die "ERROR: Can't connect to ldap: $@\n"; + + my $msg = $ldap->bind($config->val('ldap', 'binddn'), + password => $config->val('ldap', 'password')); + if ($msg->is_error()) { + die "ERROR: Can't bind to ldap: ".$msg->error()."\n"; + } +} + +sub info +{ + my $username = shift; + my $results; + my @entries; + my $hash_ref; + + &initLdap() unless $ldap; + &initDatabase() unless $dbh; + + # Fetch LDAP attributes + $results = $ldap->search(base => $config->val('ldap', 'searchbase'), + scope => 'sub', + attrs => [$config->val('ldap', 'uid_attr'), + $config->val('ldap', 'mail_attr'), + $config->val('ldap', 'fburl_attr')], + filter => sprintf('(%s=%s)', $config->val('ldap', 'uid_attr'), $username)); + if ($results->is_error()) { + die "ERROR: Can't perform ldap search: ",$results->error(),"\n"; + } + @entries = $results->entries; + die "ERROR: Unknown user $username\n" if ($#entries < 0); + foreach my $entry (@entries) { + print "Username: ", $entry->get_value( $config->val('ldap', 'uid_attr') ), "\n"; + print "Mail: ", $entry->get_value( $config->val('ldap', 'mail_attr') ), "\n"; + print "Freebusy URL: ", $entry->get_value( $config->val('ldap', 'fburl_attr') ) || "(undefined)", "\n"; + } + + # Retrive database tables information + $hash_ref = $dbh->selectall_hashref("select c_folder_id, c_folder_type, c_location, c_quick_location, c_acl_location from sogo_folder_info where c_path2 = ?", ($config->val('database', 'uri') =~ /^dbi:Pg/)?'c_folder_id':'C_FOLDER_ID', undef, ($username)) + or die "Can't execute select statement: $DBI::errstr\n"; + + print "Tables:\n"; + foreach my $id (keys %{$hash_ref}) { + print "\tType ", ($hash_ref->{$id}->{'C_FOLDER_TYPE'} || $hash_ref->{$id}->{'c_folder_type'}) + , ", ID ", $id, "\n"; + foreach my $col ('C_LOCATION', 'C_QUICK_LOCATION', 'C_ACL_LOCATION') { + print "\t\t", ($hash_ref->{$id}->{$col} || $hash_ref->{$id}->{lc($col)}), "\n"; + } + } + print "\t(no table found)\n" unless (%{$hash_ref}); +} + +sub delete +{ + my $username = shift; + my $hash_ref; + + &initDatabase() unless $dbh; + + # Select entries from sogo_folder_info + $hash_ref = $dbh->selectall_hashref("select C_UID from sogo_user_profile where C_UID = ?", + ($config->val('database', 'uri') =~ /^dbi:Pg/)?'c_uid':'C_UID', + undef, ($username)) + or die "ERROR: Can't execute select statement: $DBI::errstr\n"; + + if (%{$hash_ref}) { + # Delete entries from sogo_user_profile + $dbh->do("delete from sogo_user_profile where c_uid = ?", undef, ($username)) + or die "ERROR: Can't delete entries from sogo_user_profile: $DBI::errstr\n"; + } + else { + warn "No entries in sogo_user_profile\n"; + } + + # Select entries from sogo_folder_info + $hash_ref = $dbh->selectall_hashref("select c_folder_id, c_folder_type, c_location, c_quick_location, c_acl_location from sogo_folder_info where c_path2 = ?", ($config->val('database', 'uri') =~ /^dbi:Pg/)?'c_folder_id':'C_FOLDER_ID', undef, ($username)) + or die "Can't execute select statement: $DBI::errstr\n"; + + if (%{$hash_ref}) { + # Delete entries from sogo_folder_info + $dbh->do("delete from sogo_folder_info where c_path2 = ?", undef, ($username)) + or die "Can't delete entries from sogo_info_folder: $DBI::errstr\n"; + } + else { + die "No entries in sogo_folder_info\n"; + } + + # Drop tables + foreach my $id (keys %{$hash_ref}) { + print "Folder ID $id, type ",($hash_ref->{$id}->{'C_FOLDER_TYPE'} || $hash_ref->{$id}->{'c_folder_type'}),"\n"; + foreach my $col ('C_LOCATION', 'C_QUICK_LOCATION', 'C_ACL_LOCATION') { + $col = lc($col) unless ($hash_ref->{$id}->{$col}); + + if ($hash_ref->{$id}->{$col} =~ m#([^:]+)://([^:]+):([^@]+)@([^:]+):([^/]+)/([^/]+)/([^/]+)#) { + my ($type, $username, $password, $host, $port, $db, $table) = ($1, $2, $3, $4, $5, $6, $7); + my $uri; + print "Dropping ",$hash_ref->{$id}->{$col},"\n"; + $uri = 'dbi:Pg:host=%s;port=%s;dbname=%s' if ($type eq 'http'); + $uri = 'dbi:Oracle:host=%s;port=%s;sid=%s' if ($type eq 'oracle'); + my $s = DBI->connect(sprintf($uri, $host, $port, $db), $username, $password, { AutoCommit => 1, PrintError => 0 }) or die "\tCan't connect: $!\n"; + $s->do("drop table $table") or warn "\tERROR: Can't drop table $table: $DBI::errstr\n"; + $s->disconnect(); + } + } + } +} + +sub fburl +{ + my $username = shift; + my $results; + my @entries; + my $modified = ''; + + &initLdap() unless $ldap; + + $results = $ldap->search(base => $config->val('ldap', 'searchbase'), + scope => 'sub', + attrs => ['objectClass', + $config->val('ldap', 'uid_attr'), + $config->val('ldap', 'mail_attr'), + $config->val('ldap', 'fburl_attr')], + filter => sprintf('(%s=%s)', $config->val('ldap', 'uid_attr'), $username)); + if ($results->is_error()) { + die "ERROR: Can't perform ldap search: ",$results->error(),"\n"; + } + @entries = $results->entries; + die "ERROR: Unknown user $username\n" if ($#entries < 0); + foreach my $entry (@entries) { + my $caldav_objectclass = $config->val('ldap', 'caldav_objectclass'); + if (length($caldav_objectclass) > 0) { + # Add objectClass if not present + my @objectClasses = $entry->get_value('objectClass'); + #print "classes = ", join(", ", @objectClasses), "\n"; + unless (grep {/^$caldav_objectclass$/} @objectClasses) { + print "Adding objectClass ", $caldav_objectclass,"\n"; + $entry->add('objectClass' => $caldav_objectclass); + $modified = 1; + } + } + # Add Freebusy URL if not present + my $fburl_attr = $config->val('ldap', 'fburl_attr'); + if ($entry->get_value($fburl_attr)) { + print "Freebusy URL already defined (",$entry->get_value($fburl_attr),")\n"; + } + else { + my $fburl = $config->val('sogo', 'url') . "/SOGo/dav/$username/freebusy.ifb"; + print "Adding attribute ", $fburl_attr, " with value ", $fburl, "\n"; + $entry->add($fburl_attr => $fburl); + $modified = 1; + } + if ($modified) { + # Perform update + my $msg = $entry->update($ldap); + if ($msg->is_error()) { + print "ERROR: Can't update entry: \n"; + print $entry->dump(); + print $msg->error(),"\n"; + } + } + } +} + +## +# Main + +foreach my $username (@ARGV) { + #print $username,"\n"; + + &info($username) if $info; + &delete($username) if $delete; + &fburl($username) if $fburl; +} + +$dbh->disconnect if $dbh; +$ldap->unbind if $ldap; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 5be6e5588..56fe902e5 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -48,6 +48,7 @@ #import // #import +#import #import #import #import @@ -299,8 +300,8 @@ static NSNumber *sharedYes = nil; [r setStatus: 207]; [r setContentEncoding: NSUTF8StringEncoding]; [r setHeader: @"text/xml; charset=\"utf-8\"" forKey: @"content-type"]; - [r setHeader: @"no-cache" forKey: @"pragma"]; - [r setHeader: @"no-cache" forKey: @"cache-control"]; +// [r setHeader: @"no-cache" forKey: @"pragma"]; +// [r setHeader: @"no-cache" forKey: @"cache-control"]; [r appendContentString:@"\r\n"]; [r appendContentString: @"\r\n"]; @@ -403,6 +404,11 @@ static NSNumber *sharedYes = nil; obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */]; } + if (obj) + [[SOGoCache sharedCache] registerObject: obj + withName: _key + inContainer: container]; + return obj; } diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index c8ed4eb3a..33dd2b6e6 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -126,7 +126,7 @@ static BOOL sendEMailNotifications = NO; sm = [SoSecurityManager sharedSecurityManager]; if (![sm validatePermission: SOGoCalendarPerm_ViewAllComponent onObject: self inContext: context]) - iCalString = content; + iCalString = [record objectForKey: @"c_content"]; else if (![sm validatePermission: SOGoCalendarPerm_ViewDAndT onObject: self inContext: context]) { @@ -165,7 +165,7 @@ static BOOL sendEMailNotifications = NO; if (secure) iCalString = [self secureContentAsString]; else - iCalString = content; + iCalString = [record objectForKey: @"c_content"]; if ([iCalString length] > 0) calendar = [iCalCalendar parseSingleFromSource: iCalString]; diff --git a/SoObjects/Contacts/SOGoContactGCSEntry.m b/SoObjects/Contacts/SOGoContactGCSEntry.m index b56e5a928..b4b8be092 100644 --- a/SoObjects/Contacts/SOGoContactGCSEntry.m +++ b/SoObjects/Contacts/SOGoContactGCSEntry.m @@ -48,8 +48,11 @@ - (NGVCard *) vCard { + NSString *content; + if (!card) { + content = [record objectForKey: @"c_content"]; if ([[content uppercaseString] hasPrefix: @"BEGIN:VCARD"]) card = [NGVCard parseSingleFromSource: content]; else diff --git a/SoObjects/Mailer/SOGoMailAccounts.m b/SoObjects/Mailer/SOGoMailAccounts.m index b09e9092b..515c66523 100644 --- a/SoObjects/Mailer/SOGoMailAccounts.m +++ b/SoObjects/Mailer/SOGoMailAccounts.m @@ -104,7 +104,7 @@ static NSString *AgenorShareLoginMarker = @".-."; /* first check attributes directly bound to the application */ if ((obj = [super lookupName:_key inContext:_ctx acquire:NO])) return obj; - + if (![self isInHomeFolderBranchOfLoggedInAccount: userLogin]) { [self warnWithFormat:@ "User %@ tried to access mail hierarchy of %@", userLogin, [container nameInContainer]]; diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index 6c38725f0..ec0c28036 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -850,7 +850,7 @@ static BOOL debugSoParts = NO; } else clazz = Nil; - + return [clazz objectWithName:_key inContainer: self]; } diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index c99c714e0..526cb2264 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -20,6 +20,7 @@ libSOGo_HEADER_FILES_INSTALL_DIR = /SOGo FHS_HEADER_DIRS = SOGo libSOGo_HEADER_FILES = \ + SOGoCache.h \ SOGoObject.h \ SOGoContentObject.h \ SOGoFolder.h \ @@ -56,6 +57,7 @@ libSOGo_HEADER_FILES = \ WORequest+SOGo.h libSOGo_OBJC_FILES = \ + SOGoCache.m \ SOGoObject.m \ SOGoContentObject.m \ SOGoFolder.m \ diff --git a/SoObjects/SOGo/SOGoContentObject.h b/SoObjects/SOGo/SOGoContentObject.h index 807b98745..3f4cb53ef 100644 --- a/SoObjects/SOGo/SOGoContentObject.h +++ b/SoObjects/SOGo/SOGoContentObject.h @@ -31,7 +31,7 @@ @interface SOGoContentObject : SOGoObject { NSString *ocsPath; - NSString *content; + NSDictionary *record; BOOL isNew; } diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index 9665eda90..4aa485035 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -49,9 +49,9 @@ if ((self = [super initWithName: newName inContainer: newContainer])) { ocsPath = nil; - content = [[self ocsFolder] fetchContentWithName: newName]; - [content retain]; - isNew = (!content); + record = [[self ocsFolder] recordOfEntryWithName: newName]; + [record retain]; + isNew = (!record); } return self; @@ -59,19 +59,11 @@ - (void) dealloc { - [content release]; + [record release]; [ocsPath release]; [super dealloc]; } -/* notifications */ - -- (void) sleep -{ - [content release]; content = nil; - [super sleep]; -} - /* accessors */ - (BOOL) isFolderish @@ -136,19 +128,25 @@ - (NSString *) contentAsString { - return content; + return [record objectForKey: @"c_content"]; } - (NSException *) saveContentString: (NSString *) newContent baseVersion: (unsigned int) newBaseVersion { /* Note: "iCal multifolder saves" are implemented in the apt subclass! */ - GCSFolder *folder; + GCSFolder *folder; NSException *ex; + NSMutableDictionary *newRecord; ex = nil; - ASSIGN (content, newContent); + if (record) + newRecord = [NSMutableDictionary dictionaryWithDictionary: record]; + else + newRecord = [NSMutableDictionary dictionary]; + [newRecord setObject: newContent forKey: @"c_content"]; + ASSIGN (record, newRecord); folder = [container ocsFolder]; if (folder) @@ -307,7 +305,7 @@ folder = [self ocsFolder]; if (folder) { - versionValue = [folder versionOfContentWithName: [self nameInContainer]]; + versionValue = [record objectForKey: @"c_version"]; sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]); entityTag = [NSString stringWithCString: buf]; } @@ -325,7 +323,7 @@ { NSCalendarDate *date; - date = [[self ocsFolder] creationDateOfEntryWithName: nameInContainer]; + date = [record objectForKey: @"c_creationdate"]; return [date rfc822DateString]; } @@ -334,16 +332,19 @@ { NSCalendarDate *date; - date = [[self ocsFolder] lastModificationOfEntryWithName: nameInContainer]; + date = [record objectForKey: @"c_lastmodified"]; return [date rfc822DateString]; } - (NSString *) davContentLength { + NSString *content; + + content = [record objectForKey: @"c_content"]; + return [NSString stringWithFormat: @"%u", - [content - lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]]; + [content lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]]; } - (NSException *) davMoveToTargetObject: (id) _target diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index 8bb510661..9c5fbcee6 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -62,6 +62,7 @@ #import "NSDictionary+Utilities.h" #import "NSString+Utilities.h" +#import "SOGoCache.h" #import "SOGoObject.h" @interface SOGoObject(Content) @@ -560,10 +561,19 @@ static BOOL kontactGroupDAV = YES; acquire: (BOOL) acquire { id obj; + SOGoCache *cache; - obj = [[self soClass] lookupKey: lookupName inContext: localContext]; - if (obj) - [obj bindToObject: self inContext: localContext]; + cache = [SOGoCache sharedCache]; + obj = [cache objectNamed: lookupName inContainer: self]; + if (!obj) + { + obj = [[self soClass] lookupKey: lookupName inContext: localContext]; + if (obj) + { + [obj bindToObject: self inContext: localContext]; + [cache registerObject: obj withName: lookupName inContainer: self]; + } + } return obj; } diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 9c7bd9d3d..fe0aa2a6a 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -32,10 +32,11 @@ #import "AgenorUserDefaults.h" #import "LDAPUserManager.h" +#import "NSArray+Utilities.h" +#import "SOGoCache.h" #import "SOGoDateFormatter.h" #import "SOGoObject.h" #import "SOGoPermissions.h" -#import "NSArray+Utilities.h" #import "SOGoUser.h" @@ -124,10 +125,18 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; + (SOGoUser *) userWithLogin: (NSString *) newLogin roles: (NSArray *) newRoles { + SOGoCache *cache; SOGoUser *user; - user = [[self alloc] initWithLogin: newLogin roles: newRoles]; - [user autorelease]; + cache = [SOGoCache sharedCache]; + user = [cache userNamed: newLogin]; + if (!user) + { + user = [[self alloc] initWithLogin: newLogin roles: newRoles]; + [user autorelease]; + [cache registerUser: user]; + } + [user setPrimaryRoles: newRoles]; return user; } @@ -181,6 +190,11 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; [super dealloc]; } +- (void) setPrimaryRoles: (NSArray *) newRoles +{ + ASSIGN (roles, newRoles); +} + - (void) setCurrentPassword: (NSString *) newPassword { ASSIGN (currentPassword, newPassword); diff --git a/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox b/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox index 14c3cf60f..701eef9d4 100644 --- a/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox +++ b/UI/Templates/MailPartViewers/UIxMailPartICalViewer.wox @@ -10,7 +10,7 @@ + var:value="pathToAttachment"/>