merge of '7659385c293a64fbec59fda5d5bf34bcd7b51d96'

and 'f8b29d68c36441f92d7ffe8056a95fd25a2fd409'

Monotone-Parent: 7659385c293a64fbec59fda5d5bf34bcd7b51d96
Monotone-Parent: f8b29d68c36441f92d7ffe8056a95fd25a2fd409
Monotone-Revision: 7822887b219614cd66e9d1e19da4799f884657bf

Monotone-Author: dgehl@inverse.ca
Monotone-Date: 2007-10-18T01:44:28
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Dominik Gehl
2007-10-18 01:44:28 +00:00
280 changed files with 14070 additions and 3743 deletions

445
ChangeLog
View File

@@ -1,7 +1,446 @@
2007-10-17 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MailerUI/UIxMailActions.m ([UIxMailActions -copyAction]):
implemented new web method.
* SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject
-copyToFolderNamed:folderNameinContext:]): new method with the
code cut/pasted from -moveToFolderNamed:inContext:.
([SOGoMailObject -moveToFolderNamed:folderNameinContext:]):
modified to use the code from -copyToFolderNamed:inContext:, which
is common between the two actions.
* SoObjects/Mailer/SOGoMailAccount.m ([SOGoMailAccount -draftsFolderNameInContext:_ctx])
([SOGoMailAccount -sentFolderNameInContext:])
([SOGoMailAccount -trashFolderNameInContext:]): modified to take
the user settings into account.
* UI/MailerUI/UIxMailFolderActions.m ([UIxMailFolderActions -setAsDraftsFolderAction])
([UIxMailFolderActions -setAsSentFolderAction])
([UIxMailFolderActions -setAsTrashFolderAction]): new web methods
that change the purpose of the active folder to "Sent", "Drafts"
or "Trash".
* UI/SOGoUI/SOGoACLAdvisory.m ([SOGoACLAdvisory -subject]):
returns the subject as quoted-printable.
* UI/SOGoUI/SOGoACLAdvisory.[hm]: added two intermediary classes:
SOGoACLAdditionAdvisory and SOGoACLRemovalAdvisory implementing
the "aclMethod" method for the subsequent language-dependent
subclasses.
* UI/SOGoUI/SOGoFolderAdvisory.m ([SOGoFolderAdvisory -subject]):
returns the subject as quoted-printable.
* UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor
-dealloc]): release item, aptStartDate and aptEndDate.
2007-10-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder
-initWithName:newNameinContainer:newContainer]): the owner of a
shared folder is set to "nobody" by default.
* UI/Common/UIxAclEditor.m ([UIxAclEditor -hasOwner]): new method
that returns whether the object has an owner or not.
2007-10-15 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoFolder.m ([SOGoFolder -ocsFolder]): create
the folder even if the current user is not its owner.
2007-10-10 Ludovic Marcotte <ludovic@inverse.ca>
* We now send advisory emails when folders
are created / deleted.
* Fixed the sending of advisory emails upon
ACL changes on folders.
2007-10-10 Ludovic Marcotte <ludovic@inverse.ca>
* UI/Scheduler/UIxComponentEditor.m
Implemented event/task priority support.
* SoObjects/Contacts/SOGoContactGCSFolder.m
Added CardDAV support.
* SoObjects/SOGo/LDAPUserManager.m and SOGoUser.m
Implemented From: based on LDAP results based on
the MailFieldNames attribute (an array) specified
in every LDAP-based authentication sources.
* UI/MailPartViewers/UIxMailPartTextViewer.m and
UI/WebServerResources/MailerUI.css
We avoid replacing "\r\n" and "\n" with <br /> and
rather use CSS capabilities for proper formatting.
This is _WAY_ faster on very large mails.
2007-10-10 Francis Lachapelle <flachapelle@inverse.ca>
* UI/Scheduler/UIxComponentEditor.m
([UIxComponentEditor -componentCalendar]): returns the calendar
object of the current event.
2007-10-05 Ludovic Marcotte <ludovic@inverse.ca>
* UI/WebServerResources/MailerUI.js
We check if at least one message is selected
before performing a Reply/Reply All/Forward
* SoObjects/Appointments/SOGoAppointmentFolder.m
and others - implemented support for recurring
events (with some known limitations right now,
all soon to be fixed).
2007-10-04 Francis Lachapelle <flachapelle@inverse.ca>
* Main/SOGo.m ([SOGo -isUserName:_keyinContext:_ctx]): removed
the constraint that a username can't start with a digit.
2007-10-02 Francis Lachapelle <flachapelle@inverse.ca>
* Moved SOPE/sope-gdl1/GDLContentStore from the default trunk
repository to Inverse's branch.
2007-09-28 Francis Lachapelle <flachapelle@inverse.ca>
* SoObjects/Mailer/SOGoDraftObject.m
([SOGoDraftObject -isValidAttachmentName:_name]): removed
constraint on space in file name.
([SOGoDraftObject -saveAttachment:_attachwithMetadata:metadata]):
now removes from file name all characters preceding a backslash.
This happens with IE7 because the complete attachment file path
is sent.
2007-09-25 Francis Lachapelle <flachapelle@inverse.ca>
* SoObjects/Appointments/SOGoAptMailNotification.m
([SOGoAptMailNotification -appointmentURL]): set personal as the
default calendar where to add the event.
* UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage +initialize]):
activate the SOGoUIxDefaultModule user defaults.
2007-09-21 Francis Lachapelle <flachapelle@inverse.ca>
* UI/SOGoUI/UIxComponent.m
([UIxComponent -shortUserNameForDisplay]): returns the string
"wrongusernamepassword" when authentication failed.
2007-09-17 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MailPartViewers/UIxMailPartICalViewer.m
([UIxMailPartICalViewer -calendarFolder]): returns the "personal"
entry of the Calendars parent folder.
* UI/MailerUI/UIxMailListView.m ([UIxMailListView
-messageSubject]): new accessor method to work-around a problem
within SOPE where a subject could be returned as an NSData.
* SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder
-appendPersonalSources]): make sure the value of the "c_path4" of
the returned rows are not NSNull, otherwise, discard them.
2007-09-16 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Contacts/SOGoContactGCSFolder.m ([SOGoContactGCSFolder
-compare:otherFolder]): new overriden method that compares two
contact foldes based on their class and then transfer the control
to the super method in SOGoFolder.
* SoObjects/Contacts/SOGoContactLDAPFolder.m
([SOGoContactLDAPFolder -compare:otherFolder]): new method that
compare two contact folders based on their class and then their
display name.
* SoObjects/SOGo/SOGoFolder.m ([SOGoFolder -compare:otherFolder]):
new method for sorting folders. The folders are compared based on
their ownership, whether they are a main folder and finally
depending on their display name.
* SoObjects/SOGo/SOGoObject.m ([SOGoObject
-pathArrayToSOGoObject]): do not reorder the paths if the third
element is an instance of NSNull.
* SoObjects/SOGo/SOGoParentFolder.m ([SOGoParentFolder
-subFolders]): returns a sorted array using the "compare:"
selector.
2007-09-14 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxCalendarSelector.m ([UIxCalendarSelector
-calendars]): also returns the owner of the listed folders.
* SoObjects/Appointments/SOGoAppointmentFolder.m
([-deleteEntriesWithIds:ids]): moved method into SOGoFolder.
* UI/Scheduler/UIxCalMainView.m ([-batchDeleteAction]): moved
method into UIxFolderActions.
* SoObjects/Appointments/SOGoFreeBusyObject.m ([SOGoFreeBusyObject
-fetchFreeBusyInfosFrom:_startDateto:_endDate]): fetch the
freebusy info from the "personal" calendar.
* UI/Common/UIxParentFolderActions.m ([UIxParentFolderActions
-createFolderAction]): new standardized method for requesting
folder creations among gcs-based modules.
* UI/Common/UIxParentFolderActions.[hm]: new action class module.
* SoObjects/Appointments/SOGoAppointmentFolders.m: new class
module, equivalent to the SOGoParentFolder's child
SOGoContactFolders, but for calendars.
* SoObjects/SOGo/SOGoObject.m ([SOGoObject -labelForKey:key]): new
method that returns translated strings for controller bundles
(same as what UIxComponent does for view bundles).
([SOGoObject -pathArrayToSOGoObject]): new method that returns
the real path to a subscribed folder (if subscribed).
([SOGoObject +globallyUniqueObjectId]): move method from SOGoFolder.
([SOGoObject -globallyUniqueObjectId]): new instance method
calling its class equivalent.
2007-09-12 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MainUI/SOGoRootPage.m ([SOGoRootPage -defaultAction]): test
whether the user is logged in and if so, redirect to his/her
homepage.
([SOGoRootPage -appendToResponse:inContext:]): removed useless
method.
2007-09-11 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoFolder.m ([SOGoFolder
+folderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]):
new method.
([SOGoFolder -displayName]): new method.
([SOGoFolder -delete]): accept to proceed only if nameInContainer
!= "personal".
* SoObjects/Contacts/SOGoContactLDAPFolder.m
([SOGoContactLDAPFolder
+folderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]):
renamed from "contactFolderWithName..." for compatibility with SOGoFolder.
* SoObjects/Contacts/SOGoContactGCSFolder.m ([SOGoContactGCSFolder
+contactFolderWithName:aNameandDisplayName:aDisplayNameinContainer:aContainer]):
removed method, reimplemented in SOGoFolder.
([SOGoContactGCSFolder -displayName]): removed method,
reimplemented in SOGoFolder.
([-delete]): removed method, modified in SOGoFolder.
* SoObjects/Contacts/SOGoContactFolders.[hm]: modified class to be
a subclass of SOGoParentFolder.
* SoObjects/SOGo/SOGoParentFolder.[hm]: new class module derived
from SOGoContactFolders and modified to be more content-independent.
* UI/MailerUI/UIxMailActions.m ([UIxMailActions -markMessageUnreadAction])
([UIxMailActions -markMessageReadAction]): new methods moved from
UIxMailListView and adapted to invoke the client object directly,
since the previous versions had to to a lookup from the parent
SOGoMailFolder.
* UI/MailerUI/UIxMailListView.m ([-markMessageUnreadAction]): move
method into UIxMailActions.
([-markMessageReadAction]): same as above.
([-viewAction]): removed useless method.
([-javaScriptOK]): removed useless method.
([-isJavaScriptRequest]): removed useless method.
([-lookupActiveMessage]): removed useless method.
* UI/Common/WODirectAction+SOGo.m ([WODirectAction
-responseWithStatus:status]): new method that returns a WOResponse
initialized with the specified status code.
([WODirectAction -responseWith204]): new method that invokes the
above one with "204" as parameter.
([WODirectAction -redirectToLocation:newLocation]): rewrote method
to make use of -responseWithStatus:.
* UI/SOGoUI/UIxComponent.m ([UIxComponent -responseWith204]): new
method that returns a WOResponse initialized with the 204 status
code.
* UI/MailerUI/UIxMailListView.m ([UIxMailListView -sortedUIDs]):
always use a "not deleted" search qualifier along with the user
qualifier (if present).
2007-09-10 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Contacts/UIxContactFoldersView.m ([UIxContactFoldersView
-contactSearchAction]): only return the records which have an
email set.
* SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject
-trashInContext:_ctx]): no longer expunge the mailbox after
marking a message deleted.
([SOGoMailObject -moveToFolderNamed:folderNameinContext:]): same
as above.
* UI/MailerUI/UIxMailView.m ([-deleteAction]): removed method.
([-trashAction]): moved method into UIxMailActions.
([-moveAction]): moved method into UIxMailActions.
2007-09-07 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MailPartViewers/UIxMailPartHTMLViewer.m
([_UIxHTMLMailContentHandler
-endElement:_localNamenamespace:_nsrawName:_rawName]): remove HTML
comments from the CSS code, do not add the CSS code to the body
content and remove references of body from the CSS declarations.
([UIxMailPartHTMLViewer -cssContent]): new accessor method.
([UIxMailPartHTMLViewer -flatContentAsString]): separated code
common with cssContent in a different method and invoke it only
once.
* UI/MainUI/SOGoRootPage.[hm]: made a subclass of UIxComponent
instead of UIxPageFrame.
2007-09-06 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MainUI/SOGoRootPage.m ([-defaultAction]): commented out.
([-appendToResponse:responseinContext:ctx]): commented out.
([SOGoRootPage -connectURL]): new accessor that returns the full
url the the "connect" method.
([-connectAction]): rewrote method to return a properly formatted
auth. cookie based on the username and password passed as
parameter.
* UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]):
set the value of the cookie to "discard" and set its expiration
date to yesterday.
* SoObjects/SOGo/SOGoWebAuthenticator.m ([SOGoWebAuthenticator
-preprocessCredentialsInContext:context]): consider the user
anonymous if the cookie value is "discard".
([SOGoWebAuthenticator
-setupAuthFailResponse:responsewithReason:reasoninContext:context]):
set the expiration date of the cookie to yesterday.
* UI/SOGoUI/UIxComponent.m ([UIxComponent -applicationPath]):
returns the path to the application if the clientObject is not a
SOGoObject.
* SoObjects/SOGo/SOGoUserFolder.m ([SOGoUserFolder +initialize]):
moved the requirement of authentication from the SOGo application
class to here.
* SoObjects/Appointments/SOGoAppointmentObject.m
([SOGoAppointmentObject -saveContentString:_iCalbaseSequence:_v]):
check whether the new appointment object is still relevant before
sending a notification.
2007-09-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoWebAuthenticator.m ([SOGoWebAuthenticator
-setupAuthFailResponse:responsewithReason:reasoninContext:context]):
render the login page through the SoDefaultRenderer.
* UI/MainUI/SOGoRootPage.m ([SOGoRootPage
-isPublicInContext:localContext]): new overriden method that
returns YES.
* UI/Scheduler/UIxCalendarSelector.m ([UIxCalendarSelector
-currentCalendarLogin]): replace css-unsafe characters with _.
* UI/SOGoUI/UIxComponent.m ([UIxComponent
-shortUserNameForDisplay]): simplified method.
([-user]): removed method since [context activeUser] is as useful.
2007-09-04 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]):
set the cookie path to "/".
* Main/SOGo.m ([SOGo -authenticatorInContext:_ctx]): choose the
authenticator based on the request handler key. "dav" returns the
SOGoDAVAuthenticator, anything else returns the Web authenticator.
* SoObjects/SOGo/SOGoDAVAuthenticator.m: renamed module from
"SOGoAuthenticator".
* SoObjects/SOGo/SOGoWebAuthenticator.m: new class module
implementing a subclass of SoCookieAuthenticator, designed for
web-based cookie authentication of users.m
* UI/MainUI/SOGoUserHomePage.m ([SOGoUserHomePage -logoffAction]):
new method that resets the authentification cookie.
2007-08-29 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/LDAPSource.m ([LDAPSource
-checkLogin:loginToCheckandPassword:passwordToCheck]): initialize
didBind to NO to make sure no false authentication is returned if
the bind operation is not executed.
2007-08-28 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Mailer/SOGoDraftObject.m: added support for the
"In-Reply-To" header field when replying.
* UI/MainUI/SOGoUserHomePage.m: add the "c_" prefix to the quick
table field names that are queried.
* SoObjects/Appointments/SOGoFreeBusyObject.m ([SOGoFreeBusyObject
-iCalStringForFreeBusyInfos:_infosfrom:_startDateto:_endDate]):
add the "c_" prefix to the quick table field names that are
queried.
2007-08-24 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/Appointments/SOGoAppointmentFolder.m
([SOGoAppointmentFolder -lookupCalendarFolderForUID:uid]): add
"personal" to the ocs path of the appointment folder.
* UI/MailPartViewers/UIxMailPartViewer.m ([UIxMailPartViewer
-flatContentAsString]): use latin1 when the encoding is not
specified, and to reencode data chunk which were not correctly
decoded with the original charset.
* SoObjects/Appointments/SOGoAppointmentFolder.m ([SOGoAppointmentFolder -aclUsersForObjectAtPath:objectPathArray])
([SOGoAppointmentFolder -aclsForUser:uidforObjectAtPath:objectPathArray])
([SOGoAppointmentFolder -setRoles:rolesforUser:uidforObjectAtPath:objectPathArray])
([SOGoAppointmentFolder
-removeAclsForUsers:usersforObjectAtPath:objectPathArray]):
override those methods to use the "personal" additional directory.
* SoObjects/SOGo/SOGoUserFolder.m ([-ocsPrivateCalendarPath]):
append "/personal" to the calendar path to simulate a single
calendar in a choice of many.
* SoObjects/Mailer/SOGoMailFolder.m ([SOGoMailFolder
-lookupName:_keyinContext:acquire:_acquire]): moved the lookup
methods back here. Moved the folder existence check here, and do
it on self only when the lookup happens for a non-folder object.
This permits to accept entries for folders with parents who
don't really exist.
2007-08-23 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxTaskEditor.m ([UIxTaskEditor
-shouldTakeValuesFromRequest:requestinContext:context]): same as
below.
* UI/Scheduler/UIxAppointmentEditor.m ([UIxAppointmentEditor
-shouldTakeValuesFromRequest:requestinContext:context]):
redesigned method since any method called can be received from a
POST or a GET. Instead we check the method call itself and we
accept only if it has the "save" prefix.
* SoObjects/Appointments/SOGoAptMailNotification.m
([SOGoAptMailNotification -getSubject]): returns the subject an a
quoted-printable encoded string, if needed.
* SoObjects/Mailer/SOGoDraftObject.m ([NSString
-asQPSubjectString:encoding]): moved method into
NSString+Utilities.m.
2007-08-22 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/PreferencesUI/UIxPreferences.m ([UIxPreferences -messageForwardingList])
([UIxPreferences -itemMessageForwardingText])
* UI/PreferencesUI/UIxPreferences.m ([UIxPreferences
-messageForwardingList])
([UIxPreferences -itemMessageForwardingText])D
([UIxPreferences -userMessageForwarding])
([UIxPreferences -setUserMessageForwarding:newMessageForwarding]):
new template methods for manage the user preference regarding
@@ -3289,7 +3728,7 @@
2006-09-13 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Contacts/UIxContactView.m: added many wrapper methods to
display blocks of data à la Thunderbird Addressbook. If data is
display blocks of data à la Thunderbird Addressbook. If data is
available, those wrappers (around the NGVCard methods) will
enclose the results in a proper HTML output with the correct label
(if present), otherwise it will return an empty string.

View File

@@ -5,6 +5,7 @@ include $(GNUSTEP_MAKEFILES)/common.make
SUBPROJECTS = \
SOPE/NGCards \
SOPE/sope-gdl1/GDLContentStore \
OGoContentStore \
SoObjects \
Main \

View File

@@ -5,6 +5,9 @@ include $(GNUSTEP_MAKEFILES)/common.make
include ../Version
include ./Version
ADDITIONAL_INCLUDE_DIRS += -I../SOPE/sope-gdl1/
ADDITIONAL_LIB_DIRS += -L../SOPE/sope-gdl1/GDLContentStore/obj/
SOGOD = sogod-$(MAJOR_VERSION).$(MINOR_VERSION)
TOOL_NAME = $(SOGOD)

View File

@@ -41,10 +41,11 @@
#import <WEExtensions/WEResourceManager.h>
#import <SoObjects/SOGo/SOGoAuthenticator.h>
#import <SoObjects/SOGo/SOGoDAVAuthenticator.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/SOGo/SOGoUserFolder.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import <SoObjects/SOGo/SOGoWebAuthenticator.h>
#import "build.h"
#import "SOGoProductLoader.h"
@@ -103,7 +104,7 @@ static BOOL debugObjectAllocation = NO;
/* SoClass security declarations */
sInfo = [self soClassSecurityInfo];
/* require View permission to access the root (bound to authenticated ...) */
[sInfo declareObjectProtected: SoPerm_View];
// [sInfo declareObjectProtected: SoPerm_View];
/* to allow public access to all contained objects (subkeys) */
[sInfo setDefaultAccess: @"allow"];
@@ -231,9 +232,18 @@ static BOOL debugObjectAllocation = NO;
/* authenticator */
- (id) authenticatorInContext: (id) _ctx
- (id) authenticatorInContext: (WOContext *) context
{
return [$(@"SOGoAuthenticator") sharedSOGoAuthenticator];
id authenticator;
NSString *key;
key = [[context request] requestHandlerKey];
if ([key isEqualToString: @"dav"])
authenticator = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
else
authenticator = [SOGoWebAuthenticator sharedSOGoWebAuthenticator];
return authenticator;
}
/* name lookup */
@@ -244,9 +254,6 @@ static BOOL debugObjectAllocation = NO;
if ([_key length] < 1)
return NO;
if (isdigit([_key characterAtIndex:0]))
return NO;
return YES;
}

25
NEWS
View File

@@ -1,4 +1,27 @@
0.9.0-20070xxx
0.9.0-200710XX
--------------
- the user can now configure his folders as drafts, trash or sent folder;
- added the ability the move and copy message across mail folders;
0.9.0-200709XX
--------------
- implemented cookie-based identification in the web interface;
- fixed a bug where a false positive happening whenever a wrong user login was
given during an indirect bind;
- remove the constraint that a username can't begin with a digit;
- deleting a message no longer expunges its parent folder;
- implemented support for multiple calendars;
- it is now possible to rename folders;
- fixed search in message content;
- added tooltips for toolbar buttons (English and French);
- added checkmarks in live search options popup menus;
- added browser detection with recommanded alternatives;
- support for resizable columns in tables;
- improved support for multiple selection in tables and lists;
- improved IE7 and Safari support: attendees selector, email file attachments;
- countless bugfixes;
0.9.0-20070824
--------------
- added the ability to choose the default module from the
application settings: "Calendars", "Contacts" or "Mail";

View File

@@ -13,6 +13,9 @@ libOGoContentStore_HEADER_FILES_DIR = .
libOGoContentStore_HEADER_FILES_INSTALL_DIR = /OGoContentStore
# no headers, commented out: FHS_HEADER_DIRS = OGoContentStore
ADDITIONAL_INCLUDE_DIRS += -I../SOPE/sope-gdl1/
ADDITIONAL_LIB_DIRS += -L../SOPE/sope-gdl1/GDLContentStore/obj/
libOGoContentStore_OBJC_FILES += \
iCalEntityObject+OCS.m \
iCalRepeatableEntityObject+OCS.m \

View File

@@ -60,7 +60,7 @@
/* state */
- (void)resetExceptResult
- (void) resetExceptResult
{
if (content)
{
@@ -130,7 +130,7 @@
}
}
- (void)endValueTag
- (void) endValueTag
{
[types removeAllObjects];
[args removeAllObjects];
@@ -151,14 +151,14 @@
currentGroup = nil;
}
- (void)startVCardSet
- (void) startVCardSet
{
currentCardGroup = nil;
currentGroup = nil;
vcs.isInVCardSet = 1;
}
- (void)endVCardSet
- (void) endVCardSet
{
vcs.isInVCardSet = 0;
}

View File

@@ -262,10 +262,9 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
interval = [self->rrule repeatInterval];
until = [self lastInstanceStartDate]; // TODO: maybe replace
byMonthDay = [self->rrule byMonthDay];
/* check whether the range to be processed is beyond the 'until' date */
/* check whether the range to be processed is beyond the 'until' date */
if (until != nil) {
if ([until compare:rStart] == NSOrderedAscending) /* until before start */
return nil;
@@ -314,7 +313,7 @@ static void NGMonthDaySet_fillWithByDayX(NGMonthDaySet *daySet,
continue;
/* first check whether we are in the interval */
if ((monthIdxInRecurrence % interval) != 0)
continue;

View File

@@ -100,9 +100,10 @@ static Class yearlyCalcClass = Nil;
rule = [_rRules objectAtIndex:i];
if (![rule isKindOfClass:iCalRecurrenceRuleClass])
rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:rule];
calc = [self recurrenceCalculatorForRecurrenceRule:rule
withFirstInstanceCalendarDateRange:_fir];
rs = [calc recurrenceRangesWithinCalendarDateRange:_r];
[ranges addObjectsFromArray:rs];
}
@@ -158,6 +159,7 @@ static Class yearlyCalcClass = Nil;
unsigned k;
exDate = [exDates objectAtIndex:i];
for (k = 0; k < rCount; k++) {
unsigned rIdx;

View File

@@ -1,3 +1,9 @@
2007-08-27 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* VSSaxDriver.m ([VSSaxDriver
-parseFromSource:_sourcesystemId:_sysId]): report only tags for
which the content is not empty.
2007-05-03 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* VSSaxDriver.m ([VSSaxDriver +initialize]): removed the space

View File

@@ -471,20 +471,17 @@ static VSStringFormatter *stringFormatter = nil;
withAttrs: (SaxAttributes *) _attrs
andContent: (NSString *) _content
{
/*
This is called for all non-BEGIN|END types.
*/
VSSaxTag *a;
NSString *testContent;
// _content = [stringFormatter stringByUnescapingRFC2445Text: _content];
/* This is called for all non-BEGIN|END types. */
testContent = [[_content unescapedFromCard] stringByReplacingString: @";"
withString: @""];
/* check whether type should be reported as an attribute in XML */
[self _beginTag: _tagName group: _group withAttrs: _attrs];
if ([_content length] > 0)
if ([[testContent stringByTrimmingSpaces] length] > 0)
{
VSSaxTag *a;
[self _beginTag: _tagName group: _group withAttrs: _attrs];
a = [(VSSaxTag *)[VSSaxTag alloc]
initWithContentString: [_content unescapedFromCard]];
if (a)
@@ -492,9 +489,9 @@ static VSStringFormatter *stringFormatter = nil;
[elementList addObject: a];
[a release];
}
}
[self _endTag: _tagName];
[self _endTag: _tagName];
}
}
/* report events for collected elements */

View File

@@ -0,0 +1,482 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1991 Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the library GPL. It is
numbered 2 because it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Library General Public License, applies to some
specially designated Free Software Foundation software, and to any
other libraries whose authors decide to use it. You can use it for
your libraries, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link a program with the library, you must provide
complete object files to the recipients so that they can relink them
with the library, after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
Our method of protecting your rights has two steps: (1) copyright
the library, and (2) offer you this license which gives you legal
permission to copy, distribute and/or modify the library.
Also, for each distributor's protection, we want to make certain
that everyone understands that there is no warranty for this free
library. If the library is modified by someone else and passed on, we
want its recipients to know that what they have is not the original
version, so that any problems introduced by others will not reflect on
the original authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that companies distributing free
software will individually obtain patent licenses, thus in effect
transforming the program into proprietary software. To prevent this,
we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary
GNU General Public License, which was designed for utility programs. This
license, the GNU Library General Public License, applies to certain
designated libraries. This license is quite different from the ordinary
one; be sure to read it in full, and don't assume that anything in it is
the same as in the ordinary license.
The reason we have a separate public license for some libraries is that
they blur the distinction we usually make between modifying or adding to a
program and simply using it. Linking a program with a library, without
changing the library, is in some sense simply using the library, and is
analogous to running a utility program or application program. However, in
a textual and legal sense, the linked executable is a combined work, a
derivative of the original library, and the ordinary General Public License
treats it as such.
Because of this blurred distinction, using the ordinary General
Public License for libraries did not effectively promote software
sharing, because most developers did not use the libraries. We
concluded that weaker conditions might promote sharing better.
However, unrestricted linking of non-free programs would deprive the
users of those programs of all benefit from the free status of the
libraries themselves. This Library General Public License is intended to
permit developers of non-free programs to use free libraries, while
preserving your freedom as a user of such programs to change the free
libraries that are incorporated in them. (We have not seen how to achieve
this as regards changes in header files, but we have achieved it as regards
changes in the actual functions of the Library.) The hope is that this
will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, while the latter only
works together with the library.
Note that it is possible for a library to be covered by the ordinary
General Public License rather than by this special one.
GNU LIBRARY GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which
contains a notice placed by the copyright holder or other authorized
party saying it may be distributed under the terms of this Library
General Public License (also called "this License"). Each licensee is
addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also compile or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
d) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the source code distributed need not include anything that is normally
distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Library General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
Appendix: How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -0,0 +1,4 @@
Copyright (C) 2004 SKYRIX Software AG
Contact: info@skyrix.com

View File

@@ -0,0 +1,329 @@
2007-08-29 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* EOQualifier+GCS.m: rewrote comparison code, now uses UPPER instead of
the PostgreSQL specific ILIKE. Fixes OGo bug #1906 (v4.7.49)
2007-07-20 Helge Hess <helge.hess@opengroupware.org>
* GCSFolderManager.m: added 'some' rollback after an error (v4.7.48)
2007-07-20 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolderManager.m: fixed a bug in last check, DROP TABLE is allowed
to fail in the given context (bug #1883) (v4.7.47)
2007-07-11 Helge Hess <helge.hess@opengroupware.org>
* GCSFolderManager.m: added some error checking, plenty of open ends
pending (eg folder creation not wrapped in a transaction) (v4.7.46)
2007-06-29 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolderManager.m: fixed folder creation to populate empty path
fields with NULLs (OGo bug #1883) (v4.7.45)
2007-04-25 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolder.[hm]: added methods to delete ACL records (OGo bug #1866)
(v4.7.44)
2007-04-22 Helge Hess <helge.hess@opengroupware.org>
* GCSChannelManager.m: improved error log (v4.7.43)
2007-04-17 Helge Hess <helge.hess@opengroupware.org>
* fixed a few GNUstep compilation warnings (v4.7.42)
2007-03-21 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolder.[hm], GCSFolderManager.[hm]: added ability to create and
delete GCS folders programmatically (OGo bug #1850) (v4.7.41)
2007-02-12 Helge Hess <helge.hess@opengroupware.org>
* GCSFolder.m: fixed a gnustep-base compilation warning (v4.7.40)
2007-02-09 Helge Hess <helge.hess@opengroupware.org>
* use -errorWithFormat:, fixed a few logging crashes (incomplete format
strings) (v4.5.39)
2007-02-08 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolder.m: added a gnustep-base hack to properly format bool
numbers for SQL. Base returns YES or NO in -stringValue while
libFoundation/NGExt returns 0 or 1 (v4.5.39)
2007-01-15 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* GCSFolder.[hm], GCSFolderManager.m: added support for content table
ACLs (v4.5.38)
2006-08-31 Wolfgang Sourdeau <WSourdeau@Inverse.CA>
* EOQualifier+GCS.m: added support for OR qualifiers and for case
insensitive-like qualifiers on PostgreSQL (v4.5.37)
2006-07-04 Helge Hess <helge.hess@opengroupware.org>
* use %p for pointer formats, fixed gcc 4.1 warnings (v4.5.36)
2005-08-16 Helge Hess <helge.hess@opengroupware.org>
* GNUmakefile, GNUmakefile.preamble: added OSX framework compilation
(v4.5.35)
2005-07-23 Sebastian Reitenbach <reitenbach@rapideye.de>
* GNUmakefile.preamble: added OpenBSD linking flags (v4.5.34)
2005-07-13 Helge Hess <helge.hess@opengroupware.org>
* GCSFolder.h: added -versionOfContentWithName: method to header file
(v4.5.33)
* GCSFolder.m: return a proper exception if the extractor was unable to
create a quickrow for a given content object (v4.5.32)
* GCSFolder.m: added -writeContent:toName:baseVersion: to support
consistent update operations (eg using etags), properly increase
content object version on update operations (v4.5.31)
* GCSFolderManager.m, GCSFolder.m: changed not to use EOF
attribute-name 'beautification', eg 'c_name' will stay 'c_name'
instead of being transformed into 'cName' (v4.5.30)
2005-07-11 Helge Hess <helge.hess@opengroupware.org>
* GCSFolderManager.m: added automatic discovery of folder types by
scanning for .ocs files (v4.5.29)
2005-04-25 Helge Hess <helge.hess@opengroupware.org>
* fixed gcc 4.0 warnings (v4.5.28)
2005-03-21 Helge Hess <helge.hess@skyrix.com>
* GNUmakefile: added FHS support (v4.5.27)
2005-03-20 Helge Hess <helge.hess@opengroupware.org>
* moved OGoContentStore as GDLContentStore into sope-gdl1, removed
dependencies on NGiCal and removed some SOGo specific things
(v4.5.26)
2005-03-07 Helge Hess <helge.hess@opengroupware.org>
* appointment.ocs: added missing 'partstates' field (v0.9.25)
2005-03-04 Helge Hess <helge.hess@opengroupware.org>
* v0.9.24
* ocs_gensql.m: started tool to create SQL CREATE from ocs model file
* OCSFolderType.m: small change to the factory API, changed to use
NGResourceLocator
2005-03-03 Helge Hess <helge.hess@opengroupware.org>
* OCSFolderManager.m: fixed a bug in subfolder listing (v0.9.23)
2005-03-01 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.22
* appointment.ocs: added 'cycleenddate' and 'cycleinfo' to address
previous performance issues
* OCSiCalFieldExtractor.m: set 'cycleenddate' and 'cycleinfo' for
recurrent events. Reverted setting of 'enddate' to the previous
behaviour since 'cycleenddate' is dedicated to the task now
* iCalRepeatableEntityObject+OCS.[hm]: new category used by the
OCSiCalFieldExtractor to extract cycleInfo in an appropriate format
* sql/generate-folderinfo-sql-for-users.sh,
sql/foldertablecreate-helge-privcal.psql,
sql/foldertablecreate-helge-privcal.sqlite,
sql/generate-folderinfo-sql-for-users-sqlite.sh: adjusted to new
schema
2005-03-01 Helge Hess <helge.hess@opengroupware.org>
* OCSFolder.m: added support for storing content and quick info in
the same table (untested) (v0.9.21)
2005-02-21 Helge Hess <helge.hess@opengroupware.org>
* v0.9.20
* OCSFolderManager.m: removed quoting of SQL table and column names
(breaks with SQLite and isn't necessary for PG), fixed URL pooling
for SQLite
* NSURL+OCS.m: use tablename for last path component
2005-02-12 Marcus Mueller <znek@mulle-kybernetik.com>
* OCSiCalFieldExtractor.m: uses new iCalEvent API to determine correct
'enddate' for recurrent events. This is an optimization which can
save quite some time for complex rules. (v0.9.19)
2004-12-17 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.18
* OCSiCalFieldExtractor.m: extract participants' state
* sql/generate-folderinfo-sql-for-user.sh, sql/appointment-create.psql,
sql/foldertablecreate-helge-privcal.psql: updated with new schema.
2004-12-15 Marcus Mueller <znek@mulle-kybernetik.com>
* OCSiCalFieldExtractor.m: partmails + cn's are concatenated by '\n'
now - this directly eliminates any ambiguities. Also, instead of
using 'email' for partmails and orgmail, the extractor uses the
'rfc822Email' value which strips away any preceeding 'mailto:'
prefix, compacting the representation and speeding up comparison.
Also, "iscycle", "isallday" and "isopaque" are now provided by
NGiCal and thus always extracted (v0.9.17)
2004-12-13 Marcus Mueller <znek@mulle-kybernetik.com>
* sql/generate-folderinfo-sql-for-user.sh: fixed critical error in
Contacts folder_info, type was 'Appointment' but MUST be 'Contact'
(v0.9.16)
2004-12-10 Marcus Mueller <znek@mulle-kybernetik.com>
* sql: updated all generation scripts to the latest version (v0.9.15)
2004-12-09 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.14
* appointment.ocs: added "ispublic", "isopaque", "status" and
"orgmail".
* OCSiCalFieldExtractor.m: updated to extract new fields (see above)
* sql: updated generate-folderinfo-sql-for-users.sh
2004-10-19 Helge Hess <helge.hess@opengroupware.org>
* OCSFolder.m: added new method -fetchContentsOfAllFiles method which
fetches the contents of all files stored in the folder (required for
iCal generation, such bulk fetches should be avoided if possible!)
(v0.9.13)
2004-10-15 Marcus Mueller <znek@mulle-kybernetik.com>
* OCSStringFormatter.[hm]: minor cleanup (v0.9.12)
* v0.9.11
* OCSStringFormatter.[hm]: new class to format strings according to
Database requirements (escaping etc.).
* OCSFolder.m: uses new OCSStringFormatter now.
2004-09-25 Helge Hess <helge.hess@opengroupware.org>
* fixed compilation on MacOSX (v0.9.10)
2004-09-10 Helge Hess <helge.hess@skyrix.com>
* v0.9.9
* fixed some gcc warnings
* GNUmakefile.preamble: added pathes to compile against an FHS SOPE
* OCSiCalFieldExtractor.m: fixed type of sequence iCalEvent field
2004-09-01 Marcus Mueller <znek@mulle-kybernetik.com>
* GNUmakefile: install type models into $(GNUSTEP_USER_ROOT) (v0.9.8)
2004-08-27 Helge Hess <helge.hess@skyrix.com>
* v0.9.7
* OCSChannelManager.m: use PostgreSQL as adaptor, not PostgreSQL72
* OCSFolder.m: added support for doing folder sorting in SQL
2004-08-26 Helge Hess <helge.hess@skyrix.com>
* v0.9.6
* added OCSContactFieldExtractor
* sql: added sample contact folder create scripts
* OCSFolderType.m: read extractor class name from type model
* OCSFolderManager.m: added contact type model per default (v0.9.5)
2004-08-25 Helge Hess <helge.hess@skyrix.com>
* GNUmakefile: automatically install OCSTypeModels (v0.9.4)
2004-08-15 Helge Hess <helge.hess@skyrix.com>
* OCSFolder.m: added content deletion (v0.9.3)
* OCSFolder.m: added sanity check to store method (v0.9.2)
2004-08-14 Helge Hess <helge.hess@skyrix.com>
* v0.9.1
* OCSiCalFieldExtractor.m: extract new quick fields: location,
partmails, sequence (does not yet handle allday and cycle due to
NGiCal restrictions)
* appointment.ocs, sql/foldertablecreate-helge-privcal.psql,
sql/testapt-agenor-helge-privcal.psql, sql/appointment-create.psql:
added quick fields: isallday, iscycle, location, partmails, sequence
* started ocs_recreatequick tool intended for recreating a quick table
based on the content table of a folder
2004-07-20 Helge Hess <helge.hess@opengroupware.org>
* OCSChannelManager.m: fixed a bug in the channel GC which resulted
in an exception during the GC NSTimer
2004-07-16 Helge Hess <helge.hess@skyrix.com>
* improved error handling in various files
2004-07-02 Helge Hess <helge.hess@opengroupware.org>
* OCSChannelManager.m: added garbage collector for channel pools
2004-06-30 Helge Hess <helge.hess@opengroupware.org>
* OCSChannelManager.m: implemented pooling
* OCSFolder.m: added quick fetches
* GNUmakefile.preamble: fix link path
* GNUmakefile (libOGoContentStore_HEADER_FILES_INSTALL_DIR): install
headers in OGoContentStore
* GNUmakefile.preamble (ocs_ls_TOOL_LIBS): added static dependencies
for OSX
2004-06-30 Marcus Mueller <znek@mulle-kybernetik.com>
* ocs_cat.m, ocs_ls.m, ocs_mkdir.m: fixed for gnustep compile.
2004-06-29 Helge Hess <helge.hess@opengroupware.org>
* created ChangeLog

View File

@@ -0,0 +1,40 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_EOAdaptorChannel_GCS_H__
#define __GDLContentStore_EOAdaptorChannel_GCS_H__
#include <GDLAccess/EOAdaptorChannel.h>
@protocol GCSEOAdaptorChannel
- (NSException *) createGCSFolderTableWithName: (NSString *) tableName;
- (NSException *) createGCSFolderACLTableWithName: (NSString *) tableName;
@end
@interface EOAdaptorChannel(GCS)
- (BOOL)tableExistsWithName:(NSString *)_tableName;
@end
#endif /* __GDLContentStore_EOAdaptorChannel_GCS_H__ */

View File

@@ -0,0 +1,50 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "EOAdaptorChannel+GCS.h"
#include "common.h"
@implementation EOAdaptorChannel(GCS)
- (BOOL)tableExistsWithName:(NSString *)_tableName {
NSException *ex;
NSString *sql;
BOOL didOpen;
didOpen = NO;
if (![self isOpen]) {
if (![self openChannel])
return NO;
didOpen = YES;
}
sql = @"SELECT COUNT(*) FROM ";
sql = [sql stringByAppendingString:_tableName];
sql = [sql stringByAppendingString:@" WHERE 1 = 2"];
ex = [[[self evaluateExpressionX:sql] retain] autorelease];
[self cancelFetch];
if (didOpen) [self closeChannel];
return ex != nil ? NO : YES;
}
@end /* EOAdaptorChannel(GCS) */

View File

@@ -0,0 +1,35 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_EOQualifier_GCS_H__
#define __GDLContentStore_EOQualifier_GCS_H__
#include <EOControl/EOQualifier.h>
@class NSMutableString;
@interface EOQualifier(GCS)
- (void)_gcsAppendToString:(NSMutableString *)_ms;
@end
#endif /* __GDLContentStore_EOQualifier_GCS_H__ */

View File

@@ -0,0 +1,156 @@
/*
Copyright (C) 2004-2007 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "EOQualifier+GCS.h"
#include "common.h"
@implementation EOQualifier(GCS)
- (void)_appendAndQualifier:(EOAndQualifier *)_q
toString:(NSMutableString *)_ms
{
// TODO: move to EOQualifier category
NSArray *qs;
unsigned i, count;
qs = [_q qualifiers];
if ((count = [qs count]) == 0)
return;
for (i = 0; i < count; i++) {
if (i != 0) [_ms appendString:@" AND "];
if (count > 1) [_ms appendString:@"("];
[[qs objectAtIndex:i] _gcsAppendToString:_ms];
if (count > 1) [_ms appendString:@")"];
}
}
- (void)_appendOrQualifier:(EOAndQualifier *)_q
toString:(NSMutableString *)_ms
{
// TODO: move to EOQualifier category
NSArray *qs;
unsigned i, count;
qs = [_q qualifiers];
if ((count = [qs count]) == 0)
return;
for (i = 0; i < count; i++) {
if (i != 0) [_ms appendString:@" OR "];
if (count > 1) [_ms appendString:@"("];
[[qs objectAtIndex:i] _gcsAppendToString:_ms];
if (count > 1) [_ms appendString:@")"];
}
}
- (void)_appendKeyValueQualifier:(EOKeyValueQualifier *)_q
toString:(NSMutableString *)_ms
{
id val;
NSString *qKey, *qOperator, *qValue, *qFormat;
BOOL isCI;
qKey = [_q key];
if ((val = [_q value])) {
SEL op = [_q selector];
if ([val isNotNull]) {
isCI = NO;
if (sel_eq(op, EOQualifierOperatorEqual))
qOperator = @"=";
else if (sel_eq(op, EOQualifierOperatorNotEqual))
qOperator = @"!=";
else if (sel_eq(op, EOQualifierOperatorLessThan))
qOperator = @"<";
else if (sel_eq(op, EOQualifierOperatorGreaterThan))
qOperator = @">";
else if (sel_eq(op, EOQualifierOperatorLessThanOrEqualTo))
qOperator = @"<=";
else if (sel_eq(op, EOQualifierOperatorGreaterThanOrEqualTo))
qOperator = @">=";
else if (sel_eq(op, EOQualifierOperatorLike))
qOperator = @"LIKE";
else if (sel_eq(op, EOQualifierOperatorCaseInsensitiveLike)) {
isCI = YES;
qOperator = @"LIKE";
}
else {
[self errorWithFormat:@"%s: unsupported operation for null: %@",
__PRETTY_FUNCTION__, NSStringFromSelector(op)];
}
if ([val isKindOfClass:[NSNumber class]])
qValue = [val stringValue];
else if ([val isKindOfClass:[NSString class]]) {
qValue = [NSString stringWithFormat: @"'%@'", val];
}
else {
[self errorWithFormat:@"%s: unsupported value class: %@",
__PRETTY_FUNCTION__, NSStringFromClass([val class])];
}
}
else {
if (sel_eq(op, EOQualifierOperatorEqual)) {
qOperator = @"IS";
qValue = @"NULL";
}
else if (sel_eq(op, EOQualifierOperatorEqual)) {
qOperator = @"IS NOT";
qValue = @"NULL";
}
else {
[self errorWithFormat:@"%s: invalid operation for null: %@",
__PRETTY_FUNCTION__, NSStringFromSelector(op)];
}
}
}
else {
qOperator = @"IS";
qValue = @"NULL";
}
if (isCI)
qFormat = @"UPPER(%@) %@ UPPER(%@)";
else
qFormat = @"%@ %@ %@";
[_ms appendFormat: qFormat, qKey, qOperator, qValue];
}
- (void)_appendQualifier:(EOQualifier *)_q toString:(NSMutableString *)_ms {
if (_q == nil) return;
if ([_q isKindOfClass:[EOAndQualifier class]])
[self _appendAndQualifier:(id)_q toString:_ms];
else if ([_q isKindOfClass:[EOOrQualifier class]])
[self _appendOrQualifier:(id)_q toString:_ms];
else if ([_q isKindOfClass:[EOKeyValueQualifier class]])
[self _appendKeyValueQualifier:(id)_q toString:_ms];
else
[self errorWithFormat:@"unknown qualifier: %@", _q];
}
- (void)_gcsAppendToString:(NSMutableString *)_ms {
[self _appendQualifier:self toString:_ms];
}
@end /* EOQualifier(GCS) */

View File

@@ -0,0 +1,58 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSChannelManager_H__
#define __GDLContentStore_GCSChannelManager_H__
#import <Foundation/NSObject.h>
/*
GCSChannelManager
This object manages the connection pooling.
*/
@class NSURL, NSMutableDictionary, NSMutableArray, NSTimer;
@class EOAdaptorChannel, EOAdaptor;
@interface GCSChannelManager : NSObject
{
NSMutableDictionary *urlToAdaptor;
NSMutableArray *availableChannels;
NSMutableArray *busyChannels;
NSTimer *gcTimer;
}
+ (id)defaultChannelManager;
/* channels */
- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url;
- (void)releaseChannel:(EOAdaptorChannel *)_channel;
/* checking for tables */
- (BOOL)canConnect:(NSURL *)_url;
@end
#endif /* __GDLContentStore_GCSChannelManager_H__ */

View File

@@ -0,0 +1,516 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSChannelManager.h"
#include "NSURL+GCS.h"
#include "EOAdaptorChannel+GCS.h"
#include <GDLAccess/EOAdaptor.h>
#include <GDLAccess/EOAdaptorContext.h>
#include <GDLAccess/EOAdaptorChannel.h>
#include "common.h"
/*
TODO:
- implemented pooling
- auto-close channels which are very old?!
(eg missing release due to an exception)
*/
@interface GCSChannelHandle : NSObject
{
@public
NSURL *url;
EOAdaptorChannel *channel;
NSDate *creationTime;
NSDate *lastReleaseTime;
NSDate *lastAcquireTime;
}
- (EOAdaptorChannel *)channel;
- (BOOL)canHandleURL:(NSURL *)_url;
- (NSTimeInterval)age;
@end
@implementation GCSChannelManager
static BOOL debugOn = NO;
static BOOL debugPools = NO;
static int ChannelExpireAge = 180;
static NSTimeInterval ChannelCollectionTimer = 5 * 60;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSChannelManagerDebugEnabled"];
debugPools = [ud boolForKey:@"GCSChannelManagerPoolDebugEnabled"];
ChannelExpireAge = [[ud objectForKey:@"GCSChannelExpireAge"] intValue];
if (ChannelExpireAge < 1)
ChannelExpireAge = 180;
ChannelCollectionTimer =
[[ud objectForKey:@"GCSChannelCollectionTimer"] intValue];
if (ChannelCollectionTimer < 1)
ChannelCollectionTimer = 5*60;
}
+ (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
// TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL
return @"PostgreSQL";
}
+ (id)defaultChannelManager {
static GCSChannelManager *cm = nil;
if (cm == nil)
cm = [[self alloc] init];
return cm;
}
- (id)init {
if ((self = [super init])) {
self->urlToAdaptor = [[NSMutableDictionary alloc] initWithCapacity:4];
self->availableChannels = [[NSMutableArray alloc] initWithCapacity:16];
self->busyChannels = [[NSMutableArray alloc] initWithCapacity:16];
self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
ChannelCollectionTimer
target:self selector:@selector(_garbageCollect:)
userInfo:nil repeats:YES] retain];
}
return self;
}
- (void)dealloc {
if (self->gcTimer) [self->gcTimer invalidate];
[self->gcTimer release];
[self->busyChannels release];
[self->availableChannels release];
[self->urlToAdaptor release];
[super dealloc];
}
/* DB key */
- (NSString *)databaseKeyForURL:(NSURL *)_url {
/*
We need to build a proper key that omits passwords and URL path components
which are not required.
*/
NSString *key;
key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
[_url host], [_url port],
[_url user], [_url gcsDatabaseName]];
return key;
}
/* adaptors */
- (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url {
NSMutableDictionary *md;
id tmp;
md = [NSMutableDictionary dictionaryWithCapacity:4];
if ((tmp = [_url host]) != nil)
[md setObject:tmp forKey:@"hostName"];
if ((tmp = [_url port]) != nil)
[md setObject:tmp forKey:@"port"];
if ((tmp = [_url user]) != nil)
[md setObject:tmp forKey:@"userName"];
if ((tmp = [_url password]) != nil)
[md setObject:tmp forKey:@"password"];
if ((tmp = [_url gcsDatabaseName]) != nil)
[md setObject:tmp forKey:@"databaseName"];
[self debugWithFormat:@"build connection dictionary for URL %@: %@",
[_url absoluteString], md];
return md;
}
- (EOAdaptor *)adaptorForURL:(NSURL *)_url {
EOAdaptor *adaptor;
NSString *key;
if (_url == nil)
return nil;
if ((key = [self databaseKeyForURL:_url]) == nil)
return nil;
if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) {
[self debugWithFormat:@"using cached adaptor: %@", adaptor];
return adaptor; /* cached :-) */
}
[self debugWithFormat:@"creating new adaptor for URL: %@", _url];
if ([EOAdaptor respondsToSelector:@selector(adaptorForURL:)]) {
adaptor = [EOAdaptor adaptorForURL:_url];
}
else {
NSString *adaptorName;
NSDictionary *condict;
adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]];
if ([adaptorName length] == 0) {
[self errorWithFormat:@"cannot handle URL: %@", _url];
return nil;
}
condict = [self connectionDictionaryForURL:_url];
if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) {
[self errorWithFormat:@"did not find adaptor '%@' for URL: %@",
adaptorName, _url];
return nil;
}
[adaptor setConnectionDictionary:condict];
}
[self->urlToAdaptor setObject:adaptor forKey:key];
return adaptor;
}
/* channels */
- (GCSChannelHandle *)findBusyChannelHandleForChannel:(EOAdaptorChannel *)_ch {
NSEnumerator *e;
GCSChannelHandle *handle;
e = [self->busyChannels objectEnumerator];
while ((handle = [e nextObject])) {
if ([handle channel] == _ch)
return handle;
}
return nil;
}
- (GCSChannelHandle *)findAvailChannelHandleForURL:(NSURL *)_url {
NSEnumerator *e;
GCSChannelHandle *handle;
e = [self->availableChannels objectEnumerator];
while ((handle = [e nextObject])) {
if ([handle canHandleURL:_url])
return handle;
if (debugPools) {
[self logWithFormat:@"DBPOOL: cannot use handle (%@ vs %@)",
[_url absoluteString], [handle->url absoluteString]];
}
}
return nil;
}
- (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url {
EOAdaptor *adaptor;
EOAdaptorContext *adContext;
EOAdaptorChannel *adChannel;
if ((adaptor = [self adaptorForURL:_url]) == nil)
return nil;
if ((adContext = [adaptor createAdaptorContext]) == nil) {
[self errorWithFormat:@"could not create adaptor context!"];
return nil;
}
if ((adChannel = [adContext createAdaptorChannel]) == nil) {
[self errorWithFormat:@"could not create adaptor channel!"];
return nil;
}
return adChannel;
}
- (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url {
// TODO: naive implementation, add pooling!
EOAdaptorChannel *channel;
GCSChannelHandle *handle;
NSCalendarDate *now;
now = [NSCalendarDate date];
/* look for cached handles */
if ((handle = [self findAvailChannelHandleForURL:_url]) != nil) {
// TODO: check age?
[self->busyChannels addObject:handle];
[self->availableChannels removeObject:handle];
ASSIGN(handle->lastAcquireTime, now);
if (debugPools)
[self logWithFormat:@"DBPOOL: reused cached DB channel!"];
return [[handle channel] retain];
}
if (debugPools) {
[self logWithFormat:@"DBPOOL: create new DB channel for URL: %@",
[_url absoluteString]];
}
/* create channel */
if ((channel = [self _createChannelForURL:_url]) == nil)
return nil;
if ([channel isOpen])
;
else if (![channel openChannel]) {
[self errorWithFormat:@"could not open channel %@ for URL: %@",
channel, [_url absoluteString]];
return nil;
}
/* create handle for channel */
handle = [[GCSChannelHandle alloc] init];
handle->url = [_url retain];
handle->channel = [channel retain];
handle->creationTime = [now retain];
handle->lastAcquireTime = [now retain];
[self->busyChannels addObject:handle];
[handle release];
return [channel retain];
}
- (void)releaseChannel:(EOAdaptorChannel *)_channel {
GCSChannelHandle *handle;
if ((handle = [self findBusyChannelHandleForChannel:_channel]) != nil) {
NSCalendarDate *now;
now = [NSCalendarDate date];
handle = [handle retain];
ASSIGN(handle->lastReleaseTime, now);
[self->busyChannels removeObject:handle];
if ([[handle channel] isOpen] && [handle age] < ChannelExpireAge) {
// TODO: consider age
[self->availableChannels addObject:handle];
if (debugPools) {
[self logWithFormat:
@"DBPOOL: keeping channel (age %ds, #%d): %@",
(int)[handle age], [self->availableChannels count],
[handle->url absoluteString]];
}
[_channel release];
[handle release];
return;
}
if (debugPools) {
[self logWithFormat:
@"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
}
/* not reusing channel */
[handle release]; handle = nil;
}
if ([_channel isOpen])
[_channel closeChannel];
[_channel release];
}
/* checking for tables */
- (BOOL)canConnect:(NSURL *)_url {
/*
this can check for DB connect as well as for table URLs (whether a table
exists)
*/
EOAdaptorChannel *channel;
NSString *table;
BOOL result;
if ((channel = [self acquireOpenChannelForURL:_url]) == nil) {
if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url];
return NO;
}
if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
result = YES; /* could open channel */
/* check whether table exists */
table = [_url gcsTableName];
if ([table length] > 0)
result = [channel tableExistsWithName:table];
/* release channel */
[self releaseChannel:channel]; channel = nil;
return result;
}
/* collect old channels */
- (void)_garbageCollect:(NSTimer *)_timer {
NSMutableArray *handlesToRemove;
unsigned i, count;
if ((count = [self->availableChannels count]) == 0)
/* no available channels */
return;
/* collect channels to expire */
handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4];
for (i = 0; i < count; i++) {
GCSChannelHandle *handle;
handle = [self->availableChannels objectAtIndex:i];
if (![[handle channel] isOpen]) {
[handlesToRemove addObject:handle];
continue;
}
if ([handle age] > ChannelExpireAge) {
[handlesToRemove addObject:handle];
continue;
}
}
/* remove channels */
count = [handlesToRemove count];
if (debugPools)
[self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count];
for (i = 0; i < count; i++) {
GCSChannelHandle *handle;
handle = [[handlesToRemove objectAtIndex:i] retain];
[self->availableChannels removeObject:handle];
if ([[handle channel] isOpen])
[[handle channel] closeChannel];
[handle release];
}
[handlesToRemove release];
}
/* debugging */
- (BOOL)isDebuggingEnabled {
return debugOn;
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]];
[ms appendString:@">"];
return ms;
}
@end /* GCSChannelManager */
@implementation GCSChannelHandle
- (void)dealloc {
[self->channel release];
[self->creationTime release];
[self->lastReleaseTime release];
[self->lastAcquireTime release];
[super dealloc];
}
/* accessors */
- (EOAdaptorChannel *)channel {
return self->channel;
}
- (BOOL)canHandleURL:(NSURL *)_url {
BOOL isSQLite;
if (_url == nil) {
[self logWithFormat:@"MISMATCH: no url .."];
return NO;
}
if (_url == self->url)
return YES;
isSQLite = [[_url scheme] isEqualToString:@"sqlite"];
if (!isSQLite && ![[self->url host] isEqual:[_url host]]) {
[self logWithFormat:@"MISMATCH: different host (%@ vs %@)",
[self->url host], [_url host]];
return NO;
}
if (![[self->url gcsDatabaseName] isEqualToString:[_url gcsDatabaseName]]) {
[self logWithFormat:@"MISMATCH: different db .."];
return NO;
}
if (!isSQLite) {
if (![[self->url user] isEqual:[_url user]]) {
[self logWithFormat:@"MISMATCH: different user .."];
return NO;
}
if ([[self->url port] intValue] != [[_url port] intValue]) {
[self logWithFormat:@"MISMATCH: different port (%@ vs %@) ..",
[self->url port], [_url port]];
return NO;
}
}
return YES;
}
- (NSTimeInterval)age {
return [[NSCalendarDate calendarDate]
timeIntervalSinceDate:self->creationTime];
}
/* NSCopying */
- (id)copyWithZone:(NSZone *)_zone {
return [self retain];
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[ms appendFormat:@" channel=0x%p", self->channel];
if (self->creationTime) [ms appendFormat:@" created=%@", self->creationTime];
if (self->lastReleaseTime)
[ms appendFormat:@" last-released=%@", self->lastReleaseTime];
if (self->lastAcquireTime)
[ms appendFormat:@" last-acquired=%@", self->lastAcquireTime];
[ms appendString:@">"];
return ms;
}
@end /* GCSChannelHandle */

View File

@@ -0,0 +1,39 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSContext_H__
#define __GDLContentStore_GCSContext_H__
#import <Foundation/NSObject.h>
/*
GCSContext
Context passed to all operations.
*/
@interface GCSContext : NSObject
{
}
@end
#endif /* __GDLContentStore_GCSContext_H__ */

View File

@@ -0,0 +1,26 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSContext.h"
#include "common.h"
@implementation GCSContext
@end /* GCSContext */

View File

@@ -0,0 +1,37 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSFieldExtractor_H__
#define __GDLContentStore_GCSFieldExtractor_H__
#import <Foundation/NSObject.h>
@class NSString, NSMutableDictionary;
@interface GCSFieldExtractor : NSObject
{
}
- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content;
@end
#endif /* __GDLContentStore_GCSFieldExtractor_H__ */

View File

@@ -0,0 +1,31 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSFieldExtractor.h"
#include "common.h"
@implementation GCSFieldExtractor
- (NSMutableDictionary *)extractQuickFieldsFromContent:(NSString *)_content {
return nil;
}
@end /* GCSFieldExtractor */

View File

@@ -0,0 +1,61 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSFieldInfo_H__
#define __GDLContentStore_GCSFieldInfo_H__
#import <Foundation/NSObject.h>
/*
GCSFieldInfo
The field info inside an .ocs schema file.
The field info objects are stored in an GCSFolderType.
*/
@class NSString, NSArray;
@interface GCSFieldInfo : NSObject
{
NSString *columnName;
NSString *sqlType;
BOOL allowsNull;
BOOL isPrimaryKey;
}
+ (NSArray *)fieldsForPropertyList:(NSArray *)_plist;
- (id)initWithPropertyList:(id)_plist;
/* accessors */
- (NSString *)columnName;
- (NSString *)sqlType;
- (BOOL)doesAllowNull;
- (BOOL)isPrimaryKey;
/* generating SQL */
- (NSString *)sqlCreateSection;
@end
#endif /* __GDLContentStore_GCSFieldInfo_H__ */

View File

@@ -0,0 +1,127 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSFieldInfo.h"
#include "common.h"
@implementation GCSFieldInfo
+ (NSArray *)fieldsForPropertyList:(NSArray *)_plist {
NSMutableArray *fields;
unsigned i, count;
if (_plist == nil)
return nil;
count = [_plist count];
fields = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; i++) {
GCSFieldInfo *field;
field = [[GCSFieldInfo alloc] initWithPropertyList:
[_plist objectAtIndex:i]];
if (field != nil) [fields addObject:field];
[field release];
}
return fields;
}
- (id)initWithPropertyList:(id)_plist {
if ((self = [super init])) {
NSDictionary *plist = _plist;
self->columnName = [[plist objectForKey:@"columnName"] copy];
self->sqlType = [[plist objectForKey:@"sqlType"] copy];
self->allowsNull = [[plist objectForKey:@"allowsNull"] boolValue];
self->isPrimaryKey = [[plist objectForKey:@"isPrimaryKey"] boolValue];
if (![self->columnName isNotNull] || ![self->sqlType isNotNull]) {
[self release];
return nil;
}
}
return self;
}
- (void)dealloc {
[self->columnName release];
[self->sqlType release];
[super dealloc];
}
/* accessors */
- (NSString *)columnName {
return self->columnName;
}
- (NSString *)sqlType {
return self->sqlType;
}
- (BOOL)doesAllowNull {
return self->allowsNull;
}
- (BOOL)isPrimaryKey {
return self->isPrimaryKey;
}
/* generating SQL */
- (NSString *)sqlCreateSection {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:32];
[ms appendString:[self columnName]];
[ms appendString:@" "];
[ms appendString:[self sqlType]];
[ms appendString:@" "];
if (![self doesAllowNull]) [ms appendString:@"NOT "];
[ms appendString:@"NULL"];
if ([self isPrimaryKey]) [ms appendString:@" PRIMARY KEY"];
return ms;
}
/* description */
- (void)appendAttributesToDescription:(NSMutableString *)ms {
id tmp;
if ((tmp = [self columnName]) != nil) [ms appendFormat:@" column=%@", tmp];
if ((tmp = [self sqlType]) != nil) [ms appendFormat:@" sql=%@", tmp];
if ([self doesAllowNull]) [ms appendString:@" allows-null"];
if ([self isPrimaryKey]) [ms appendString:@" pkey"];
}
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[self appendAttributesToDescription:ms];
[ms appendString:@">"];
return ms;
}
@end /* GCSFieldInfo */

View File

@@ -0,0 +1,128 @@
/*
Copyright (C) 2004-2007 SKYRIX Software AG
Copyright (C) 2007 Helge Hess
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSFolder_H__
#define __GDLContentStore_GCSFolder_H__
#import <Foundation/NSObject.h>
/*
GCSFolder
TODO: document
Fixed Quick-Table SQL fields:
- "c_name" (name of the file in the folder)
Fixed BLOB-Table SQL fields:
- "c_name" (name of the file in the folder)
- "c_content" (content of the file in the folder)
- "c_version" (update revision of the file in the folder)
*/
@class NSString, NSURL, NSNumber, NSArray, NSException, NSMutableString;
@class NSDictionary;
@class EOQualifier, EOFetchSpecification;
@class EOAdaptorChannel;
@class GCSFolderManager, GCSFolderType, GCSChannelManager;
@interface GCSFolder : NSObject
{
GCSFolderManager *folderManager;
GCSFolderType *folderInfo;
NSNumber *folderId;
NSString *folderName;
NSString *path;
NSURL *location;
NSURL *quickLocation;
NSURL *aclLocation;
NSString *folderTypeName;
struct {
int requiresFolderSelect:1;
int sameTableForQuick:1;
int reserved:30;
} ofFlags;
}
- (id)initWithPath:(NSString *)_path primaryKey:(id)_folderId
folderTypeName:(NSString *)_ftname folderType:(GCSFolderType *)_ftype
location:(NSURL *)_loc quickLocation:(NSURL *)_qloc
aclLocation: (NSURL *)_aloc
folderManager:(GCSFolderManager *)_fm;
/* accessors */
- (NSNumber *)folderId;
- (NSString *)folderName;
- (NSString *)path;
- (NSURL *)location;
- (NSURL *)quickLocation;
- (NSURL *)aclLocation;
- (NSString *)folderTypeName;
- (GCSFolderManager *)folderManager;
- (GCSChannelManager *)channelManager;
- (NSString *)storeTableName;
- (NSString *)quickTableName;
- (NSString *)aclTableName;
- (BOOL)isQuickInfoStoredInContentTable;
/* connection */
- (EOAdaptorChannel *)acquireStoreChannel;
- (EOAdaptorChannel *)acquireQuickChannel;
- (EOAdaptorChannel *)acquireAclChannel;
- (void)releaseChannel:(EOAdaptorChannel *)_channel;
- (BOOL)canConnectStore;
- (BOOL)canConnectQuick;
/* operations */
- (NSArray *)subFolderNames;
- (NSArray *)allSubFolderNames;
- (NSNumber *)versionOfContentWithName:(NSString *)_name;
- (NSString *)fetchContentWithName:(NSString *)_name;
- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name
baseVersion:(unsigned int)_baseVersion;
- (NSException *)writeContent:(NSString *)_content toName:(NSString *)_name;
- (NSException *)deleteContentWithName:(NSString *)_name;
- (NSException *)deleteFolder;
- (NSDictionary *)fetchContentsOfAllFiles;
- (NSArray *)fetchFields:(NSArray *)_flds
fetchSpecification:(EOFetchSpecification *)_fs;
- (NSArray *)fetchFields:(NSArray *)_flds matchingQualifier:(EOQualifier *)_q;
- (NSArray *)fetchAclMatchingQualifier:(EOQualifier *)_q;
- (void)deleteAclMatchingQualifier:(EOQualifier *)_q;
- (void)deleteAclWithSpecification:(EOFetchSpecification *)_fs;
@end
#endif /* __GDLContentStore_GCSFolder_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSFolderManager_H__
#define __GDLContentStore_GCSFolderManager_H__
#import <Foundation/NSObject.h>
/*
GCSFolderManager
Objects of this class manage the "folder_info" table, they manage the
model and manage the tables required for a folder.
*/
@class NSString, NSArray, NSURL, NSDictionary, NSException;
@class GCSChannelManager, GCSFolder, GCSFolderType;
@interface GCSFolderManager : NSObject
{
GCSChannelManager *channelManager;
NSDictionary *nameToType;
NSURL *folderInfoLocation;
}
+ (id)defaultFolderManager;
- (id)initWithFolderInfoLocation:(NSURL *)_url;
/* accessors */
- (NSURL *)folderInfoLocation;
- (NSString *)folderInfoTableName;
/* connection */
- (GCSChannelManager *)channelManager;
- (BOOL)canConnect;
/* handling folder names */
- (NSString *)internalNameFromPath:(NSString *)_path;
- (NSArray *)internalNamesFromPath:(NSString *)_path;
- (NSString *)pathFromInternalName:(NSString *)_name;
/* operations */
- (BOOL)folderExistsAtPath:(NSString *)_path;
- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag;
- (GCSFolder *)folderAtPath:(NSString *)_path;
- (NSException *)createFolderOfType:(NSString *)_type withName:(NSString *)_name atPath:(NSString *)_path;
- (NSException *)deleteFolderAtPath:(NSString *)_path;
/* folder types */
- (GCSFolderType *)folderTypeWithName:(NSString *)_name;
/* cache management */
- (void)reset;
@end
#endif /* __GDLContentStore_GCSFolderManager_H__ */

View File

@@ -0,0 +1,853 @@
/*
Copyright (C) 2004-2007 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSFolderManager.h"
#include "GCSChannelManager.h"
#include "GCSFolderType.h"
#include "GCSFolder.h"
#include "NSURL+GCS.h"
#include "EOAdaptorChannel+GCS.h"
#include "common.h"
#include <GDLAccess/EOAdaptorChannel.h>
#include <NGExtensions/NGResourceLocator.h>
#include <unistd.h>
/*
Required database schema:
<arbitary table>
c_path
c_path1, path2, path3... [quickPathCount times]
c_foldername
TODO:
- add a local cache?
*/
@implementation GCSFolderManager
static GCSFolderManager *fm = nil;
static BOOL debugOn = NO;
static BOOL debugSQLGen = NO;
static BOOL debugPathTraversal = NO;
static int quickPathCount = 4;
static NSArray *emptyArray = nil;
#if 0
static NSString *GCSPathColumnName = @"c_path";
static NSString *GCSTypeColumnName = @"c_folder_type";
static NSString *GCSTypeRecordName = @"c_folder_type";
#endif
static NSString *GCSPathRecordName = @"c_path";
static NSString *GCSGenericFolderTypeName = @"Container";
static const char *GCSPathColumnPattern = "c_path%i";
static NSCharacterSet *asciiAlphaNumericCS = nil;
+ (void)initialize {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey:@"GCSFolderManagerDebugEnabled"];
debugSQLGen = [ud boolForKey:@"GCSFolderManagerSQLDebugEnabled"];
emptyArray = [[NSArray alloc] init];
if (!asciiAlphaNumericCS)
{
asciiAlphaNumericCS
= [NSCharacterSet characterSetWithCharactersInString:
@"0123456789"
@"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@"abcdefghijklmnopqrstuvwxyz"];
[asciiAlphaNumericCS retain];
}
}
+ (id)defaultFolderManager {
NSString *s;
NSURL *url;
if (fm) return fm;
s = [[NSUserDefaults standardUserDefaults] stringForKey:@"OCSFolderInfoURL"];
if ([s length] == 0) {
NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not configured.",
__PRETTY_FUNCTION__);
return nil;
}
if ((url = [NSURL URLWithString:s]) == nil) {
NSLog(@"ERROR(%s): default 'OCSFolderInfoURL' is not a valid URL: '%@'",
__PRETTY_FUNCTION__, s);
return nil;
}
if ((fm = [[self alloc] initWithFolderInfoLocation:url]) == nil) {
NSLog(@"ERROR(%s): could not create folder manager with URL: '%@'",
__PRETTY_FUNCTION__, [url absoluteString]);
return nil;
}
NSLog(@"Note: setup default manager at: %@", url);
return fm;
}
- (NSDictionary *)loadDefaultFolderTypes {
NSMutableDictionary *typeMap;
NSArray *types;
unsigned i, count;
types = [[GCSFolderType resourceLocator] lookupAllFilesWithExtension:@"ocs"
doReturnFullPath:NO];
if ((count = [types count]) == 0) {
[self logWithFormat:@"Note: no GCS folder types found."];
return nil;
}
typeMap = [NSMutableDictionary dictionaryWithCapacity:count];
[self logWithFormat:@"Note: loading %d GCS folder types:", count];
for (i = 0, count = [types count]; i < count; i++) {
NSString *type;
GCSFolderType *typeObject;
type = [[types objectAtIndex:i] stringByDeletingPathExtension];
typeObject = [[GCSFolderType alloc] initWithFolderTypeName:type];
[self logWithFormat:@" %@: %s",
type, [typeObject isNotNull] ? "OK" : "FAIL"];
[typeMap setObject:typeObject forKey:type];
[typeObject release];
}
return typeMap;
}
- (id)initWithFolderInfoLocation:(NSURL *)_url {
if (_url == nil) {
[self logWithFormat:@"ERROR(%s): missing folder info url!",
__PRETTY_FUNCTION__];
[self release];
return nil;
}
if ((self = [super init])) {
self->channelManager = [[GCSChannelManager defaultChannelManager] retain];
self->folderInfoLocation = [_url retain];
if ([[self folderInfoTableName] length] == 0) {
[self logWithFormat:@"ERROR(%s): missing tablename in URL: %@",
__PRETTY_FUNCTION__, [_url absoluteString]];
[self release];
return nil;
}
/* register default folder types */
self->nameToType = [[self loadDefaultFolderTypes] copy];
}
return self;
}
- (void)dealloc {
[self->nameToType release];
[self->folderInfoLocation release];
[self->channelManager release];
[super dealloc];
}
/* accessors */
- (NSURL *)folderInfoLocation {
return self->folderInfoLocation;
}
- (NSString *)folderInfoTableName {
return [[self folderInfoLocation] gcsTableName];
}
/* connection */
- (GCSChannelManager *)channelManager {
return self->channelManager;
}
- (EOAdaptorChannel *)acquireOpenChannel {
EOAdaptorChannel *ch;
ch = [[self channelManager] acquireOpenChannelForURL:
[self folderInfoLocation]];
return ch;
}
- (void)releaseChannel:(EOAdaptorChannel *)_channel {
[[self channelManager] releaseChannel:_channel];
if (debugOn) [self debugWithFormat:@"released channel: %@", _channel];
}
- (BOOL)canConnect {
return [[self channelManager] canConnect:[self folderInfoLocation]];
}
- (NSArray *)performSQL:(NSString *)_sql {
EOAdaptorChannel *channel;
NSException *ex;
NSMutableArray *rows;
NSDictionary *row;
NSArray *attrs;
/* acquire channel */
if ((channel = [self acquireOpenChannel]) == nil) {
if (debugOn) [self debugWithFormat:@"could not acquire channel!"];
return nil;
}
if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
/* run SQL */
if ((ex = [channel evaluateExpressionX:_sql]) != nil) {
[self logWithFormat:@"ERROR(%s): cannot execute\n SQL '%@':\n %@",
__PRETTY_FUNCTION__, _sql, ex];
[self releaseChannel:channel];
return nil;
}
/* fetch results */
attrs = [channel describeResults:NO /* do not beautify names */];
rows = [NSMutableArray arrayWithCapacity:16];
while ((row = [channel fetchAttributes:attrs withZone:NULL]) != nil)
[rows addObject:row];
[self releaseChannel:channel];
return rows;
}
/* row factory */
- (GCSFolder *)folderForRecord:(NSDictionary *)_record {
GCSFolder *folder;
GCSFolderType *folderType;
NSString *folderTypeName, *locationString, *folderName, *path;
NSNumber *folderId;
NSURL *location, *quickLocation, *aclLocation;
if (_record == nil) return nil;
folderTypeName = [_record objectForKey:@"c_folder_type"];
if (![folderTypeName isNotNull]) {
[self logWithFormat:@"ERROR(%s): missing type in folder: %@",
__PRETTY_FUNCTION__, _record];
return nil;
}
if ((folderType = [self folderTypeWithName:folderTypeName]) == nil) {
[self logWithFormat:
@"ERROR(%s): could not resolve type '%@' of folder: %@",
__PRETTY_FUNCTION__,
folderTypeName, [_record valueForKey:@"c_path"]];
return nil;
}
folderId = [_record objectForKey:@"c_folder_id"];
folderName = [_record objectForKey:@"c_path"];
path = [self pathFromInternalName:folderName];
locationString = [_record objectForKey:@"c_location"];
location = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
if (location == nil) {
[self logWithFormat:@"ERROR(%s): missing folder location in record: %@",
__PRETTY_FUNCTION__, _record];
return nil;
}
locationString = [_record objectForKey:@"c_quick_location"];
quickLocation = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
if (quickLocation == nil) {
[self logWithFormat:@"WARNING(%s): missing quick location in record: %@",
__PRETTY_FUNCTION__, _record];
}
locationString = [_record objectForKey:@"c_acl_location"];
aclLocation = [locationString isNotNull]
? [NSURL URLWithString:locationString]
: nil;
folder = [[GCSFolder alloc] initWithPath:path primaryKey:folderId
folderTypeName:folderTypeName
folderType:folderType
location:location quickLocation:quickLocation
aclLocation:aclLocation
folderManager:self];
return [folder autorelease];
}
/* path SQL */
- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
{
/* generates a WHERE qualifier for matching the "quick" entries */
NSMutableString *sql;
unsigned i, count;
if ((count = [_names count]) == 0) {
[self debugWithFormat:@"WARNING(%s): passed in empty name array!",
__PRETTY_FUNCTION__];
return @"1 = 2";
}
sql = [NSMutableString stringWithCapacity:(count * 8)];
for (i = 0; i < quickPathCount; i++) {
NSString *pathColumn;
char buf[32];
sprintf(buf, GCSPathColumnPattern, (i + 1));
pathColumn = [[NSString alloc] initWithCString:buf];
/* Note: the AND addition must be inside the if's for non-exact stuff */
if (i < count) {
/* exact match, regular column */
if ([sql length] > 0) [sql appendString:@" AND "];
[sql appendString:pathColumn];
[sql appendFormat:@" = '%@'", [_names objectAtIndex:i]];
}
else if (_beExact) {
/* exact match, ensure that all additional quick-cols are NULL */
if ([sql length] > 0) [sql appendString:@" AND "];
[sql appendString:pathColumn];
[sql appendString:@" IS NULL"];
if (debugPathTraversal) [self logWithFormat:@"BE EXACT, NULL columns"];
}
else if (_directSubs) {
/* fetch immediate subfolders */
if ([sql length] > 0) [sql appendString:@" AND "];
[sql appendString:pathColumn];
if (i == count) {
/* if it is a direct subfolder, the next path cannot be empty */
[sql appendString:@" IS NOT NULL"];
if (debugPathTraversal)
[self logWithFormat:@"DIRECT SUBS, first level"];
}
else {
/* but for 'direct' subfolders, all following things must be empty */
[sql appendString:@" IS NULL"];
if (debugPathTraversal)
[self logWithFormat:@"DIRECT SUBS, lower level"];
}
}
[pathColumn release];
}
if (_beExact && (count > quickPathCount)) {
[sql appendString:@" AND c_foldername = '"];
[sql appendString:[_names lastObject]];
[sql appendString:@"'"];
}
return sql;
}
- (NSString *)generateSQLPathFetchForInternalNames:(NSArray *)_names
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
{
/* fetches the 'path' subset for a given quick-names */
NSMutableString *sql;
NSString *ws;
ws = [self generateSQLWhereForInternalNames:_names
exactMatch:_beExact orDirectSubfolderMatch:_directSubs];
if ([ws length] == 0)
return nil;
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT c_path FROM "];
[sql appendString:[self folderInfoTableName]];
[sql appendString:@" WHERE "];
[sql appendString:ws];
if (debugSQLGen) [self logWithFormat:@"PathFetch-SQL: %@", sql];
return sql;
}
/* handling folder names */
- (BOOL)_isStandardizedPath:(NSString *)_path {
if (![_path isAbsolutePath]) return NO;
if ([_path rangeOfString:@".."].length > 0) return NO;
if ([_path rangeOfString:@"~"].length > 0) return NO;
if ([_path rangeOfString:@"//"].length > 0) return NO;
return YES;
}
- (NSString *)internalNameFromPath:(NSString *)_path {
// TODO: ensure proper path and SQL escaping!
if (![self _isStandardizedPath:_path]) {
[self debugWithFormat:@"%s: not a standardized path: '%@'",
__PRETTY_FUNCTION__, _path];
return nil;
}
if ([_path hasSuffix:@"/"] && [_path length] > 1)
_path = [_path substringToIndex:([_path length] - 1)];
return _path;
}
- (NSArray *)internalNamesFromPath:(NSString *)_path {
NSString *fname;
NSArray *fnames;
if ((fname = [self internalNameFromPath:_path]) == nil)
return nil;
if ([fname hasPrefix:@"/"])
fname = [fname substringFromIndex:1];
fnames = [fname componentsSeparatedByString:@"/"];
if ([fnames count] == 0)
return nil;
return fnames;
}
- (NSString *)pathFromInternalName:(NSString *)_name {
/* for incomplete pathes, like '/Users/helge/' */
return _name;
}
- (NSString *)pathPartFromInternalName:(NSString *)_name {
/* for incomplete pathes, like 'Users/' */
return _name;
}
- (NSDictionary *)filterRecords:(NSArray *)_records forPath:(NSString *)_path {
unsigned i, count;
NSString *name;
if (_records == nil) return nil;
if ((name = [self internalNameFromPath:_path]) == nil) return nil;
for (i = 0, count = [_records count]; i < count; i++) {
NSDictionary *record;
NSString *recName;
record = [_records objectAtIndex:i];
recName = [record objectForKey:GCSPathRecordName];
#if 0
[self logWithFormat:@"check '%@' vs '%@' (%@)...",
name, recName, [_records objectAtIndex:i]];
#endif
if ([name isEqualToString:recName])
return [_records objectAtIndex:i];
}
return nil;
}
- (BOOL)folderExistsAtPath:(NSString *)_path {
NSString *fname;
NSArray *fnames, *records;
NSString *sql;
unsigned count;
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return NO;
}
sql = [self generateSQLPathFetchForInternalNames:fnames
exactMatch:YES orDirectSubfolderMatch:NO];
if ([sql length] == 0) {
[self debugWithFormat:@"got no SQL for names: %@", fnames];
return NO;
}
if ((records = [self performSQL:sql]) == nil) {
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
return NO;
}
if ((count = [records count]) == 0)
return NO;
fname = [self internalNameFromPath:_path];
if (count == 1) {
NSDictionary *record;
NSString *sname;
record = [records objectAtIndex:0];
sname = [record objectForKey:GCSPathRecordName];
return [fname isEqualToString:sname];
}
[self logWithFormat:@"records: %@", records];
return NO;
}
- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_recursive{
NSMutableArray *result;
NSString *fname;
NSArray *fnames, *records;
NSString *sql;
unsigned i, count;
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return nil;
}
sql = [self generateSQLPathFetchForInternalNames:fnames
exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)];
if ([sql length] == 0) {
[self debugWithFormat:@"got no SQL for names: %@", fnames];
return nil;
}
if ((records = [self performSQL:sql]) == nil) {
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
return nil;
}
if ((count = [records count]) == 0)
return emptyArray;
result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)];
fname = [self internalNameFromPath:_path];
fname = [fname stringByAppendingString:@"/"]; /* add slash */
for (i = 0; i < count; i++) {
NSDictionary *record;
NSString *sname, *spath;
record = [records objectAtIndex:i];
sname = [record objectForKey:GCSPathRecordName];
if (![sname hasPrefix:fname]) /* does not match at all ... */
continue;
/* strip prefix and following slash */
sname = [sname substringFromIndex:[fname length]];
spath = [self pathPartFromInternalName:sname];
if (_recursive) {
if ([spath length] > 0) [result addObject:spath];
}
else {
/* direct children only, so exclude everything with a slash */
if ([sname rangeOfString:@"/"].length == 0 && [spath length] > 0)
[result addObject:spath];
}
}
return result;
}
- (GCSFolder *)folderAtPath:(NSString *)_path {
NSMutableString *sql;
NSArray *fnames, *records;
NSString *ws;
NSDictionary *record;
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return nil;
}
/* generate SQL to fetch folder attributes */
ws = [self generateSQLWhereForInternalNames:fnames
exactMatch:YES orDirectSubfolderMatch:NO];
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT "];
[sql appendString:@"c_folder_id, "];
[sql appendString:@"c_path, "];
[sql appendString:@"c_location, c_quick_location, c_acl_location,"];
[sql appendString:@" c_folder_type"];
[sql appendString:@" FROM "];
[sql appendString:[self folderInfoTableName]];
[sql appendString:@" WHERE "];
[sql appendString:ws];
if (debugSQLGen) [self logWithFormat:@"folderAtPath: %@", sql];
/* fetching */
if ((records = [self performSQL:sql]) == nil) {
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
return nil;
}
// TODO: need to filter on path
// required when we start to have deeper hierarchies
// => isn't that already done below?
if ([records count] != 1) {
if ([records count] == 0) {
[self debugWithFormat:@"found no records for path: '%@'", _path];
return nil;
}
[self logWithFormat:@"ERROR(%s): more than one row for path: '%@'",
__PRETTY_FUNCTION__, _path];
return nil;
}
if ((record = [self filterRecords:records forPath:_path]) == nil) {
[self debugWithFormat:@"found no record for path: '%@'", _path];
return nil;
}
return [self folderForRecord:record];
}
- (NSString *)baseTableNameWithUID:(NSString *)_uid {
NSDate *now;
unichar currentChar;
unsigned int count, max, done;
NSMutableString *newUID;
newUID = [NSMutableString string];
now = [NSDate date];
max = [_uid length];
done = 0;
count = 0;
while (done < 8 && count < max)
{
currentChar = [_uid characterAtIndex: count];
if ([asciiAlphaNumericCS characterIsMember: currentChar])
{
[newUID appendFormat: @"%c", currentChar];
done++;
}
count++;
}
return [NSString stringWithFormat: @"%@%u",
newUID, [now timeIntervalSince1970]];
}
- (NSException *)createFolderOfType:(NSString *)_type
withName:(NSString*)_name atPath:(NSString *)_path
{
// TBD: badly broken, needs to be wrapped in a transaction.
// TBD: would be best to perform all operations as a single SQL statement.
GCSFolderType *ftype;
NSString *tableName, *quickTableName, *aclTableName;
NSString *baseURL, *pathElement;
EOAdaptorChannel <GCSEOAdaptorChannel> *channel;
NSEnumerator *pathElements;
NSMutableArray *paths;
NSException *error;
NSString *sql;
paths = [[NSMutableArray alloc] initWithCapacity: 5];
pathElements = [[_path componentsSeparatedByString: @"/"] objectEnumerator];
while ((pathElement = [pathElements nextObject]) != nil) {
NSString *p = [[NSString alloc] initWithFormat: @"'%@'", pathElement];
[paths addObject: p];
[p release]; p = nil;
}
while ([paths count] < 5)
[paths addObject: @"NULL"];
// TBD: fix SQL injection issue!
sql = [NSString stringWithFormat: @"SELECT * FROM %@ WHERE c_path = '%@'",
[self folderInfoTableName], _path];
if ([[self performSQL: sql] isNotEmpty]) {
return [NSException exceptionWithName:@"GCSExitingFolder"
reason:@"a folder already exists at that path"
userInfo:nil];
}
if ((ftype = [self folderTypeWithName:_type]) == nil) {
return [NSException exceptionWithName:@"GCSMissingFolderType"
reason:@"missing folder type"userInfo:nil];
}
if ((channel = [self acquireOpenChannel]) == nil) {
return [NSException exceptionWithName:@"GCSNoChannel"
reason:@"could not open channel"
userInfo:nil];
}
tableName = [self baseTableNameWithUID: [paths objectAtIndex: 2]];
quickTableName = [tableName stringByAppendingString: @"_quick"];
aclTableName = [tableName stringByAppendingString: @"_acl"];
sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
if ((error = [channel evaluateExpressionX:sql]) != nil)
; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
sql = [@"DROP TABLE " stringByAppendingString:tableName];
if ((error = [channel evaluateExpressionX:sql]) != nil)
; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
sql = [@"DROP TABLE " stringByAppendingString:aclTableName];
if ((error = [channel evaluateExpressionX:sql]) != nil)
; // 'DROP TABLE' is allowed to fail (DROP IF EXISTS is not in PG<8.2)
if ((error = [channel createGCSFolderTableWithName: tableName]) != nil)
return error;
sql = [ftype sqlQuickCreateWithTableName: quickTableName];
if (debugSQLGen) [self logWithFormat:@"quick-Create: %@", sql];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
/* 'rollback' TBD: wrap in proper tx */
sql = [@"DROP TABLE " stringByAppendingString:tableName];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self warnWithFormat:@"failed to drop freshly created table: %@",
tableName];
}
return error;
}
if (debugSQLGen) [self logWithFormat:@"acl-Create: %@", sql];
if ((error = [channel createGCSFolderACLTableWithName: aclTableName])
!= nil) {
/* 'rollback' TBD: wrap in proper tx */
sql = [@"DROP TABLE " stringByAppendingString:quickTableName];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self warnWithFormat:@"failed to drop freshly created table: %@",
tableName];
}
sql = [@"DROP TABLE " stringByAppendingString:tableName];
if ((error = [channel evaluateExpressionX:sql]) != nil) {
[self warnWithFormat:@"failed to drop freshly created table: %@",
tableName];
}
return error;
}
// TBD: fix SQL injection issues
baseURL
= [[folderInfoLocation absoluteString] stringByDeletingLastPathComponent];
sql = [NSString stringWithFormat: @"INSERT INTO %@"
@" (c_path, c_path1, c_path2, c_path3, c_path4,"
@" c_foldername, c_location, c_quick_location,"
@" c_acl_location, c_folder_type)"
@" VALUES ('%@', %@, %@, %@, %@, '%@', '%@/%@',"
@" '%@/%@', '%@/%@', '%@')",
[self folderInfoTableName], _path,
[paths objectAtIndex: 1], [paths objectAtIndex: 2],
[paths objectAtIndex: 3], [paths objectAtIndex: 4],
_name,
baseURL, tableName,
baseURL, quickTableName,
baseURL, aclTableName,
_type];
if ((error = [channel evaluateExpressionX:sql]) != nil)
return error;
[paths release]; paths = nil;
[self releaseChannel: channel];
return nil;
}
- (NSException *)deleteFolderAtPath:(NSString *)_path {
GCSFolder *folder;
NSArray *fnames;
NSString *sql, *ws;
EOAdaptorChannel *channel;
NSException *ex;
if ((folder = [self folderAtPath:_path]) == nil) {
return [NSException exceptionWithName:@"GCSMissingFolder"
reason:@"missing folder"
userInfo:nil];
}
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return nil;
}
ws = [self generateSQLWhereForInternalNames:fnames
exactMatch:YES orDirectSubfolderMatch:NO];
sql = [NSString stringWithFormat: @"DELETE FROM %@ WHERE %@",
[self folderInfoTableName], ws];
if ((channel = [self acquireOpenChannel]) == nil) {
return [NSException exceptionWithName:@"GCSNoChannel"
reason:@"could not "
userInfo:nil];
}
if ((ex = [channel evaluateExpressionX:sql]) != nil) {
[self releaseChannel:channel];
return ex;
}
[self releaseChannel:channel];
return [folder deleteFolder];
}
/* folder types */
- (GCSFolderType *)folderTypeWithName:(NSString *)_name {
NSString *specificName;
GCSFolderType *type;
if ([_name length] == 0)
_name = GCSGenericFolderTypeName;
specificName = [NSString stringWithFormat: @"%@-%@",
_name, [folderInfoLocation scheme]];
type = [self->nameToType objectForKey: [specificName lowercaseString]];
if (!type)
type = [self->nameToType objectForKey:[_name lowercaseString]];
return type;
}
/* cache management */
- (void)reset {
/* does nothing in the moment, but we need a way to signal refreshes */
}
/* debugging */
- (BOOL)isDebuggingEnabled {
return debugOn;
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[ms appendFormat:@" url=%@", [self->folderInfoLocation absoluteString]];
[ms appendFormat:@" channel-manager=0x%p", [self channelManager]];
[ms appendString:@">"];
return ms;
}
@end /* GCSFolderManager */

View File

@@ -0,0 +1,82 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSFolderType_H__
#define __GDLContentStore_GCSFolderType_H__
/*
GCSFolderType
This is the "model" of a folder, it specifies what quick attributes are
available, in which tables it is stored and how it is retrieved.
For now, we only support one 'quick' table (we might want to have multiple
ones in the future).
Note: using the 'folderQualifier' we are actually prepared for 'multiple
folders in a single table' setups. So in case we want to go that
route later, we can still do it :-)
*/
#import <Foundation/NSObject.h>
@class NSString, NSArray, NSDictionary;
@class EOQualifier;
@class NGResourceLocator;
@class GCSFolder, GCSFieldExtractor;
@interface GCSFolderType : NSObject
{
NSString *blobTablePattern; // eg 'SOGo_$folderId$_blob
NSString *quickTablePattern; // eg 'SOGo_$folderId$_quick
NSArray *fields; // GCSFieldInfo objects
NSDictionary *fieldDict; // maps a name to GCSFieldInfo
EOQualifier *folderQualifier; // to further limit the table set
NSString *extractorClassName;
GCSFieldExtractor *extractor;
}
+ (id)folderTypeWithName:(NSString *)_type;
- (id)initWithPropertyList:(id)_plist;
- (id)initWithContentsOfFile:(NSString *)_path;
- (id)initWithFolderTypeName:(NSString *)_typeName;
/* operations */
- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder;
- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder;
/* generating SQL */
- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName;
/* quick support */
- (GCSFieldExtractor *)quickExtractor;
/* locator used to find .ocs files */
+ (NGResourceLocator *)resourceLocator;
@end
#endif /* __GDLContentStore_GCSFolderType_H__ */

View File

@@ -0,0 +1,198 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSFolderType.h"
#include "GCSFolder.h"
#include "GCSFieldInfo.h"
#include "GCSFieldExtractor.h"
#include "common.h"
#include <EOControl/EOKeyValueCoding.h>
#include <NGExtensions/NGResourceLocator.h>
@implementation GCSFolderType
- (id)initWithPropertyList:(id)_plist {
if ((self = [super init])) {
NSDictionary *plist = _plist;
self->blobTablePattern = [[plist objectForKey:@"blobTablePattern"] copy];
self->quickTablePattern = [[plist objectForKey:@"quickTablePattern"]copy];
self->extractorClassName =
[[plist objectForKey:@"extractorClassName"] copy];
// TODO: qualifier;
self->fields = [[GCSFieldInfo fieldsForPropertyList:
[plist objectForKey:@"fields"]] retain];
}
return self;
}
- (id)initWithContentsOfFile:(NSString *)_path {
NSDictionary *plist;
plist = [NSDictionary dictionaryWithContentsOfFile:_path];
if (plist == nil) {
NSLog(@"ERROR(%s): could not read dictionary at path %@",
__PRETTY_FUNCTION__, _path);
[self release];
return nil;
}
return [self initWithPropertyList:plist];
}
+ (NGResourceLocator *)resourceLocator {
NGResourceLocator *loc;
// TODO: fix me, GCS instead of OCS
loc = [NGResourceLocator resourceLocatorForGNUstepPath:
@"Library/OCSTypeModels"
fhsPath:@"share/ocs"];
return loc;
}
+ (id)folderTypeWithName:(NSString *)_typeName {
NSString *filename, *path;
// TODO: fix me, GCS instead of OCS
filename = [_typeName stringByAppendingPathExtension:@"ocs"];
path = [[self resourceLocator] lookupFileWithName:filename];
if (path != nil)
return [[self alloc] initWithContentsOfFile:path];
NSLog(@"ERROR(%s): did not find model for type: '%@'",
__PRETTY_FUNCTION__, _typeName);
return nil;
}
- (id)initWithFolderTypeName:(NSString *)_typeName {
// DEPRECATED
[self release];
return [[GCSFolderType folderTypeWithName:_typeName] retain];
}
- (void)dealloc {
[self->extractor release];
[self->extractorClassName release];
[self->blobTablePattern release];
[self->quickTablePattern release];
[self->fields release];
[self->fieldDict release];
[self->folderQualifier release];
[super dealloc];
}
/* operations */
- (NSString *)blobTableNameForFolder:(GCSFolder *)_folder {
return [self->blobTablePattern
stringByReplacingVariablesWithBindings:_folder];
}
- (NSString *)quickTableNameForFolder:(GCSFolder *)_folder {
return [self->quickTablePattern
stringByReplacingVariablesWithBindings:_folder];
}
- (EOQualifier *)qualifierForFolder:(GCSFolder *)_folder {
NSArray *keys;
NSDictionary *bindings;
keys = [[self->folderQualifier allQualifierKeys] allObjects];
if ([keys count] == 0)
return self->folderQualifier;
bindings = [_folder valuesForKeys:keys];
return [self->folderQualifier qualifierWithBindings:bindings
requiresAllVariables:NO];
}
/* generating SQL */
- (NSString *)sqlQuickCreateWithTableName:(NSString *)_tabName {
NSMutableString *sql;
unsigned i, count;
sql = [NSMutableString stringWithCapacity:512];
[sql appendString:@"CREATE TABLE "];
[sql appendString:_tabName];
[sql appendString:@" (\n"];
count = [self->fields count];
for (i = 0; i < count; i++) {
if (i > 0) [sql appendString:@",\n"];
[sql appendString:@" "];
[sql appendString:[[self->fields objectAtIndex:i] sqlCreateSection]];
}
[sql appendString:@"\n)"];
return sql;
}
/* quick support */
- (GCSFieldExtractor *)quickExtractor {
Class clazz;
if (self->extractor != nil) {
return [self->extractor isNotNull]
? self->extractor : (GCSFieldExtractor *)nil;
}
clazz = self->extractorClassName
? NSClassFromString(self->extractorClassName)
: [GCSFieldExtractor class];
if (clazz == Nil) {
[self logWithFormat:@"ERROR: did not find field extractor class!"];
return nil;
}
if ((self->extractor = [[clazz alloc] init]) == nil) {
[self logWithFormat:@"ERROR: could not create field extractor of class %@",
clazz];
return nil;
}
return self->extractor;
}
/* description */
- (NSString *)description {
NSMutableString *ms;
ms = [NSMutableString stringWithCapacity:256];
[ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
[ms appendFormat:@" blobtable='%@'", self->blobTablePattern];
[ms appendFormat:@" quicktable='%@'", self->quickTablePattern];
[ms appendFormat:@" fields=%@", self->fields];
[ms appendFormat:@" extractor=%@", self->extractorClassName];
if (self->folderQualifier)
[ms appendFormat:@" qualifier=%@", self->folderQualifier];
[ms appendString:@">"];
return ms;
}
@end /* GCSFolderType */

View File

@@ -0,0 +1,37 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_GCSStringFormatter_H__
#define __GDLContentStore_GCSStringFormatter_H__
#include <NGExtensions/NSString+Escaping.h>
@interface GCSStringFormatter : NSObject < NGStringEscaping >
{
}
+ (id)sharedFormatter;
- (NSString *)stringByFormattingString:(NSString *)_s;
@end
#endif /* __GDLContentStore_GCSStringFormatter_H__ */

View File

@@ -0,0 +1,63 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "GCSStringFormatter.h"
#include "common.h"
@implementation GCSStringFormatter
static NSCharacterSet *escapeSet = nil;
+ (void)initialize {
static BOOL didInit = NO;
if(didInit)
return;
didInit = YES;
escapeSet =
[[NSCharacterSet characterSetWithCharactersInString:@"\\'"] retain];
}
+ (id)sharedFormatter {
static id sharedInstance = nil;
if(!sharedInstance) {
sharedInstance = [[self alloc] init];
}
return sharedInstance;
}
- (NSString *)stringByFormattingString:(NSString *)_s {
NSString *s;
s = [_s stringByEscapingCharactersFromSet:escapeSet
usingStringEscaping:self];
return [NSString stringWithFormat:@"'%@'", s];
}
- (NSString *)stringByEscapingString:(NSString *)_s {
if([_s isEqualToString:@"\\"]) {
return @"\\\\"; /* easy ;-) */
}
return @"\\'";
}
@end /* GCSStringFormatter */

View File

@@ -0,0 +1,77 @@
# GNUstep makefiles
include ../../../config.make
include $(GNUSTEP_MAKEFILES)/common.make
include ./Version
GNUSTEP_INSTALLATION_DIR = ${GNUSTEP_LOCAL_ROOT}
ifneq ($(frameworks),yes)
LIBRARY_NAME = libGDLContentStore
else
FRAMEWORK_NAME = GDLContentStore
endif
libGDLContentStore_PCH_FILE = common.h
libGDLContentStore_SOVERSION=$(MAJOR_VERSION).$(MINOR_VERSION)
libGDLContentStore_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION).$(SUBMINOR_VERSION)
TOOL_NAME = gcs_ls gcs_mkdir gcs_cat gcs_recreatequick gcs_gensql
libGDLContentStore_HEADER_FILES_DIR = .
libGDLContentStore_HEADER_FILES_INSTALL_DIR = /GDLContentStore
FHS_HEADER_FILES_INSTALL_DIR = $(libGDLContentStore_HEADER_FILES_INSTALL_DIR)
libGDLContentStore_HEADER_FILES += \
NSURL+GCS.h \
EOAdaptorChannel+GCS.h \
\
GCSContext.h \
GCSFieldInfo.h \
GCSFolder.h \
GCSFolderManager.h \
GCSFolderType.h \
GCSChannelManager.h \
GCSFieldExtractor.h \
GCSStringFormatter.h \
libGDLContentStore_OBJC_FILES += \
NSURL+GCS.m \
EOAdaptorChannel+GCS.m \
EOQualifier+GCS.m \
\
GCSContext.m \
GCSFieldInfo.m \
GCSFolder.m \
GCSFolderManager.m \
GCSFolderType.m \
GCSChannelManager.m \
GCSFieldExtractor.m \
GCSStringFormatter.m \
gcs_ls_OBJC_FILES += gcs_ls.m
gcs_mkdir_OBJC_FILES += gcs_mkdir.m
gcs_cat_OBJC_FILES += gcs_cat.m
gcs_gensql_OBJC_FILES += gcs_gensql.m
gcs_recreatequick_OBJC_FILES += gcs_recreatequick.m
# framework support
GDLContentStore_PCH_FILE = $(libGDLContentStore_PCH_FILE)
GDLContentStore_HEADER_FILES = $(libGDLContentStore_HEADER_FILES)
GDLContentStore_OBJC_FILES = $(libGDLContentStore_OBJC_FILES)
GDLContentStore_SUBPROJECTS = $(libGDLContentStore_SUBPROJECTS)
# building
-include GNUmakefile.preamble
ifneq ($(frameworks),yes)
include $(GNUSTEP_MAKEFILES)/library.make
else
include $(GNUSTEP_MAKEFILES)/framework.make
endif
include $(GNUSTEP_MAKEFILES)/tool.make
-include GNUmakefile.postamble
include fhs.make

View File

@@ -0,0 +1,78 @@
# compilation settings
SOPE_ROOT=../..
ADDITIONAL_CPPFLAGS += -Wall
ADDITIONAL_INCLUDE_DIRS += -I. -I..
# dependencies
libGDLContentStore_LIBRARIES_DEPEND_UPON += \
-lGDLAccess \
-lNGExtensions -lEOControl \
-lDOM -lSaxObjC
GDLContentStore_LIBRARIES_DEPEND_UPON += \
-framework GDLAccess \
-framework NGExtensions -framework EOControl \
-framework DOM -framework SaxObjC
ifneq ($(frameworks),yes)
GCS_TOOL_LIBS += \
-lGDLContentStore -lGDLAccess \
-lNGExtensions -lEOControl \
-lDOM -lSaxObjC
else
GCS_TOOL_LIBS += \
-framework GDLContentStore -framework GDLAccess \
-framework NGExtensions -framework EOControl \
-framework DOM -framework SaxObjC
endif
gcs_ls_TOOL_LIBS += $(GCS_TOOL_LIBS)
gcs_mkdir_TOOL_LIBS += $(GCS_TOOL_LIBS)
gcs_cat_TOOL_LIBS += $(GCS_TOOL_LIBS)
gcs_recreatequick_TOOL_LIBS += $(GCS_TOOL_LIBS)
gcs_gensql_TOOL_LIBS += $(GCS_TOOL_LIBS)
gcs_ls_PCH_FILE += common.h
gcs_mkdir_PCH_FILE += common.h
gcs_cat_PCH_FILE += common.h
gcs_recreatequick_PCH_FILE += common.h
gcs_gensql_PCH_FILE += common.h
# library/framework search pathes
DEP_DIRS = \
. \
../GDLAccess \
$(SOPE_ROOT)/sope-core/NGExtensions \
$(SOPE_ROOT)/sope-core/EOControl \
$(SOPE_ROOT)/sope-xml/DOM \
$(SOPE_ROOT)/sope-xml/SaxObjC
ifneq ($(frameworks),yes)
ADDITIONAL_LIB_DIRS += \
$(foreach dir,$(DEP_DIRS),\
-L$(GNUSTEP_BUILD_DIR)/$(dir)/$(GNUSTEP_OBJ_DIR_NAME))
else
ADDITIONAL_LIB_DIRS += \
$(foreach dir,$(DEP_DIRS),-F$(GNUSTEP_BUILD_DIR)/$(dir))
endif
SYSTEM_LIB_DIR += $(CONFIGURE_SYSTEM_LIB_DIR)
# platform specific settings
ifeq ($(FOUNDATION_LIB),apple)
libGDLContentStore_PREBIND_ADDR="0xC7700000"
libGDLContentStore_LDFLAGS += -seg1addr $(libGDLContentStore_PREBIND_ADDR)
endif
ifeq ($(findstring openbsd3, $(GNUSTEP_HOST_OS)), openbsd3)
GCS_TOOL_LIBS += -liconv
endif

View File

@@ -0,0 +1,41 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __GDLContentStore_NSURL_GCS_H__
#define __GDLContentStore_NSURL_GCS_H__
#import <Foundation/NSURL.h>
/*
"Database URLs"
We use the schema:
postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename]
*/
@interface NSURL(GCS)
- (NSString *)gcsDatabaseName;
- (NSString *)gcsTableName;
@end
#endif /* __GDLContentStore_NSURL_GCS_H__ */

View File

@@ -0,0 +1,51 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include "NSURL+GCS.h"
#include "common.h"
@implementation NSURL(GCS)
- (NSString *)gcsPathComponent:(unsigned)_idx {
NSString *p;
NSArray *pcs;
unsigned len;
p = [self path];
if ([p length] == 0)
return nil;
pcs = [p componentsSeparatedByString:@"/"];
if ((len = [pcs count]) == 0)
return nil;
if (len <= _idx)
return nil;
return [pcs objectAtIndex:_idx];
}
- (NSString *)gcsDatabaseName {
return [self gcsPathComponent:1];
}
- (NSString *)gcsTableName {
return [[self path] lastPathComponent];
}
@end /* NSURL(GCS) */

View File

@@ -0,0 +1,117 @@
Storage Backend
===============
The storage backend implements the "low level" folder abstraction, which is
basically an arbitary "BLOB" containing some document. The feature is that
we extract "quick access" / "searchable" attributes from the document content.
Further it contains the "folder management" API, as named folders can be stored
in different databases.
Note: we need a way to tell where "new" folders should be created
Note: to sync with LDAP we need to periodically delete or archive old folders
Folders have associated a type (like 'calendar') which defines the query
attributes and serialization format.
TODO
====
- fix some OCS naming
- defaults
- lookup directories
- hierarchies deeper than 4 (properly filter on path in OCS)
Open Questions
==============
System-meta-data in the blob-table or in the quick-table?
- master data belongs into the blob table
- could be regular 'NSxxx' keys to differentiate meta data from
Class Hierarchy
===============
[NSObject]
OCSContext - tracking context
OCSFolder - represents a single folder
OCSFolderManager - manages folders
OCSFolderType - the mapping info for a specific folder-type
OCSFieldInfo - mapping info for one 'quick field'
OCSChannelManager - maintains EOAdaptorChannel objects
TBD:
- field 'extractor'
- field 'value' (eg array values for participants?)
- BLOB archiver/unarchiver
Defaults
========
OCSFolderInfoURL - the DB URL where the folder-info table is located
eg: http://OGo:OGo@localhost/test/folder_info
OCSFolderManagerDebugEnabled - enable folder-manager debug logs
OCSFolderManagerSQLDebugEnabled - enable folder-manager SQL gen debug logs
OCSChannelManagerDebugEnabled - enable channel debug pooling logs
OCSChannelManagerPoolDebugEnabled - debug pool handle allocation
OCSChannelExpireAge - if that age in seconds is exceeded, a channel
will be removed from the pool
OCSChannelCollectionTimer - time in seconds. each n-seconds the pool will be
checked for channels too old
[PGDebugEnabled] - enable PostgreSQL adaptor debugging
URLs
====
"Database URLs"
We use the schema:
postgresql://[user]:[password]@[host]:[port]/[dbname]/[tablename]
Support Tools
=============
- tools we need:
- one to recreate a quick table based on the blob table
Notes
=====
- need to use http:// URLs for connect info, until generic URLs in
libFoundation are fixed (the parses breaks on the login/password parts)
QA
==
Q: Why do we use two tables, we could store the quick columns in the blob?
==
They could be in the same table. We considered using separate tables since the
quick table is likely to be recreated now and then if BLOB indexing
requirements change.
Actually one could even use different _quick tables which share a common BLOB
table.
(a quick table is nothing more than a database index and like with DB indexes
multiple ones for different requirements can make sense).
Further it might improve caching behaviour for row based caches (the quick
table is going to be queried much more often) - not sure whether this is
relevant with PostgreSQL, probably not?
Q: Can we use a VARCHAR primary key?
==
We asked in the postgres IRC channel and apparently the performance penalty of
string primary keys isn't big.
We could also use an 'internal' int sequence in addition (might be useful for
supporting ZideLook)
Motivation: the 'iCalendar' ID is a string and usually looks like a GUID.
Q: Why using VARCHAR instead of TEXT in the BLOB?
==
To quote PostgreSQL documentation:
"There are no performance differences between these three types, apart from
the increased storage size when using the blank-padded type."
So varchar(xx) is just a large TEXT. Since we intend to store mostly small
snippets of data (tiny XML fragments), we considered VARCHAR the more
appropriate type.

View File

@@ -0,0 +1,14 @@
# Version file
MAJOR_VERSION:=4
MINOR_VERSION:=7
SUBMINOR_VERSION:=49
# v4.5.29 requires libNGExtensions v4.5.161
# v4.5.26 does not require libNGiCal anymore
# v0.9.19 requires libNGiCal v4.5.40
# v0.9.18 requires libNGiCal v4.5.38
# v0.9.17 requires libNGiCal v4.5.37
# v0.9.11 requires libFoundation v1.0.63
# v0.9.11 requires libNGExtensions v4.3.125
# v0.9.7 requires libGDLAccess v1.1.35

View File

@@ -0,0 +1,36 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: common.h 673 2005-03-20 19:09:53Z helge $
#import <Foundation/Foundation.h>
#import <Foundation/NSURL.h>
#include <NGExtensions/NGExtensions.h>
#if NeXT_RUNTIME || APPLE_RUNTIME
# define objc_free(__mem__) free(__mem__)
# define objc_malloc(__size__) malloc(__size__)
# define objc_calloc(__cnt__, __size__) calloc(__cnt__, __size__)
# define objc_realloc(__ptr__, __size__) realloc(__ptr__, __size__)
# ifndef sel_eq
# define sel_eq(sela,selb) (sela==selb?YES:NO)
# endif
#endif

View File

@@ -0,0 +1,44 @@
# postprocessing
# FHS support (this is a hack and is going to be done by gstep-make!)
# NOTE: you need to define FHS_HEADER_FILES_INSTALL_DIR for one library
ifneq ($(FHS_INSTALL_ROOT),)
FHS_INCLUDE_DIR=$(FHS_INSTALL_ROOT)/include/
FHS_BIN_DIR=$(FHS_INSTALL_ROOT)/bin
FHS_LIB_DIR=$(CONFIGURE_FHS_INSTALL_LIBDIR)
NONFHS_LIBDIR="$(GNUSTEP_LIBRARIES)/$(GNUSTEP_TARGET_LDIR)/"
NONFHS_LIBNAME="$(LIBRARY_NAME)$(LIBRARY_NAME_SUFFIX)$(SHARED_LIBEXT)"
NONFHS_BINDIR="$(GNUSTEP_TOOLS)/$(GNUSTEP_TARGET_LDIR)"
fhs-header-dirs ::
$(MKDIRS) $(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR)
fhs-bin-dirs ::
$(MKDIRS) $(FHS_BIN_DIR)
move-headers-to-fhs :: fhs-header-dirs
@echo "moving headers to $(FHS_INCLUDE_DIR) .."
mv $(GNUSTEP_HEADERS)$(FHS_HEADER_FILES_INSTALL_DIR)/*.h \
$(FHS_INCLUDE_DIR)$(FHS_HEADER_FILES_INSTALL_DIR)/
move-libs-to-fhs ::
@echo "moving libs to $(FHS_LIB_DIR) .."
mv $(NONFHS_LIBDIR)/$(NONFHS_LIBNAME)* $(FHS_LIB_DIR)/
move-tools-to-fhs :: fhs-bin-dirs
@echo "moving tools from $(NONFHS_BINDIR) to $(FHS_BIN_DIR) .."
for i in $(TOOL_NAME); do \
mv "$(NONFHS_BINDIR)/$${i}" $(FHS_BIN_DIR); \
done
move-to-fhs :: move-headers-to-fhs move-libs-to-fhs move-tools-to-fhs
after-install :: move-to-fhs
endif

View File

@@ -0,0 +1,122 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSObject.h>
@class NSUserDefaults, NSArray;
@class GCSFolderManager;
@interface Tool : NSObject
{
NSUserDefaults *ud;
GCSFolderManager *folderManager;
}
+ (int)runWithArgs:(NSArray *)_args;
- (int)run;
@end
#include <GDLContentStore/GCSFolder.h>
#include <GDLContentStore/GCSFolderManager.h>
#include "common.h"
@implementation Tool
- (id)init {
if ((self = [super init])) {
self->ud = [[NSUserDefaults standardUserDefaults] retain];
self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
}
return self;
}
- (void)dealloc {
[self->ud release];
[self->folderManager release];
[super dealloc];
}
/* operation */
- (int)runOnPath:(NSString *)_path {
GCSFolder *folder;
NSString *dirname, *filename;
NSString *content;
dirname = [_path stringByDeletingLastPathComponent];
filename = [_path lastPathComponent];
if ((folder = [self->folderManager folderAtPath:dirname]) == nil) {
[self logWithFormat:@"did not find folder for file: '%@'", dirname];
return 1;
}
if ((content = [folder fetchContentWithName:filename]) == nil) {
[self logWithFormat:@"did not find file: '%@'", _path];
return 1;
}
printf("%s\n", [content cString]);
return 0;
}
- (int)run {
NSEnumerator *e;
NSString *path;
[self logWithFormat:@"manager: %@", self->folderManager];
if (![self->folderManager canConnect]) {
[self logWithFormat:@"cannot connect folder-info database!"];
return 1;
}
e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults]
objectEnumerator];
[e nextObject]; // skip tool name
while ((path = [e nextObject]))
[self runOnPath:path];
return 0;
}
+ (int)runWithArgs:(NSArray *)_args {
return [(Tool *)[[[self alloc] init] autorelease] run];
}
@end /* Tool */
int main(int argc, char **argv, char **env) {
NSAutoreleasePool *pool;
int rc;
pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
rc = [Tool runWithArgs:
[[NSProcessInfo processInfo] argumentsWithoutDefaults]];
[pool release];
return rc;
}

View File

@@ -0,0 +1,114 @@
/*
Copyright (C) 2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSObject.h>
@class NSUserDefaults, NSArray;
@class GCSFolderManager;
@interface Tool : NSObject
{
NSUserDefaults *ud;
}
+ (int)runWithArgs:(NSArray *)_args;
- (int)run;
@end
#include <GDLContentStore/GCSFolderType.h>
#include "common.h"
@implementation Tool
- (id)init {
if ((self = [super init])) {
self->ud = [[NSUserDefaults standardUserDefaults] retain];
}
return self;
}
- (void)dealloc {
[self->ud release];
[super dealloc];
}
/* operation */
- (int)runOnTable:(NSString *)_tableName typeName:(NSString *)_typeName {
GCSFolderType *folderType;
if ((folderType = [GCSFolderType folderTypeWithName:_typeName]) != nil) {
NSString *s;
s = [folderType sqlQuickCreateWithTableName:_tableName];
fwrite([s cString], 1, [s cStringLength], stdout);
printf("\n");
}
else {
fprintf(stderr, "ERROR: did not find GCS type: '%s'\n",
[_typeName cString]);
}
return 0;
}
- (int)run {
NSEnumerator *e;
NSString *tableName, *typeName;
e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults]
objectEnumerator];
[e nextObject]; // skip tool name
while ((tableName = [e nextObject]) != nil) {
typeName = [e nextObject];
if (typeName == nil) {
[self logWithFormat:@"got tablename '%@' but no type?!", tableName];
break;
}
[self runOnTable:tableName typeName:typeName];
}
return 0;
}
+ (int)runWithArgs:(NSArray *)_args {
return [(Tool *)[[[self alloc] init] autorelease] run];
}
@end /* Tool */
int main(int argc, char **argv, char **env) {
NSAutoreleasePool *pool;
int rc;
pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
rc = [Tool runWithArgs:
[[NSProcessInfo processInfo] argumentsWithoutDefaults]];
[pool release];
return rc;
}

View File

@@ -0,0 +1,140 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSObject.h>
@class NSUserDefaults, NSArray;
@class GCSFolderManager;
@interface Tool : NSObject
{
NSUserDefaults *ud;
GCSFolderManager *folderManager;
}
+ (int)runWithArgs:(NSArray *)_args;
- (int)run;
@end
#include <GDLContentStore/GCSFolder.h>
#include <GDLContentStore/GCSFolderManager.h>
#include "common.h"
@implementation Tool
- (id)init {
if ((self = [super init])) {
self->ud = [[NSUserDefaults standardUserDefaults] retain];
self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
}
return self;
}
- (void)dealloc {
[self->ud release];
[self->folderManager release];
[super dealloc];
}
/* operation */
- (int)runOnPath:(NSString *)_path {
NSArray *subfolders;
unsigned i, count;
GCSFolder *folder;
[self logWithFormat:@"ls path: '%@'", _path];
#if 0 // we do not necessarily need the whole hierarchy
if (![self->folderManager folderExistsAtPath:_path])
[self logWithFormat:@"folder does not exist: '%@'", _path];
#endif
subfolders = [self->folderManager
listSubFoldersAtPath:_path
recursive:[ud boolForKey:@"r"]];
if (subfolders == nil) {
[self logWithFormat:@"cannot list folder: '%@'", _path];
return 1;
}
for (i = 0, count = [subfolders count]; i < count; i++) {
printf("%s\n", [[subfolders objectAtIndex:i] cString]);
}
folder = [self->folderManager folderAtPath:_path];
if ([folder isNotNull]) {
NSLog(@"folder: %@", folder);
NSLog(@" can%s connect store: %@", [folder canConnectStore] ? "" : "not",
[[folder location] absoluteString]);
NSLog(@" can%s connect quick: %@", [folder canConnectQuick] ? "" : "not",
[[folder quickLocation] absoluteString]);
}
else {
NSLog(@"ERROR: could not create folder object for path: '%@'", _path);
}
return 0;
}
- (int)run {
NSEnumerator *e;
NSString *path;
[self logWithFormat:@"manager: %@", self->folderManager];
if (![self->folderManager canConnect]) {
[self logWithFormat:@"cannot connect folder-info database!"];
return 1;
}
e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults]
objectEnumerator];
[e nextObject]; // skip tool name
while ((path = [e nextObject]) != nil)
[self runOnPath:path];
return 0;
}
+ (int)runWithArgs:(NSArray *)_args {
return [(Tool *)[[[self alloc] init] autorelease] run];
}
@end /* Tool */
int main(int argc, char **argv, char **env) {
NSAutoreleasePool *pool;
int rc;
pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
rc = [Tool runWithArgs:
[[NSProcessInfo processInfo] argumentsWithoutDefaults]];
[pool release];
return rc;
}

View File

@@ -0,0 +1,126 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSObject.h>
@class NSUserDefaults, NSArray;
@class GCSFolderManager;
@interface Tool : NSObject
{
NSUserDefaults *ud;
GCSFolderManager *folderManager;
}
+ (int)runWithArgs:(NSArray *)_args;
- (int)run;
@end
#include <GDLContentStore/GCSFolder.h>
#include <GDLContentStore/GCSFolderManager.h>
#include "common.h"
@implementation Tool
- (id)init {
if ((self = [super init])) {
self->ud = [[NSUserDefaults standardUserDefaults] retain];
self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
}
return self;
}
- (void)dealloc {
[self->ud release];
[self->folderManager release];
[super dealloc];
}
/* operation */
- (int)runOnPath:(NSString *)_path type:(NSString *)_type {
NSException *error;
[self logWithFormat:@"mkdir %@ at path: '%@'", _type, _path];
if ([self->folderManager folderExistsAtPath:_path]) {
[self logWithFormat:@"folder already exist at path: '%@'", _path];
return 1;
}
if ((error = [self->folderManager createFolderOfType:_type atPath:_path])) {
[self logWithFormat:@"creation of folder %@ at %@ failed: %@",
_type, _path, error];
return 1;
}
if ([self->folderManager folderExistsAtPath:_path])
[self logWithFormat:@"CREATED."];
else
[self logWithFormat:@"cannot find fresh folder?"];
return 0;
}
- (int)run {
NSEnumerator *e;
NSString *type;
NSString *path;
[self logWithFormat:@"manager: %@", self->folderManager];
if (![self->folderManager canConnect]) {
[self logWithFormat:@"cannot connect folder-info database!"];
return 1;
}
e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults]
objectEnumerator];
[e nextObject]; // skip tool name
type = [[[e nextObject] copy] autorelease];
while ((path = [e nextObject]))
[self runOnPath:path type:type];
return 0;
}
+ (int)runWithArgs:(NSArray *)_args {
return [(Tool *)[[[self alloc] init] autorelease] run];
}
@end /* Tool */
int main(int argc, char **argv, char **env) {
NSAutoreleasePool *pool;
int rc;
pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
rc = [Tool runWithArgs:
[[NSProcessInfo processInfo] argumentsWithoutDefaults]];
[pool release];
return rc;
}

View File

@@ -0,0 +1,121 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSObject.h>
@class NSUserDefaults, NSArray;
@class GCSFolderManager;
@interface Tool : NSObject
{
NSUserDefaults *ud;
GCSFolderManager *folderManager;
}
+ (int)runWithArgs:(NSArray *)_args;
- (int)run;
@end
#include <GDLContentStore/GCSFolder.h>
#include <GDLContentStore/GCSFolderManager.h>
#include "common.h"
@implementation Tool
- (id)init {
if ((self = [super init])) {
self->ud = [[NSUserDefaults standardUserDefaults] retain];
self->folderManager = [[GCSFolderManager defaultFolderManager] retain];
}
return self;
}
- (void)dealloc {
[self->ud release];
[self->folderManager release];
[super dealloc];
}
/* operation */
- (int)runOnPath:(NSString *)_path {
GCSFolder *folder;
[self logWithFormat:@"update quick from store at path: '%@'", _path];
if (![self->folderManager folderExistsAtPath:_path]) {
[self logWithFormat:@"no folder exist at path: '%@'", _path];
return 1;
}
if ((folder = [self->folderManager folderAtPath:_path]) == nil) {
[self logWithFormat:@"got no folder object for path: '%@'", _path];
return 2;
}
[self logWithFormat:@" folder: %@", folder];
// TODO:
[self logWithFormat:@" should recreate folder .."];
return 0;
}
- (int)run {
NSEnumerator *e;
NSString *path;
[self logWithFormat:@"manager: %@", self->folderManager];
if (![self->folderManager canConnect]) {
[self logWithFormat:@"cannot connect folder-info database!"];
return 1;
}
e = [[[NSProcessInfo processInfo] argumentsWithoutDefaults]
objectEnumerator];
[e nextObject]; // skip tool name
while ((path = [e nextObject]))
[self runOnPath:path];
return 0;
}
+ (int)runWithArgs:(NSArray *)_args {
return [(Tool *)[[[self alloc] init] autorelease] run];
}
@end /* Tool */
int main(int argc, char **argv, char **env) {
NSAutoreleasePool *pool;
int rc;
pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
rc = [Tool runWithArgs:
[[NSProcessInfo processInfo] argumentsWithoutDefaults]];
[pool release];
return rc;
}

View File

@@ -0,0 +1,435 @@
Index: sope-mime/NGImap4/NGImap4Connection.m
===================================================================
--- sope-mime/NGImap4/NGImap4Connection.m (revision 1544)
+++ sope-mime/NGImap4/NGImap4Connection.m (working copy)
@@ -381,7 +381,7 @@
if (debugCache) [self logWithFormat:@" no folders cached yet .."];
- result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*")
+ result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"")
pattern:@"*"];
if (![[result valueForKey:@"result"] boolValue]) {
[self errorWithFormat:@"Could not list mailbox hierarchy!"];
Index: sope-mime/NGImap4/NGImap4ResponseParser.m
===================================================================
--- sope-mime/NGImap4/NGImap4ResponseParser.m (revision 1544)
+++ sope-mime/NGImap4/NGImap4ResponseParser.m (working copy)
@@ -84,6 +84,8 @@
static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
BOOL isBodyStructure);
+static NSArray *_parseLanguages();
+
static NSString *_parseBodyString(NGImap4ResponseParser *self,
BOOL _convertString);
static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
@@ -1627,6 +1629,29 @@
return _parseBodyDecodeString(self, _convertString, NO /* no decode */);
}
+static NSArray *_parseLanguages(NGImap4ResponseParser *self) {
+ NSMutableArray *languages;
+ NSString *language;
+
+ languages = [NSMutableArray array];
+ if (_la(self, 0) == '(') {
+ while (_la(self, 0) != ')') {
+ _consume(self,1);
+ language = _parseBodyString(self, YES);
+ if ([language length])
+ [languages addObject: language];
+ }
+ _consume(self,1);
+ }
+ else {
+ language = _parseBodyString(self, YES);
+ if ([language length])
+ [languages addObject: language];
+ }
+
+ return languages;
+}
+
static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
{
NSMutableDictionary *list;
@@ -1734,10 +1759,11 @@
*encoding, *bodysize;
NSDictionary *parameterList;
NSMutableDictionary *dict;
+ NSArray *languages;
type = [_parseBodyString(self, YES) lowercaseString];
_consumeIfMatch(self, ' ');
- subtype = _parseBodyString(self, YES);
+ subtype = [_parseBodyString(self, YES) lowercaseString];
_consumeIfMatch(self, ' ');
parameterList = _parseBodyParameterList(self);
_consumeIfMatch(self, ' ');
@@ -1762,7 +1788,8 @@
_consumeIfMatch(self, ' ');
[dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
}
- else if ([type isEqualToString:@"message"]) {
+ else if ([type isEqualToString:@"message"]
+ && [subtype isEqualToString:@"rfc822"]) {
if (_la(self, 0) != ')') {
_consumeIfMatch(self, ' ');
_consumeIfMatch(self, '(');
@@ -1805,14 +1832,9 @@
forKey: @"disposition"];
if (_la(self, 0) != ')') {
_consume(self,1);
- if (_la(self, 0) == '(') {
- [dict setObject: _parseBodyParameterList(self)
- forKey: @"language"];
- }
- else {
- [dict setObject: _parseBodyString(self, YES)
- forKey: @"language"];
- }
+ languages = _parseLanguages(self);
+ if ([languages count])
+ [dict setObject: languages forKey: @"languages"];
if (_la(self, 0) != ')') {
_consume(self,1);
[dict setObject: _parseBodyString(self, YES)
@@ -1829,6 +1851,7 @@
static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self,
BOOL isBodyStructure) {
NSMutableArray *parts;
+ NSArray *languages;
NSString *kind;
NSMutableDictionary *dict;
@@ -1854,14 +1877,9 @@
forKey: @"disposition"];
if (_la(self, 0) != ')') {
_consume(self,1);
- if (_la(self, 0) == '(') {
- [dict setObject: _parseBodyParameterList(self)
- forKey: @"language"];
- }
- else {
- [dict setObject: _parseBodyString(self, YES)
- forKey: @"language"];
- }
+ languages = _parseLanguages(self);
+ if ([languages count])
+ [dict setObject: languages forKey: @"languages"];
if (_la(self, 0) != ')') {
_consume(self,1);
[dict setObject: _parseBodyString(self, YES)
Index: sope-mime/NGMime/NGMimeBodyPart.m
===================================================================
--- sope-mime/NGMime/NGMimeBodyPart.m (revision 1544)
+++ sope-mime/NGMime/NGMimeBodyPart.m (working copy)
@@ -31,18 +31,6 @@
return 2;
}
-static NGMimeType *defaultType = nil;
-
-+ (void)initialize {
- static BOOL isInitialized = NO;
- if (!isInitialized) {
- isInitialized = YES;
-
- defaultType =
- [[NGMimeType mimeType:@"text/plain; charset=us-ascii"] retain];
- }
-}
-
+ (id)bodyPartWithHeader:(NGHashMap *)_header {
return [[[self alloc] initWithHeader:_header] autorelease];
}
@@ -156,13 +144,12 @@
if (!Fields)
Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames];
-
type = [self->header objectForKey:Fields->contentType];
if (![type isKindOfClass:[NGMimeType class]])
type = [NGMimeType mimeType:[type stringValue]];
- return (type != nil ? type : (id)defaultType);
+ return type;
}
- (NSString *)contentId {
Index: sope-mime/NGMime/NGMimeBodyParser.m
===================================================================
--- sope-mime/NGMime/NGMimeBodyParser.m (revision 1544)
+++ sope-mime/NGMime/NGMimeBodyParser.m (working copy)
@@ -67,7 +67,10 @@
if (_data == nil) return nil;
ctype = [_part contentType];
-
+ if (!ctype
+ && [_d respondsToSelector: @selector(parser:contentTypeOfPart:)])
+ ctype = [_d parser: self contentTypeOfPart: _part];
+
if (![ctype isKindOfClass:[NGMimeType class]])
ctype = [NGMimeType mimeType:[ctype stringValue]];
Index: sope-mime/NGMime/NGMimePartParser.h
===================================================================
--- sope-mime/NGMime/NGMimePartParser.h (revision 1544)
+++ sope-mime/NGMime/NGMimePartParser.h (working copy)
@@ -117,6 +117,7 @@
BOOL parserParseRawBodyDataOfPart:1;
BOOL parserBodyParserForPart:1;
BOOL parserDecodeBodyOfPart:1;
+ BOOL parserContentTypeOfPart:1;
} delegateRespondsTo;
@@ -275,6 +276,9 @@
- (id<NGMimeBodyParser>)parser:(NGMimePartParser *)_parser
bodyParserForPart:(id<NGMimePart>)_part;
+- (NGMimeType *)parser:(id)_parser
+ contentTypeOfPart:(id<NGMimePart>)_part;
+
@end /* NSObject(NGMimePartParserDelegate) */
@interface NSObject(NGMimePartParser)
Index: sope-mime/NGMime/NGMimePartParser.m
===================================================================
--- sope-mime/NGMime/NGMimePartParser.m (revision 1544)
+++ sope-mime/NGMime/NGMimePartParser.m (working copy)
@@ -1091,7 +1091,10 @@
id<NGMimeBodyParser> bodyParser = nil;
ctype = [_p contentType];
-
+ if (!ctype
+ && self->delegateRespondsTo.parserContentTypeOfPart)
+ ctype = [self->delegate parser: self contentTypeOfPart: _p];
+
contentType = ([ctype isKindOfClass:[NGMimeType class]])
? ctype
: [NGMimeType mimeType:[ctype stringValue]];
Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.h
===================================================================
--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (revision 1544)
+++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.h (working copy)
@@ -28,6 +28,7 @@
#define ___PostgreSQL72_Channel_H___
#include <GDLAccess/EOAdaptorChannel.h>
+#include <GDLContentStore/EOAdaptorChannel+GCS.h>
#include <libpq-fe.h>
@class NSArray, NSString, NSMutableDictionary;
@@ -40,7 +41,7 @@
int modification;
} PostgreSQL72FieldInfo;
-@interface PostgreSQL72Channel : EOAdaptorChannel
+@interface PostgreSQL72Channel : EOAdaptorChannel <GCSEOAdaptorChannel>
{
// connection is valid after an openChannel call
PGConnection *connection;
Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m
===================================================================
--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1544)
+++ sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (working copy)
@@ -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-appserver/NGObjWeb/GNUmakefile.postamble
===================================================================
--- sope-appserver/NGObjWeb/GNUmakefile.postamble (revision 1544)
+++ sope-appserver/NGObjWeb/GNUmakefile.postamble (working copy)
@@ -23,14 +23,20 @@
# install makefiles
-after-install ::
+after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make
+
+ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0)
+after-install :: $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make
+endif
+
+$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make: ngobjweb.make
$(MKDIRS) $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/
$(INSTALL_DATA) ngobjweb.make $(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/Additional/ngobjweb.make
-ifneq ($(GNUSTEP_MAKE_VERSION),1.3.0)
-after-install ::
+$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make: woapp-gs.make
$(INSTALL_DATA) woapp-gs.make \
$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/woapp.make
+
+$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make: wobundle-gs.make
$(INSTALL_DATA) wobundle-gs.make \
$(INSTALL_ROOT_DIR)/$(GNUSTEP_MAKEFILES)/wobundle.make
-endif
Index: sope-appserver/NGObjWeb/WOContext.m
===================================================================
--- sope-appserver/NGObjWeb/WOContext.m (revision 1544)
+++ sope-appserver/NGObjWeb/WOContext.m (working copy)
@@ -64,11 +64,13 @@
static BOOL testNSURLs = NO;
static BOOL newCURLStyle = NO;
static NSString *WOApplicationSuffix = nil;
+static NSURL *redirectURL = nil;
+ (void)initialize {
static BOOL didInit = NO;
NSUserDefaults *ud;
NSString *cn;
+ NSString *url;
if (didInit) return;
@@ -91,6 +93,9 @@
debugCursor = [ud boolForKey:@"WODebugCursor"] ? 1 : 0;
debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
WOApplicationSuffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
+ url = [ud stringForKey:@"WOApplicationRedirectURL"];
+ if (url != nil)
+ redirectURL = [NSURL URLWithString: url];
}
+ (id)contextWithRequest:(WORequest *)_r {
@@ -503,6 +508,11 @@
return nil;
}
+ if (redirectURL) {
+ // Use URL from user defaults (WOApplicationRedirectURL)
+ return redirectURL;
+ }
+
if ((serverURL = [rq headerForKey:@"x-webobjects-server-url"]) == nil) {
if ((host = [rq headerForKey:@"host"]))
serverURL = [@"http://" stringByAppendingString:host];
Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m
===================================================================
--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (revision 1544)
+++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (working copy)
@@ -216,6 +216,12 @@
assocCount++;
}
}
+ if (count > 0) {
+ if ((self->isAbsolute = OWGetProperty(_config, @"absolute"))) {
+ count--;
+ assocCount++;
+ }
+ }
self->rest = _config;
Index: sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m
===================================================================
--- sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (revision 1544)
+++ sope-appserver/NGObjWeb/DynamicElements/_WOComplexHyperlink.m (working copy)
@@ -40,6 +40,7 @@
WOAssociation *string;
WOAssociation *target;
WOAssociation *disabled;
+ WOAssociation *isAbsolute;
WOElement *template;
/* new in WO4: */
@@ -359,6 +360,7 @@
{
if ((self = [super initWithName:_name hyperlinkInfo:_info template:_t])) {
self->href = _info->href;
+ self->isAbsolute = _info->isAbsolute;
}
return self;
}
@@ -374,6 +376,9 @@
// TODO: we need a binding to disable rewriting!
NSRange r;
+ if ([[self->isAbsolute valueInContext:_ctx] boolValue] == YES)
+ return NO;
+
r = [_s rangeOfString:@":"];
if (r.length == 0)
return YES;
Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h
===================================================================
--- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (revision 1544)
+++ sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.h (working copy)
@@ -41,7 +41,8 @@
WOAssociation *pageName;
WOAssociation *actionClass;
WOAssociation *directActionName;
-
+ WOAssociation *isAbsolute;
+
BOOL sidInUrl;
/* 'ivar' associations */
Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m
===================================================================
--- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (revision 1544)
+++ sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (working copy)
@@ -31,6 +31,7 @@
#include <NGObjWeb/WOCookie.h>
#include <NGExtensions/NSData+gzip.h>
#include <NGHttp/NGHttp.h>
+#include <NGMime/NGMimeType.h>
#include "common.h"
#include <string.h>
@@ -1016,6 +1017,12 @@
- (void)parser:(NGMimePartParser *)_parser didParseHeader:(NGHashMap *)_header {
}
+- (NGMimeType *)parser:(id)_parser
+ contentTypeOfPart:(id<NGMimePart>)_part
+{
+ return [NGMimeType mimeType: @"text/plain; charset=utf-8"];
+}
+
@end /* WOHttpAdaptor */
@implementation WOCoreApplication(SimpleParserSelection)

View File

@@ -1,4 +1,10 @@
#!/bin/bash
# chkconfig: - 85 15
# description: SOGo is a groupware server
# processname: sogod-0.9
# config: /etc/sysconfig/sogo
# config: /etc/httpd/conf.d/SOGo.conf
# pidfile: /var/run/sogo/sogod.pid
# SOGo init script for RedHat
#
@@ -22,18 +28,17 @@
# Boston, MA 02111-1307, USA.
# sogod Scalable OpenGroupware.org (Inverse edition)
#
# chkconfig: - 85 15
# description: SOGo is a groupware server
# processname: sogod-0.9
# config: /etc/sysconfig/sogo
# config: /etc/httpd/conf.d/SOGo.conf
# pidfile: /var/run/sogo/sogod.pid
PATH=/sbin:/bin:/usr/sbin:/usr/bin
. /etc/rc.d/init.d/functions
if [ -z "$GNUSTEP_SYSTEM_ROOT" ]
then
. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
fi
REAL_DAEMON=$GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9
DAEMON=/usr/sbin/sogod
NAME=sogo
DESC="Scalable OpenGroupware.Org (Inverse edition)"
@@ -52,26 +57,24 @@ test -x $DAEMON || exit 0
case "$1" in
start)
echo -n $"Starting $DESC: " /
echo -n $"Starting $DESC: "
daemon --user sogo --pidfile $PIDFILE $DAEMON
echo "$NAME."
;;
stop)
echo -n $"Stopping $DESC: "
killproc --pidfile $PIDFILE $DAEMON
rm -f $PIDFILE
killproc -p $PIDFILE $REAL_DAEMON && rm -f $PIDFILE
echo "$NAME."
;;
restart|force-reload)
echo -n $"Restarting $DESC: "
killproc --pidfile $PIDFILE $DAEMON
rm -f $PIDFILE
killproc -p $PIDFILE $REAL_DAEMON && rm -f $PIDFILE
sleep 1
daemon --user sogo --pidfile $PIDFILE $DAEMON
echo "$NAME."
;;
status)
status $DAEMON
status $REAL_DAEMON
;;
*)
N=/etc/init.d/$NAME

87
Scripts/sogo-init.d-rhel4 Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# chkconfig: - 85 15
# description: SOGo is a groupware server
# processname: sogod-0.9
# config: /etc/sysconfig/sogo
# config: /etc/httpd/conf.d/SOGo.conf
# pidfile: /var/run/sogo/sogod.pid
# SOGo init script for RedHat
#
# Copyright (C) 2007 Inverse groupe conseil
#
# 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.
# sogod Scalable OpenGroupware.org (Inverse edition)
PATH=/sbin:/bin:/usr/sbin:/usr/bin
. /etc/rc.d/init.d/functions
if [ -z "$GNUSTEP_SYSTEM_ROOT" ]
then
. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
fi
REAL_DAEMON=$GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9
DAEMON=/usr/sbin/sogod
NAME=sogo
DESC="Scalable OpenGroupware.Org (Inverse edition)"
PIDFILE=/var/run/sogo/sogod.pid
SOGO_ARGS=""
if [ -f /etc/sysconfig/sogo ]; then
. /etc/sysconfig/sogo
fi
test -x $DAEMON || exit 0
#set -e
case "$1" in
start)
echo -n $"Starting $DESC: "
daemon su - sogo -c $DAEMON
echo "$NAME."
;;
stop)
echo -n $"Stopping $DESC: "
killproc `basename $REAL_DAEMON` && rm -f $PIDFILE
echo "$NAME."
;;
restart|force-reload)
echo -n $"Restarting $DESC: "
killproc `basename $REAL_DAEMON` && rm -f $PIDFILE
sleep 1
daemon su - sogo -c $DAEMON
echo "$NAME."
;;
status)
status $REAL_DAEMON
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
exit 0

View File

@@ -1,8 +1,19 @@
#!/bin/sh
PIDFILE=/var/run/sogo/sogod.pid
PROGRAM=sogod-0.9
oldpid=`pgrep -u $USER sogod-0.9`
if [ -n "$oldpid" ]
then
echo SOGo already launched.
exit 1
fi
. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
echo $$ > $PIDFILE
exec $GNUSTEP_SYSTEM_ROOT/Tools/sogod-0.9 >& /var/log/sogo/sogod.log
$GNUSTEP_SYSTEM_ROOT/Tools/$PROGRAM >& /var/log/sogo/sogod.log &
newpid=`pgrep -u $USER $PROGRAM | awk '{ print $1 }'`
echo $newpid > $PIDFILE

70
Scripts/sql-update-20070822.sh Executable file
View File

@@ -0,0 +1,70 @@
#!/bin/bash
# this script only work with PostgreSQL
defaultusername=$USER
defaulthostname=localhost
defaultdatabase=$USER
indextable=sogo_folder_info
read -p "Username ($defaultusername): " username
read -p "Hostname ($defaulthostname): " hostname
read -p "Database ($defaultdatabase): " database
if [ -z "$username" ]
then
username=$defaultusername
fi
if [ -z "$hostname" ]
then
hostname=$defaulthostname
fi
if [ -z "$database" ]
then
database=$defaultdatabase
fi
echo ""
echo "You will now be requested your password twice..."
echo "After that, a list of SQL operations will scroll."
echo ""
sqlscript=""
function addField() {
oldIFS="$IFS"
IFS=" "
part="`echo -e \"ALTER TABLE $table ADD COLUMN c_deleted INTEGER;\\n\"`";
sqlscript="$sqlscript$part"
IFS="$oldIFS"
}
tables=`psql -t -U $username -h $hostname $database -c "select split_part(c_location, '/', 5) from $indextable where c_folder_type != 'Container';"`
for table in $tables;
do
addField
done
sqlscript="$sqlscript;update $indextable set c_path4 = 'personal', c_path = '/Users/' || c_path2 || '/Calendar/personal' where c_path3 = 'Calendar' and c_path4 is null;"
function updateCalendarLocation() {
oldIFS="$IFS"
IFS=" "
user="`echo $table | cut -f 1 -d :`"
tablename="`echo $table | cut -f 2 -d :`"
newstart="/$user/Calendar/personal";
part="update $tablename set c_object = replace(c_object, '/$user/Calendar', '$newstart') where c_object not like '$newstart%';";
sqlscript="$sqlscript$part"
IFS="$oldIFS"
}
tables=`psql -t -U $username -h $hostname $database -c "select c_path2 || ':' || split_part(c_acl_location, '/', 5) from $indextable where c_folder_type = 'Appointment';"`
for table in $tables;
do
updateCalendarLocation
done
echo "$sqlscript" | psql -q -e -U $username -h $hostname $database > /dev/null
echo "Please ignore the errors above. They just mean that the migration was already done for the elements in question.";

View File

@@ -0,0 +1 @@
"Personal Calendar" = "Personal Calendar";

View File

@@ -0,0 +1 @@
"Personal Calendar" = "Agenda personnel";

View File

@@ -6,7 +6,9 @@ WOBUNDLE_NAME = Appointments
Appointments_PRINCIPAL_CLASS = SOGoAppointmentsProduct
# Appointments_LANGUAGES = English French
Appointments_LANGUAGES = English French German
Appointments_LOCALIZED_RESOURCE_FILES=Localizable.strings
Appointments_OBJC_FILES = \
Product.m \
@@ -17,6 +19,7 @@ Appointments_OBJC_FILES = \
SOGoAppointmentObject.m \
SOGoTaskObject.m \
SOGoAppointmentFolder.m \
SOGoAppointmentFolders.m \
SOGoGroupAppointmentFolder.m \
SOGoFreeBusyObject.m \
\
@@ -32,13 +35,20 @@ Appointments_RESOURCE_FILES += \
Appointments_COMPONENTS += \
SOGoAptMailEnglishInvitation.wo \
SOGoAptMailFrenchInvitation.wo \
SOGoAptMailEnglishUpdate.wo \
SOGoAptMailFrenchUpdate.wo \
SOGoAptMailEnglishRemoval.wo \
SOGoAptMailFrenchRemoval.wo \
SOGoAptMailEnglishDeletion.wo \
SOGoAptMailFrenchInvitation.wo \
SOGoAptMailFrenchUpdate.wo \
SOGoAptMailFrenchRemoval.wo \
SOGoAptMailFrenchDeletion.wo \
SOGoAptMailGermanInvitation.wo \
SOGoAptMailGermanUpdate.wo \
SOGoAptMailGermanRemoval.wo \
SOGoAptMailGermanDeletion.wo \
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/
ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/wobundle.make

View File

@@ -0,0 +1 @@
"Personal Calendar" = "Personal Calendar";

View File

@@ -53,6 +53,8 @@
NSMutableDictionary *uidToFilename;
}
- (BOOL) isActive;
/* selection */
- (NSArray *) calendarUIDs;
@@ -82,8 +84,6 @@
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate;
- (void) deleteEntriesWithIds: (NSArray *) ids;
/* URL generation */
- (NSString *) baseURLForAptWithUID: (NSString *) _uid
@@ -113,8 +113,6 @@
- (NSArray *) fetchAllSOGoAppointments;
- (NSArray *) calendarFolders;
- (NSString *) roleForComponentsWithAccessClass: (iCalAccessClass) accessClass
forUser: (NSString *) uid;

View File

@@ -125,11 +125,6 @@ static NSNumber *sharedYes = nil;
return logger;
}
- (BOOL) folderIsMandatory
{
return YES;
}
/* selection */
- (NSArray *) calendarUIDs
@@ -226,6 +221,7 @@ static NSNumber *sharedYes = nil;
return filterData;
}
#warning filters is leaked here
- (NSArray *) _parseCalendarFilters: (id <DOMElement>) parentNode
{
NSEnumerator *children;
@@ -263,6 +259,7 @@ static NSNumber *sharedYes = nil;
max = [filters count];
for (count = 0; count < max; count++)
{
#warning huh? why not objectAtIndex: count?
currentFilter = [filters objectAtIndex: 0];
apts = [self fetchCoreInfosFrom: [currentFilter objectForKey: @"start"]
to: [currentFilter objectForKey: @"end"]
@@ -573,13 +570,16 @@ static NSNumber *sharedYes = nil;
md = [[_record mutableCopy] autorelease];
/* cycle is in _r */
/* cycle is in _r. We also have to override the c_startdate/c_enddate with the date values of
the reccurence since we use those when displaying events in SOGo Web */
tmp = [_r startDate];
[tmp setTimeZone: timeZone];
[md setObject:tmp forKey:@"startDate"];
[md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_startdate"];
tmp = [_r endDate];
[tmp setTimeZone: timeZone];
[md setObject:tmp forKey:@"endDate"];
[md setObject: [NSNumber numberWithInt: [tmp timeIntervalSince1970]] forKey: @"c_enddate"];
return md;
}
@@ -638,20 +638,24 @@ static NSNumber *sharedYes = nil;
rules = [cycleinfo objectForKey:@"rules"];
exRules = [cycleinfo objectForKey:@"exRules"];
exDates = [cycleinfo objectForKey:@"exDates"];
ranges = [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange:_r
firstInstanceCalendarDateRange:fir
recurrenceRules:rules
exceptionRules:exRules
exceptionDates:exDates];
count = [ranges count];
for (i = 0; i < count; i++) {
NGCalendarDateRange *rRange;
id fixedRow;
rRange = [ranges objectAtIndex:i];
fixedRow = [self fixupCycleRecord:row cycleRange:rRange];
if (fixedRow != nil) [_ma addObject:fixedRow];
if (fixedRow != nil)
{
[_ma addObject:fixedRow];
}
}
}
@@ -746,7 +750,7 @@ static NSNumber *sharedYes = nil;
privacySqlString = @"and (c_isopaque = 1)";
else
{
#warning we do not manage all the user's possible emails
#warning we do not manage all the possible user emails
email = [[activeUser primaryIdentity] objectForKey: @"email"];
privacySqlString
@@ -850,21 +854,20 @@ static NSNumber *sharedYes = nil;
ma = [NSMutableArray arrayWithArray: records];
}
/* fetch recurrent apts now */
sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@%@",
dateSqlString, componentSqlString, privacySqlString];
/* fetch recurrent apts now. we do NOT consider the date range when doing that
as the c_startdate/c_enddate of a recurring event is always set to the first
recurrence - others are generated on the fly */
sql = [NSString stringWithFormat: @"(c_iscycle = 1)%@%@", componentSqlString, privacySqlString];
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
[fields addObject: @"c_cycleinfo"];
records = [_folder fetchFields: fields matchingQualifier: qualifier];
if (records)
{
if (logger)
[self debugWithFormat: @"fetched %i cyclic records: %@",
[records count], records];
if (r)
if (r) {
records = [self fixupCyclicRecords: records fetchRange: r];
}
if (!ma)
ma = [NSMutableArray arrayWithCapacity: [records count]];
@@ -905,7 +908,6 @@ static NSNumber *sharedYes = nil;
component: _component];
}
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
{
@@ -932,33 +934,13 @@ static NSNumber *sharedYes = nil;
@"c_status", @"c_classification",
@"c_isallday", @"c_isopaque",
@"c_participants", @"c_partmails",
@"c_partstates", @"c_sequence", @"c_priority",
@"c_partstates", @"c_sequence", @"c_priority", @"c_cycleinfo",
nil];
return [self fetchFields: infos from: _startDate to: _endDate
component: _component];
}
- (void) deleteEntriesWithIds: (NSArray *) ids
{
Class objectClass;
unsigned int count, max;
NSString *currentId;
id deleteObject;
max = [ids count];
for (count = 0; count < max; count++)
{
currentId = [ids objectAtIndex: count];
objectClass
= [self objectClassForResourceNamed: currentId];
deleteObject = [objectClass objectWithName: currentId
inContainer: self];
[deleteObject delete];
[deleteObject primaryDelete];
}
}
/* URL generation */
- (NSString *) baseURLForAptWithUID: (NSString *)_uid
@@ -1029,7 +1011,7 @@ static NSNumber *sharedYes = nil;
calendarFolder = [SOGoAppointmentFolder objectWithName: @"Calendar"
inContainer: userFolder];
[calendarFolder
setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar", uid]];
setOCSPath: [NSString stringWithFormat: @"/Users/%@/Calendar/personal", uid]];
[calendarFolder setOwner: uid];
return calendarFolder;
@@ -1041,21 +1023,28 @@ static NSNumber *sharedYes = nil;
/* Note: can return NSNull objects in the array! */
NSMutableArray *folders;
NSEnumerator *e;
NSString *uid;
NSString *uid, *ownerLogin;
id folder;
ownerLogin = [self ownerInContext: context];
if ([_uids count] == 0) return nil;
folders = [NSMutableArray arrayWithCapacity:16];
e = [_uids objectEnumerator];
while ((uid = [e nextObject])) {
id folder;
while ((uid = [e nextObject]))
{
if ([uid isEqualToString: ownerLogin])
folder = self;
else
{
folder = [self lookupCalendarFolderForUID: uid];
if (![folder isNotNull])
[self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
}
folder = [self lookupCalendarFolderForUID: uid];
if (![folder isNotNull])
[self logWithFormat:@"Note: did not find folder for uid: '%@'", uid];
/* Note: intentionally add 'null' folders to allow a mapping */
[folders addObject:folder ? folder : [NSNull null]];
}
[folders addObject: folder];
}
return folders;
}
@@ -1196,67 +1185,67 @@ static NSNumber *sharedYes = nil;
return events;
}
#warning We only support ONE calendar per user at this time
- (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
toFolderList: (NSMutableArray *) calendarFolders
{
NSEnumerator *keys;
NSString *currentKey;
NSMutableDictionary *currentCalendar;
BOOL firstShouldBeActive;
unsigned int count;
// #warning We only support ONE calendar per user at this time
// - (BOOL) _appendSubscribedFolders: (NSDictionary *) subscribedFolders
// toFolderList: (NSMutableArray *) calendarFolders
// {
// NSEnumerator *keys;
// NSString *currentKey;
// NSMutableDictionary *currentCalendar;
// BOOL firstShouldBeActive;
// unsigned int count;
firstShouldBeActive = YES;
// firstShouldBeActive = YES;
keys = [[subscribedFolders allKeys] objectEnumerator];
currentKey = [keys nextObject];
count = 1;
while (currentKey)
{
currentCalendar = [NSMutableDictionary new];
[currentCalendar autorelease];
[currentCalendar
setDictionary: [subscribedFolders objectForKey: currentKey]];
[currentCalendar setObject: currentKey forKey: @"folder"];
[calendarFolders addObject: currentCalendar];
if ([[currentCalendar objectForKey: @"active"] boolValue])
firstShouldBeActive = NO;
count++;
currentKey = [keys nextObject];
}
// keys = [[subscribedFolders allKeys] objectEnumerator];
// currentKey = [keys nextObject];
// count = 1;
// while (currentKey)
// {
// currentCalendar = [NSMutableDictionary new];
// [currentCalendar autorelease];
// [currentCalendar
// setDictionary: [subscribedFolders objectForKey: currentKey]];
// [currentCalendar setObject: currentKey forKey: @"folder"];
// [calendarFolders addObject: currentCalendar];
// if ([[currentCalendar objectForKey: @"active"] boolValue])
// firstShouldBeActive = NO;
// count++;
// currentKey = [keys nextObject];
// }
return firstShouldBeActive;
}
// return firstShouldBeActive;
// }
- (NSArray *) calendarFolders
{
NSMutableDictionary *userCalendar, *calendarDict;
NSMutableArray *calendarFolders;
SOGoUser *calendarUser;
BOOL firstActive;
// - (NSArray *) calendarFolders
// {
// NSMutableDictionary *userCalendar, *calendarDict;
// NSMutableArray *calendarFolders;
// SOGoUser *calendarUser;
// BOOL firstActive;
calendarFolders = [NSMutableArray new];
[calendarFolders autorelease];
// calendarFolders = [NSMutableArray new];
// [calendarFolders autorelease];
calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
roles: nil];
userCalendar = [NSMutableDictionary new];
[userCalendar autorelease];
[userCalendar setObject: @"/" forKey: @"folder"];
[userCalendar setObject: @"Calendar" forKey: @"displayName"];
[calendarFolders addObject: userCalendar];
// calendarUser = [SOGoUser userWithLogin: [self ownerInContext: context]
// roles: nil];
// userCalendar = [NSMutableDictionary new];
// [userCalendar autorelease];
// [userCalendar setObject: @"/" forKey: @"folder"];
// [userCalendar setObject: @"Calendar" forKey: @"displayName"];
// [calendarFolders addObject: userCalendar];
calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
firstActive = ([self _appendSubscribedFolders:
[calendarDict objectForKey: @"SubscribedFolders"]
toFolderList: calendarFolders]
|| firstActive);
[userCalendar setObject: [NSNumber numberWithBool: firstActive]
forKey: @"active"];
// calendarDict = [[calendarUser userSettings] objectForKey: @"Calendar"];
// firstActive = [[calendarDict objectForKey: @"activateUserFolder"] boolValue];
// firstActive = ([self _appendSubscribedFolders:
// [calendarDict objectForKey: @"SubscribedFolders"]
// toFolderList: calendarFolders]
// || firstActive);
// [userCalendar setObject: [NSNumber numberWithBool: firstActive]
// forKey: @"active"];
return calendarFolders;
}
// return calendarFolders;
// }
// - (NSArray *) fetchContentObjectNames
// {
@@ -1297,4 +1286,16 @@ static NSNumber *sharedYes = nil;
return @"IPF.Appointment";
}
- (BOOL) isActive
{
NSUserDefaults *settings;
NSArray *activeFolders;
settings = [[context activeUser] userSettings];
activeFolders
= [[settings objectForKey: @"Calendar"] objectForKey: @"ActiveFolders"];
return [activeFolders containsObject: nameInContainer];
}
@end /* SOGoAppointmentFolder */

View File

@@ -1,4 +1,4 @@
/* SOGoMailEnglishForward.m - this file is part of SOGo
/* SOGoAppointmentFolders.h - this file is part of SOGo
*
* Copyright (C) 2007 Inverse groupe conseil
*
@@ -20,10 +20,13 @@
* Boston, MA 02111-1307, USA.
*/
#import "SOGoMailForward.h"
#ifndef SOGOAPPOINTMENTFOLDERS_H
#define SOGOAPPOINTMENTFOLDERS_H
#import <SoObjects/SOGo/SOGoParentFolder.h>
@interface SOGoAppointmentFolders : SOGoParentFolder
@interface SOGoMailEnglishForward : SOGoMailForward
@end
@implementation SOGoMailEnglishForward
@end
#endif /* SOGOAPPOINTMENTFOLDERS_H */

View File

@@ -1,4 +1,4 @@
/* SOGoACLEnglishAdditionAdvisory.h - this file is part of SOGo
/* SOGoAppointmentFolders.m - this file is part of SOGo
*
* Copyright (C) 2007 Inverse groupe conseil
*
@@ -20,12 +20,27 @@
* Boston, MA 02111-1307, USA.
*/
#ifndef SOGOACLFRENCHADDITIONADVISORY_H
#define SOGOACLFRENCHADDITIONADVISORY_H
#import <Foundation/NSString.h>
#import "SOGoACLAdvisory.h"
#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentFolders.h"
@implementation SOGoAppointmentFolders
+ (NSString *) gcsFolderType
{
return @"Appointment";
}
+ (Class) subFolderClass
{
return [SOGoAppointmentFolder class];
}
- (NSString *) defaultFolderName
{
return @"Personal calendar";
}
@interface SOGoACLEnglishAdditionAdvisory : SOGoACLAdvisory
@end
#endif /* SOGOACLFRENCHADDITIONADVISORY_H */

View File

@@ -46,13 +46,6 @@
@interface SOGoAppointmentObject : SOGoCalendarComponent
/* folder management */
- (id) lookupHomeFolderForUID: (NSString *) _uid
inContext: (id) _ctx;
- (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
inContext: (id) _ctx;
/* "iCal multifolder saves" */
- (NSException *) saveContentString: (NSString *) _iCal

View File

@@ -19,6 +19,8 @@
02111-1307, USA.
*/
#import <Foundation/NSCalendarDate.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
@@ -32,6 +34,8 @@
#import <SoObjects/SOGo/SOGoPermissions.h>
#import "NSArray+Appointments.h"
#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentObject.h"
@implementation SOGoAppointmentObject
@@ -94,25 +98,17 @@
return uids;
}
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
// TODO: what does this do? lookup the home of the organizer?
return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
}
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
}
/* store in all the other folders */
- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
- (NSException *) saveContentString: (NSString *) _iCal
inUIDs: (NSArray *) _uids
{
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject]) != nil) {
NSException *error;
SOGoAppointmentObject *apt;
@@ -156,8 +152,8 @@
id folder;
NSException *allErrors = nil;
e = [[self lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
e = [[container lookupCalendarFoldersForUIDs:_uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject])) {
NSException *error;
SOGoAppointmentObject *apt;
@@ -179,6 +175,14 @@
}
/* "iCal multifolder saves" */
- (BOOL) _aptIsStillRelevant: (iCalEvent *) appointment
{
NSCalendarDate *now;
now = [NSCalendarDate calendarDate];
return ([[appointment endDate] earlierDate: now] == now);
}
- (NSException *) saveContentString: (NSString *) _iCal
baseSequence: (int) _v
@@ -319,9 +323,11 @@
if (delError != nil) return delError;
/* email notifications */
if ([self sendEMailNotifications])
if ([self sendEMailNotifications]
&& [self _aptIsStillRelevant: newApt])
{
attendees = [NSMutableArray arrayWithArray: [changes insertedAttendees]];
attendees
= [NSMutableArray arrayWithArray: [changes insertedAttendees]];
[attendees removePerson: organizer];
[self sendEMailUsingTemplateNamed: @"Invitation"
forOldObject: nil
@@ -338,19 +344,20 @@
toAttendees: attendees];
}
attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
attendees
= [NSMutableArray arrayWithArray: [changes deletedAttendees]];
[attendees removePerson: organizer];
if ([attendees count])
{
iCalEvent *canceledApt;
iCalEvent *cancelledApt;
canceledApt = [newApt copy];
[(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
cancelledApt = [newApt copy];
[(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
[self sendEMailUsingTemplateNamed: @"Removal"
forOldObject: nil
andNewObject: canceledApt
andNewObject: cancelledApt
toAttendees: attendees];
[canceledApt release];
[cancelledApt release];
}
}
@@ -396,7 +403,7 @@
attendees = [NSMutableArray arrayWithArray:[apt attendees]];
[attendees removePerson:[apt organizer]];
/* flag appointment as being canceled */
/* flag appointment as being cancelled */
[(iCalCalendar *) [apt parent] setMethod: @"cancel"];
[apt increaseSequence];

View File

@@ -22,17 +22,19 @@
#include "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishDeletion : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishDeletion
@end
@interface SOGoAptMailFrenchDeletion : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchDeletion
@end
@interface SOGoAptMailGermanDeletion : SOGoAptMailNotification
@end
@implementation SOGoAptMailGermanDeletion
@end

View File

@@ -1,4 +1,4 @@
<#IsSubject>Appointment <#AptStartDate /> at <#AptStartTime /> has been canceled</#IsSubject>
<#IsSubject>Appointment <#AptStartDate /> at <#AptStartTime /> has been cancelled</#IsSubject>
<#IsBody>
The appointment at <#AptStartDate /> <#AptStartTime /> has been cancelled by <#Organizer />.
</#IsBody>

View File

@@ -1,4 +1,4 @@
<#IsSubject>Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé</#IsSubject>
<#IsSubject>Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé</#IsSubject>
<#IsBody>
Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé par <#Organizer />.
Le rendez-vous du <#AptStartDate /> à <#AptStartTime /> a été supprimé par <#Organizer />.
</#IsBody>

View File

@@ -1,7 +1,7 @@
<#IsSubject>Rendez-vous le <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsSubject>Rendez-vous le <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsBody>
Vous êtes invité par <#Organizer /> à une réunion.
Vous êtes invité par <#Organizer /> à une réunion.
<#HasHomePageURL>
Veuillez donner votre réponse à l'adresse <#HomePageURL />.
Veuillez donner votre réponse à l'adresse <#HomePageURL />.
</#HasHomePageURL>
</#IsBody>

View File

@@ -1,4 +1,4 @@
<#IsSubject>Supprimé de la réunion du <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsSubject>Supprimé de la réunion du <#AptStartDate /> à <#AptStartTime /></#IsSubject>
<#IsBody>
Vous ne faites plus parti de la liste des invités de la réunion du <#AptStartDate /> à <#AptStartTime /> organisée par <#Organizer />.
Vous ne faites plus parti de la liste des invités de la réunion du <#AptStartDate /> à <#AptStartTime /> organisée par <#Organizer />.
</#IsBody>

View File

@@ -1,5 +1,5 @@
<#IsSubject>Rendez-vous du <#OldAptStartDate /> à <#OldAptStartTime /> modifié</#IsSubject>
<#IsSubject>Rendez-vous du <#OldAptStartDate /> à <#OldAptStartTime /> modifié</#IsSubject>
<#IsBody>
La réunion qui devait se dérouler le <#OldAptStartDate /> à <#OldAptStartTime /> est maintenant prévue le <#NewAptStartDate /> à <#NewAptStartTime />.
Vous êtes invité à accepter ou refuser de participer à la réunion pour cette nouvelle date à l'adresse <#HomePageURL />.
La réunion qui devait se dérouler le <#OldAptStartDate /> à <#OldAptStartTime /> est maintenant prévue le <#NewAptStartDate /> à <#NewAptStartTime />.
Vous êtes invité à accepter ou refuser de participer à la réunion pour cette nouvelle date à l'adresse <#HomePageURL />.
</#IsBody>

View File

@@ -1,4 +1,4 @@
<#IsSubject>Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde gelöscht.</#IsSubject>
<#IsSubject>Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde gelöscht.</#IsSubject>
<#IsBody>
Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde von <#Organizer /> gelöscht.
Der Termin am <#AptStartDate /> um <#AptStartTime /> wurde von <#Organizer /> gelöscht.
</#IsBody>

View File

@@ -1,7 +1,7 @@
<#IsSubject>Termin am <#AptStartDate /> um <#AptStartTime /></#IsSubject>
<#IsBody>
<#Organizer /> lädt Sie zu einem Termin am <#AptStartDate /> um <#AptStartTime /> ein.
<#Organizer /> lädt Sie zu einem Termin am <#AptStartDate /> um <#AptStartTime /> ein.
<#HasHomePageURL>
Bitte benutzen Sie folgende URL, um anzugeben, ob Sie an dem Termin teilnehmen können: <#HomePageURL />.
Bitte benutzen Sie folgende URL, um anzugeben, ob Sie an dem Termin teilnehmen können: <#HomePageURL />.
</#HasHomePageURL>
</#IsBody>

View File

@@ -1,4 +1,4 @@
<#IsSubject>Von dem Termin am <#AptStartDate /> um <#AptStartTime /> gelöscht</#IsSubject>
<#IsSubject>Von dem Termin am <#AptStartDate /> um <#AptStartTime /> gelöscht</#IsSubject>
<#IsBody>
Sie sind nicht mehr eingeladen zu dem Termin am <#AptStartDate /> um <#AptStartTime /> organisiert von <#Organizer />.
</#IsBody>

View File

@@ -1,5 +1,5 @@
<#IsSubject>Veränderung des Termins am <#OldAptStartDate /> um <#OldAptStartTime /></#IsSubject>
<#IsSubject>Veränderung des Termins am <#OldAptStartDate /> um <#OldAptStartTime /></#IsSubject>
<#IsBody>
Der Termin, der ursprünglich am <#OldAptStartDate /> um <#OldAptStartTime /> stattfinden sollte, ist jetzt für den <#NewAptStartDate /> um <#NewAptStartTime /> geplant.
Bitte geben Sie an folgender URL an, ob Sie an diesem Termin zum neuen Datum teilnehmen können: <#HomePageURL />.
Der Termin, der ursprünglich am <#OldAptStartDate /> um <#OldAptStartTime /> stattfinden sollte, ist jetzt für den <#NewAptStartDate /> um <#NewAptStartTime /> geplant.
Bitte geben Sie an folgender URL an, ob Sie an diesem Termin zum neuen Datum teilnehmen können: <#HomePageURL />.
</#IsBody>

View File

@@ -19,20 +19,22 @@
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
#import "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishInvitation : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishInvitation
@end
@interface SOGoAptMailFrenchInvitation : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchInvitation
@end
@interface SOGoAptMailGermanInvitation : SOGoAptMailNotification
@end
@implementation SOGoAptMailGermanInvitation
@end

View File

@@ -48,21 +48,21 @@
- (id) newApt;
- (void) setNewApt: (iCalEntityObject *) _newApt;
- (NSString *)homePageURL;
- (void)setHomePageURL: (NSString *)_homePageURL;
- (NSString *) homePageURL;
- (void) setHomePageURL: (NSString *) _homePageURL;
- (NSTimeZone *)viewTZ;
- (void)setViewTZ:(NSTimeZone *)_viewTZ;
- (NSTimeZone *) viewTZ;
- (void) setViewTZ: (NSTimeZone *) _viewTZ;
/* Helpers */
- (NSCalendarDate *)oldStartDate;
- (NSCalendarDate *)newStartDate;
- (NSCalendarDate *) oldStartDate;
- (NSCalendarDate *) newStartDate;
/* Content Generation */
- (NSString *)getSubject;
- (NSString *)getBody;
- (NSString *) getSubject;
- (NSString *) getBody;
@end

View File

@@ -28,6 +28,8 @@
#import <NGExtensions/NSObject+Logs.h>
#import <NGCards/iCalEntityObject.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import "SOGoAptMailNotification.h"
@interface SOGoAptMailNotification (PrivateAPI)
@@ -88,7 +90,7 @@ static NSTimeZone *EST = nil;
NSString *aptUID;
aptUID = [[self newApt] uid];
return [NSString stringWithFormat:@"%@/Calendar/%@/edit?mail-invitation=yes",
return [NSString stringWithFormat:@"%@/Calendar/personal/%@/edit?mail-invitation=yes",
[self homePageURL],
aptUID];
}
@@ -141,7 +143,8 @@ static NSTimeZone *EST = nil;
[self name]];
subject = @"ERROR: missing subject!";
}
return subject;
return [subject asQPSubjectString: @"utf-8"];
}
- (NSString *)getBody {

View File

@@ -19,20 +19,22 @@
02111-1307, USA.
*/
#include "SOGoAptMailNotification.h"
#import "SOGoAptMailNotification.h"
@interface SOGoAptMailEnglishRemoval : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailEnglishRemoval
@end
@interface SOGoAptMailFrenchRemoval : SOGoAptMailNotification
{
}
@end
@implementation SOGoAptMailFrenchRemoval
@end
@interface SOGoAptMailGermanRemoval : SOGoAptMailNotification
@end
@implementation SOGoAptMailGermanRemoval
@end

View File

@@ -567,7 +567,7 @@ static BOOL sendEMailNotifications = NO;
}
else
role = SOGoCalendarRole_Organizer;
return role;
}

View File

@@ -35,6 +35,8 @@
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoPermissions.h>
#import "SOGoAppointmentFolder.h"
#import "SOGoFreeBusyObject.h"
@interface SOGoFreeBusyObject (PrivateAPI)
@@ -80,17 +82,18 @@
- (NSArray *) fetchFreeBusyInfosFrom: (NSCalendarDate *) _startDate
to: (NSCalendarDate *) _endDate
{
id calFolder;
SOGoAppointmentFolder *calFolder;
// SoSecurityManager *sm;
NSArray *infos;
calFolder = [container lookupName: @"Calendar" inContext: nil acquire: NO];
calFolder = [[container lookupName: @"Calendar" inContext: nil acquire: NO]
lookupName: @"personal" inContext: nil acquire: NO];
// sm = [SoSecurityManager sharedSecurityManager];
// if (![sm validatePermission: SOGoPerm_FreeBusyLookup
// onObject: calFolder
// inContext: context])
infos = [calFolder fetchFreeBusyInfosFrom: _startDate
to: _endDate];
infos = [calFolder fetchFreeBusyInfosFrom: _startDate
to: _endDate];
// else
// {
// infos = [NSArray new];
@@ -169,9 +172,9 @@
info = [events nextObject];
while (info)
{
if ([[info objectForKey: @"isopaque"] boolValue])
if ([[info objectForKey: @"c_isopaque"] boolValue])
{
type = [self _fbTypeForEventStatus: [info objectForKey: @"status"]];
type = [self _fbTypeForEventStatus: [info objectForKey: @"c_status"]];
[freebusy addFreeBusyFrom: [info objectForKey: @"startDate"]
to: [info objectForKey: @"endDate"]
type: type];

View File

@@ -44,13 +44,6 @@
@interface SOGoTaskObject : SOGoCalendarComponent
/* folder management */
- (id) lookupHomeFolderForUID: (NSString *) _uid
inContext: (id) _ctx;
- (NSArray *) lookupCalendarFoldersForUIDs: (NSArray *) _uids
inContext: (id) _ctx;
/* "iCal multifolder saves" */
- (NSException *) saveContentString: (NSString *) _iCal

View File

@@ -34,6 +34,7 @@
#import "NSArray+Appointments.h"
#import "SOGoAptMailNotification.h"
#import "SOGoAppointmentFolder.h"
#import "SOGoTaskObject.h"
@@ -118,17 +119,6 @@ static NSString *mailTemplateDefaultLanguage = nil;
return uids;
}
/* folder management */
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx {
// TODO: what does this do? lookup the home of the organizer?
return [[self container] lookupHomeFolderForUID:_uid inContext:_ctx];
}
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx {
return [[self container] lookupCalendarFoldersForUIDs:_uids inContext:_ctx];
}
/* store in all the other folders */
- (NSException *)saveContentString:(NSString *)_iCal inUIDs:(NSArray *)_uids {
@@ -136,7 +126,7 @@ static NSString *mailTemplateDefaultLanguage = nil;
id folder;
NSException *allErrors = nil;
e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject]) != nil) {
NSException *error;
@@ -170,38 +160,38 @@ static NSString *mailTemplateDefaultLanguage = nil;
}
return allErrors;
}
- (NSException *)deleteInUIDs:(NSArray *)_uids {
NSEnumerator *e;
id folder;
NSException *allErrors = nil;
// - (NSException *)deleteInUIDs:(NSArray *)_uids {
// NSEnumerator *e;
// id folder;
// NSException *allErrors = nil;
e = [[self lookupCalendarFoldersForUIDs: _uids inContext: context]
objectEnumerator];
while ((folder = [e nextObject])) {
NSException *error;
SOGoTaskObject *task;
// e = [[container lookupCalendarFoldersForUIDs: _uids inContext: context]
// objectEnumerator];
// while ((folder = [e nextObject])) {
// NSException *error;
// SOGoTaskObject *task;
task = [folder lookupName: [self nameInContainer]
inContext: context
acquire: NO];
if (![task isNotNull]) {
[self logWithFormat:@"Note: did not find '%@' in folder: %@",
[self nameInContainer], folder];
continue;
}
if ([task isKindOfClass: [NSException class]]) {
[self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
continue;
}
// task = [folder lookupName: [self nameInContainer]
// inContext: context
// acquire: NO];
// if (![task isNotNull]) {
// [self logWithFormat:@"Note: did not find '%@' in folder: %@",
// [self nameInContainer], folder];
// continue;
// }
// if ([task isKindOfClass: [NSException class]]) {
// [self logWithFormat:@"Exception: %@", [(NSException *) task reason]];
// continue;
// }
if ((error = [task primaryDelete]) != nil) {
[self logWithFormat:@"Note: failed to delete in folder: %@", folder];
// TODO: make compound
allErrors = error;
}
}
return allErrors;
}
// if ((error = [task primaryDelete]) != nil) {
// [self logWithFormat:@"Note: failed to delete in folder: %@", folder];
// // TODO: make compound
// allErrors = error;
// }
// }
// return allErrors;
// }
/* "iCal multifolder saves" */
@@ -376,15 +366,15 @@ static NSString *mailTemplateDefaultLanguage = nil;
// attendees = [NSMutableArray arrayWithArray:[changes deletedAttendees]];
// [attendees removePerson: organizer];
// if ([attendees count]) {
// iCalToDo *canceledApt;
// iCalToDo *cancelledApt;
// canceledApt = [newApt copy];
// [(iCalCalendar *) [canceledApt parent] setMethod: @"cancel"];
// cancelledApt = [newApt copy];
// [(iCalCalendar *) [cancelledApt parent] setMethod: @"cancel"];
// [self sendEMailUsingTemplateNamed: @"Removal"
// forOldObject: nil
// andNewObject: canceledApt
// andNewObject: cancelledApt
// toAttendees: attendees];
// [canceledApt release];
// [cancelledApt release];
// }
// }
@@ -406,45 +396,48 @@ static NSString *mailTemplateDefaultLanguage = nil;
- delete in removed folders
- send iMIP mail for all folders not found
*/
iCalToDo *task;
NSArray *removedUIDs;
NSMutableArray *attendees;
// iCalToDo *task;
// NSArray *removedUIDs;
// NSMutableArray *attendees;
/* load existing content */
[self primaryDelete];
return nil;
// /* load existing content */
task = (iCalToDo *) [self component: NO];
// task = (iCalToDo *) [self component: NO];
/* compare sequence if requested */
// /* compare sequence if requested */
if (_v != 0) {
// TODO
}
// if (_v != 0) {
// // TODO
// }
removedUIDs = [self attendeeUIDsFromTask:task];
// removedUIDs = [self attendeeUIDsFromTask:task];
if ([self sendEMailNotifications])
{
/* send notification email to attendees excluding organizer */
attendees = [NSMutableArray arrayWithArray:[task attendees]];
[attendees removePerson:[task organizer]];
// if ([self sendEMailNotifications])
// {
// /* send notification email to attendees excluding organizer */
// attendees = [NSMutableArray arrayWithArray:[task attendees]];
// [attendees removePerson:[task organizer]];
/* flag task as being canceled */
[(iCalCalendar *) [task parent] setMethod: @"cancel"];
[task increaseSequence];
// /* flag task as being cancelled */
// [(iCalCalendar *) [task parent] setMethod: @"cancel"];
// [task increaseSequence];
/* remove all attendees to signal complete removal */
[task removeAllAttendees];
// /* remove all attendees to signal complete removal */
// [task removeAllAttendees];
/* send notification email */
[self sendEMailUsingTemplateNamed: @"Deletion"
forOldObject: nil
andNewObject: task
toAttendees: attendees];
}
// /* send notification email */
// [self sendEMailUsingTemplateNamed: @"Deletion"
// forOldObject: nil
// andNewObject: task
// toAttendees: attendees];
// }
/* perform */
// /* perform */
return [self deleteInUIDs:removedUIDs];
// return [self deleteInUIDs:removedUIDs];
}
- (NSException *)saveContentString:(NSString *)_iCalString {

View File

@@ -23,6 +23,8 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <NGCards/iCalPerson.h>
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/SOGoUser.h>
@@ -32,44 +34,31 @@
- (BOOL) userIsParticipant: (SOGoUser *) user
{
NSEnumerator *emails;
NSArray *identities;
NSString *currentEmail;
BOOL response;
NSEnumerator *participants;
iCalPerson *currentParticipant;
BOOL isParticipant;
response = NO;
isParticipant = NO;
identities = [user allIdentities];
emails = [[identities objectsForKey: @"email"] objectEnumerator];
currentEmail = [emails nextObject];
while (!response && currentEmail)
if ([self isParticipant: currentEmail])
response = YES;
participants = [[self participants] objectEnumerator];
currentParticipant = [participants nextObject];
while (!isParticipant
&& currentParticipant)
if ([user hasEmail: [currentParticipant rfc822Email]])
isParticipant = YES;
else
currentEmail = [emails nextObject];
currentParticipant = [participants nextObject];
return response;
return isParticipant;
}
- (BOOL) userIsOrganizer: (SOGoUser *) user
{
NSEnumerator *emails;
NSArray *identities;
NSString *currentEmail;
BOOL response;
NSString *orgMail;
response = NO;
orgMail = [[self organizer] rfc822Email];
identities = [user allIdentities];
emails = [[identities objectsForKey: @"email"] objectEnumerator];
currentEmail = [emails nextObject];
while (!response && currentEmail)
if ([self isOrganizer: currentEmail])
response = YES;
else
currentEmail = [emails nextObject];
return response;
return [user hasEmail: orgMail];
}
@end

View File

@@ -8,6 +8,9 @@
};
classes = {
SOGoAppointmentFolder = {
superclass = "SOGoParentFolder";
};
SOGoAppointmentFolder = {
superclass = "SOGoFolder";
defaultRoles = {

View File

@@ -0,0 +1 @@
"Personal Address Book" = "Personal Address Book";

View File

@@ -0,0 +1 @@
"Personal Address Book" = "Carnet d'adresses personnel";

View File

@@ -19,6 +19,9 @@ Contacts_RESOURCE_FILES += \
Version \
product.plist \
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/
ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
-include GNUmakefile.postamble

View File

@@ -0,0 +1 @@
"Personal Calendar" = "Personal Calendar";

View File

@@ -43,16 +43,6 @@
@protocol SOGoContactFolder <NSObject>
+ (id <SOGoContactFolder>) contactFolderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (SOGoObject *) aContainer;
- (id <SOGoContactFolder>) initWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (SOGoObject *) aContainer;
- (NSString *) displayName;
- (NSArray *) lookupContactsWithFilter: (NSString *) filter
sortBy: (NSString *) sortKey
ordering: (NSComparisonResult) sortOrdering;

View File

@@ -1,6 +1,6 @@
/* SOGoContactFolders.h - this file is part of SOGo
*
* Copyright (C) 2006 Inverse groupe conseil
* Copyright (C) 2006, 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@@ -23,25 +23,9 @@
#ifndef SOGOCONTACTFOLDERS_H
#define SOGOCONTACTFOLDERS_H
#import <SOGo/SOGoObject.h>
#import <SoObjects/SOGo/SOGoParentFolder.h>
@class NSMutableDictionary;
@class NSString;
@class WOResponse;
@interface SOGoContactFolders : SOGoObject
{
NSMutableDictionary *contactFolders;
NSString *OCSPath;
}
- (NSString *) defaultSourceName;
- (void) setBaseOCSPath: (NSString *) newOCSPath;
- (NSArray *) contactFolders;
- (WOResponse *) newFolderWithName: (NSString *) name;
@interface SOGoContactFolders : SOGoParentFolder
@end

View File

@@ -1,6 +1,6 @@
/* SOGoContactFolders.m - this file is part of SOGo
*
* Copyright (C) 2006 Inverse groupe conseil
* Copyright (C) 2006, 2007 Inverse groupe conseil
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
@@ -20,7 +20,6 @@
* Boston, MA 02111-1307, USA.
*/
/* exchange folder types: */
/* MailItems IPF.Note
ContactItems IPF.Contact
AppointmentItems IPF.Appointment
@@ -28,23 +27,11 @@
TaskItems IPF.Task
JournalItems IPF.Journal */
#import <Foundation/NSDictionary.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/SoUser.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <GDLContentStore/GCSChannelManager.h>
#import <GDLAccess/EOAdaptorChannel.h>
#import <GDLContentStore/NSURL+GCS.h>
#import <Foundation/NSEnumerator.h>
#import <SoObjects/SOGo/LDAPUserManager.h>
#import <SoObjects/SOGo/SOGoPermissions.h>
#import "SOGoContactGCSFolder.h"
#import "SOGoContactLDAPFolder.h"
@@ -52,89 +39,14 @@
@implementation SOGoContactFolders
- (id) init
+ (NSString *) gcsFolderType
{
if ((self = [super init]))
{
contactFolders = nil;
OCSPath = nil;
}
return self;
return @"Contact";
}
- (void) dealloc
+ (Class) subFolderClass
{
if (contactFolders)
[contactFolders release];
if (OCSPath)
[OCSPath release];
[super dealloc];
}
- (void) _fetchPersonalFolders: (NSString *) sql
withChannel: (EOAdaptorChannel *) fc
{
NSArray *attrs;
NSDictionary *row;
SOGoContactGCSFolder *ab;
BOOL hasPersonal;
NSString *key, *path;
hasPersonal = NO;
[fc evaluateExpressionX: sql];
attrs = [fc describeResults: NO];
row = [fc fetchAttributes: attrs withZone: NULL];
while (row)
{
ab = [SOGoContactGCSFolder
contactFolderWithName: [row objectForKey: @"c_path4"]
andDisplayName: [row objectForKey: @"c_foldername"]
inContainer: self];
key = [row objectForKey: @"c_path4"];
hasPersonal = (hasPersonal || [key isEqualToString: @"personal"]);
[ab setOCSPath: [NSString stringWithFormat: @"%@/%@",
OCSPath, key]];
[contactFolders setObject: ab forKey: key];
row = [fc fetchAttributes: attrs withZone: NULL];
}
if (!hasPersonal)
{
ab = [SOGoContactGCSFolder contactFolderWithName: @"personal"
andDisplayName: @"Contacts"
inContainer: self];
path = [NSString stringWithFormat:
@"/Users/%@/Contacts/personal",
[self ownerInContext: context]];
[ab setOCSPath: path];
[contactFolders setObject: ab forKey: @"personal"];
}
}
- (void) appendPersonalSources
{
GCSChannelManager *cm;
EOAdaptorChannel *fc;
NSURL *folderLocation;
NSString *sql;
cm = [GCSChannelManager defaultChannelManager];
folderLocation
= [[GCSFolderManager defaultFolderManager] folderInfoLocation];
fc = [cm acquireOpenChannelForURL: folderLocation];
if (fc)
{
sql = [NSString
stringWithFormat: (@"SELECT c_path4, c_foldername FROM %@"
@" WHERE c_path2 = '%@'"
@" AND c_folder_type = 'Contact'"),
[folderLocation gcsTableName], [self ownerInContext: context]];
[self _fetchPersonalFolders: sql withChannel: fc];
[cm releaseChannel: fc];
// sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'",
// uidColumnName, [self uid]];
}
return [SOGoContactGCSFolder class];
}
- (void) appendSystemSources
@@ -150,142 +62,18 @@
while (currentSourceID)
{
displayName = [um displayNameForSourceWithID: currentSourceID];
currentFolder = [SOGoContactLDAPFolder contactFolderWithName: currentSourceID
currentFolder = [SOGoContactLDAPFolder folderWithName: currentSourceID
andDisplayName: displayName
inContainer: self];
[currentFolder setLDAPSource: [um sourceWithID: currentSourceID]];
[contactFolders setObject: currentFolder forKey: currentSourceID];
[subFolders setObject: currentFolder forKey: currentSourceID];
currentSourceID = [sourceIDs nextObject];
}
}
- (WOResponse *) newFolderWithName: (NSString *) name
- (NSString *) defaultFolderName
{
SOGoContactGCSFolder *newFolder;
WOResponse *response;
newFolder = [SOGoContactGCSFolder contactFolderWithName: name
andDisplayName: name
inContainer: self];
if ([newFolder isKindOfClass: [NSException class]])
response = (WOResponse *) newFolder;
else
{
[newFolder setOCSPath: [NSString stringWithFormat: @"%@/%@",
OCSPath, name]];
if ([newFolder create])
{
response = [WOResponse new];
[response setStatus: 201];
[response autorelease];
}
else
response = [NSException exceptionWithHTTPStatus: 400
reason: @"The new folder could not be created"];
}
return response;
}
- (void) initContactSources
{
if (!contactFolders)
{
contactFolders = [NSMutableDictionary new];
[self appendPersonalSources];
[self appendSystemSources];
}
}
- (id) lookupName: (NSString *) name
inContext: (WOContext *) lookupContext
acquire: (BOOL) acquire
{
id obj;
/* first check attributes directly bound to the application */
obj = [super lookupName: name inContext: lookupContext acquire: NO];
if (!obj)
{
if (!contactFolders)
[self initContactSources];
obj = [contactFolders objectForKey: name];
if (!obj)
obj = [NSException exceptionWithHTTPStatus: 404];
}
return obj;
}
- (NSArray *) toManyRelationshipKeys
{
if (!contactFolders)
[self initContactSources];
return [contactFolders allKeys];
}
- (NSArray *) contactFolders
{
if (!contactFolders)
[self initContactSources];
return [contactFolders allValues];
}
/* acls */
- (NSArray *) aclsForUser: (NSString *) uid
{
return nil;
}
// - (NSString *) roleOfUser: (NSString *) uid
// {
// NSArray *roles, *traversalPath;
// NSString *objectName, *role;
// role = nil;
// traversalPath = [context objectForKey: @"SoRequestTraversalPath"];
// if ([traversalPath count] > 2)
// {
// objectName = [traversalPath objectAtIndex: 2];
// roles = [[context activeUser]
// rolesForObject: [self lookupName: objectName
// inContext: context
// acquire: NO]
// inContext: context];
// if ([roles containsObject: SOGoRole_Assistant]
// || [roles containsObject: SOGoRole_Delegate])
// role = SOGoRole_Assistant;
// }
// return role;
// }
- (BOOL) davIsCollection
{
return YES;
}
- (NSString *) davContentType
{
return @"httpd/unix-directory";
}
- (void) setBaseOCSPath: (NSString *) newOCSPath
{
if (OCSPath)
[OCSPath release];
OCSPath = newOCSPath;
if (OCSPath)
[OCSPath retain];
}
/* web interface */
- (NSString *) defaultSourceName
{
return @"personal";
return @"Personal Address Book";
}
@end

View File

@@ -30,9 +30,6 @@
@class NSString;
@interface SOGoContactGCSFolder : SOGoFolder <SOGoContactFolder>
{
NSString *displayName;
}
@end

View File

@@ -21,14 +21,20 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOResponse.h>
#import <NGObjWeb/WORequest.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <GDLContentStore/GCSFolder.h>
#import <DOM/DOMProtocols.h>
#import <SaxObjC/SaxObjC.h>
#import <SaxObjC/XMLNamespaces.h>
#import <SoObjects/SOGo/NSDictionary+Utilities.h>
#import "SOGoContactGCSEntry.h"
@@ -41,47 +47,6 @@
@implementation SOGoContactGCSFolder
+ (id <SOGoContactFolder>) contactFolderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (SOGoObject *) aContainer
{
SOGoContactGCSFolder *folder;
folder = [[self alloc] initWithName: aName
andDisplayName: aDisplayName
inContainer: aContainer];
[folder autorelease];
return folder;
}
- (void) dealloc
{
[displayName release];
[super dealloc];
}
- (id <SOGoContactFolder>) initWithName: (NSString *) newName
andDisplayName: (NSString *) newDisplayName
inContainer: (SOGoObject *) newContainer
{
if ((self = [self initWithName: newName
inContainer: newContainer]))
ASSIGN (displayName, newDisplayName);
return self;
}
- (BOOL) folderIsMandatory
{
return [nameInContainer isEqualToString: @"personal"];
}
- (NSString *) displayName
{
return displayName;
}
/* name lookup */
- (id <SOGoContactObject>) lookupContactWithId: (NSString *) recordId
@@ -105,7 +70,6 @@
BOOL isPut;
isPut = NO;
/* first check attributes directly bound to the application */
obj = [super lookupName:_key inContext:_ctx acquire:NO];
if (!obj)
{
@@ -210,6 +174,140 @@
return newRecords;
}
- (BOOL) _isValidFilter: (NSString *) theString
{
if ([theString caseInsensitiveCompare: @"sn"] == NSOrderedSame)
return YES;
if ([theString caseInsensitiveCompare: @"givenname"] == NSOrderedSame)
return YES;
if ([theString caseInsensitiveCompare: @"mail"] == NSOrderedSame)
return YES;
if ([theString caseInsensitiveCompare: @"telephonenumber"] == NSOrderedSame)
return YES;
return NO;
}
- (NSDictionary *) _parseContactFilter: (id <DOMElement>) filterElement
{
NSMutableDictionary *filterData;
id <DOMNode> parentNode;
id <DOMNodeList> ranges;
parentNode = [filterElement parentNode];
if ([[parentNode tagName] isEqualToString: @"filter"] &&
[self _isValidFilter: [filterElement attribute: @"name"]])
{
ranges = [filterElement getElementsByTagName: @"text-match"];
if ([ranges count] && [[[ranges objectAtIndex: 0] childNodes] count])
{
filterData = [NSMutableDictionary new];
[filterData autorelease];
[filterData setObject: [[[[ranges objectAtIndex: 0] childNodes] lastObject] data]
forKey: [filterElement attribute: @"name"]];
}
}
else
filterData = nil;
return filterData;
}
#warning filters is leaked here
- (NSArray *) _parseContactFilters: (id <DOMElement>) parentNode
{
NSEnumerator *children;
id<DOMElement> node;
NSMutableArray *filters;
NSDictionary *filter;
filters = [NSMutableArray new];
children = [[parentNode getElementsByTagName: @"prop-filter"]
objectEnumerator];
node = [children nextObject];
while (node)
{
filter = [self _parseContactFilter: node];
if (filter)
[filters addObject: filter];
node = [children nextObject];
}
return filters;
}
- (void) appendObject: (NSDictionary *) object
withBaseURL: (NSString *) baseURL
toREPORTResponse: (WOResponse *) r
{
SOGoContactGCSEntry *component;
Class componentClass;
NSString *name, *etagLine, *contactString;
name = [object objectForKey: @"c_name"];
componentClass = [SOGoContactGCSEntry class];
component = [componentClass objectWithName: name inContainer: self];
[r appendContentString: @" <D:response>\r\n"];
[r appendContentString: @" <D:href>"];
[r appendContentString: baseURL];
if (![baseURL hasSuffix: @"/"])
[r appendContentString: @"/"];
[r appendContentString: name];
[r appendContentString: @"</D:href>\r\n"];
[r appendContentString: @" <D:propstat>\r\n"];
[r appendContentString: @" <D:prop>\r\n"];
etagLine = [NSString stringWithFormat: @" <D:getetag>%@</D:getetag>\r\n",
[component davEntityTag]];
[r appendContentString: etagLine];
[r appendContentString: @" </D:prop>\r\n"];
[r appendContentString: @" <D:status>HTTP/1.1 200 OK</D:status>\r\n"];
[r appendContentString: @" </D:propstat>\r\n"];
[r appendContentString: @" <C:addressbook-data>"];
contactString = [[component contentAsString] stringByEscapingXMLString];
[r appendContentString: contactString];
[r appendContentString: @"</C:addressbook-data>\r\n"];
[r appendContentString: @" </D:response>\r\n"];
}
- (void) _appendComponentsMatchingFilters: (NSArray *) filters
toResponse: (WOResponse *) response
{
unsigned int count, max;
NSDictionary *currentFilter, *contact;
NSEnumerator *contacts;
NSString *baseURL;
baseURL = [self baseURLInContext: context];
max = [filters count];
for (count = 0; count < max; count++)
{
currentFilter = [filters objectAtIndex: count];
contacts = [[self lookupContactsWithFilter: [[currentFilter allValues] lastObject]
sortBy: @"c_givenname"
ordering: NSOrderedDescending]
objectEnumerator];
while ((contact = [contacts nextObject]))
{
[self appendObject: contact
withBaseURL: baseURL
toREPORTResponse: response];
}
}
}
- (NSArray *) lookupContactsWithFilter: (NSString *) filter
sortBy: (NSString *) sortKey
ordering: (NSComparisonResult) sortOrdering
@@ -218,13 +316,11 @@
EOQualifier *qualifier;
EOSortOrdering *ordering;
// NSLog (@"fetching records matching '%@', sorted by '%@' in order %d",
// filter, sortKey, sortOrdering);
fields = folderListingFields;
qualifier = [self _qualifierForFilter: filter];
dbRecords = [[self ocsFolder] fetchFields: fields
matchingQualifier: qualifier];
if ([dbRecords count] > 0)
{
records = [self _flattenedRecords: dbRecords];
@@ -242,7 +338,7 @@
// else
// [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
//[self debugWithFormat:@"fetched %i records.", [records count]];
[self debugWithFormat:@"fetched %i records.", [records count]];
return records;
}
@@ -251,6 +347,32 @@
return [NSArray arrayWithObject: @"urn:ietf:params:xml:ns:carddav"];
}
- (id) davAddressbookQuery: (id) queryContext
{
WOResponse *r;
NSArray *filters;
id <DOMDocument> document;
r = [context response];
[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 appendContentString:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"];
[r appendContentString: @"<D:multistatus xmlns:D=\"DAV:\""
@" xmlns:C=\"urn:ietf:params:xml:ns:carddav\">\r\n"];
document = [[context request] contentAsDOMDocument];
filters = [self _parseContactFilters: [document documentElement]];
[self _appendComponentsMatchingFilters: filters
toResponse: r];
[r appendContentString:@"</D:multistatus>\r\n"];
return r;
}
- (NSArray *) davComplianceClassesInContext: (id)_ctx
{
NSMutableArray *classes;
@@ -273,14 +395,6 @@
return @"vcard-collection";
}
- (NSException *) delete
{
return (([nameInContainer isEqualToString: @"personal"])
? [NSException exceptionWithHTTPStatus: 403
reason: @"the 'personal' folder cannot be deleted"]
: [super delete]);
}
// /* GET */
// - (id) GETAction: (id)_ctx
@@ -300,6 +414,20 @@
// return r;
// }
/* sorting */
- (NSComparisonResult) compare: (id) otherFolder
{
NSComparisonResult comparison;
if ([NSStringFromClass([otherFolder class])
isEqualToString: @"SOGoContactLDAPFolder"])
comparison = NSOrderedAscending;
else
comparison = [super compare: otherFolder];
return comparison;
}
/* folder type */
- (NSString *) folderType

View File

@@ -37,9 +37,12 @@
BOOL ignoreSoObjectHunger;
}
- (id <SOGoContactFolder>) initWithName: (NSString *) newName
andDisplayName: (NSString *) newDisplayName
inContainer: (SOGoObject *) newContainer;
+ (id) folderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (id) aContainer;
- (id) initWithName: (NSString *) newName
andDisplayName: (NSString *) newDisplayName
inContainer: (id) newContainer;
- (void) setLDAPSource: (LDAPSource *) newLdapSource;
@end

View File

@@ -41,11 +41,11 @@
@implementation SOGoContactLDAPFolder
+ (id <SOGoContactFolder>) contactFolderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (SOGoObject *) aContainer
+ (id) folderWithName: (NSString *) aName
andDisplayName: (NSString *) aDisplayName
inContainer: (id) aContainer
{
SOGoContactLDAPFolder *folder;
id folder;
folder = [[self alloc] initWithName: aName
andDisplayName: aDisplayName
@@ -68,9 +68,9 @@
return self;
}
- (id <SOGoContactFolder>) initWithName: (NSString *) newName
andDisplayName: (NSString *) newDisplayName
inContainer: (SOGoObject *) newContainer
- (id) initWithName: (NSString *) newName
andDisplayName: (NSString *) newDisplayName
inContainer: (id) newContainer
{
if ((self = [self initWithName: newName
inContainer: newContainer]))
@@ -256,7 +256,28 @@
return YES;
}
/* sorting */
- (NSComparisonResult) compare: (id) otherFolder
{
NSComparisonResult comparison;
if ([NSStringFromClass([otherFolder class])
isEqualToString: @"SOGoContactGCSFolder"])
comparison = NSOrderedDescending;
else
comparison
= [[self displayName]
localizedCaseInsensitiveCompare: [otherFolder displayName]];
return comparison;
}
/* acls */
- (NSString *) ownerInContext: (WOContext *) noContext
{
return @"nobody";
}
/* TODO: this might change one day when we support LDAP acls */
- (NSArray *) aclsForUser: (NSString *) uid
{

View File

@@ -9,12 +9,7 @@
classes = {
SOGoContactFolders = {
superclass = "SOGoFolder";
protectedBy = "Access Contents Information";
defaultRoles = {
"Access Contents Information" = ( "Authenticated" );
"WebDAV Access" = ( "Authenticated" );
};
superclass = "SOGoParentFolder";
};
SOGoContactGCSFolder = {
superclass = "SOGoFolder";

View File

@@ -30,9 +30,7 @@ Mailer_OBJC_FILES += \
SOGoDraftsFolder.m \
SOGoDraftObject.m \
\
SOGoMailForward.m \
SOGoMailEnglishForward.m \
SOGoMailFrenchForward.m
SOGoMailForward.m
Mailer_RESOURCE_FILES += \
Version \
@@ -40,7 +38,11 @@ Mailer_RESOURCE_FILES += \
Mailer_RESOURCE_FILES += \
SOGoMailEnglishForward.wo \
SOGoMailFrenchForward.wo
SOGoMailFrenchForward.wo \
SOGoMailGermanForward.wo
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/sope-gdl1/
ADDITIONAL_LIB_DIRS += -L../../SOPE/sope-gdl1/GDLContentStore/obj/
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/wobundle.make

View File

@@ -53,6 +53,7 @@
NGImap4Envelope *envelope;
int IMAP4ID;
NSMutableDictionary *headers;
NSString *inReplyTo;
NSString *text;
NSString *sourceURL;
NSString *sourceFlag;

View File

@@ -53,6 +53,7 @@
#import <SoObjects/SOGo/NSArray+Utilities.h>
#import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
#import <SoObjects/SOGo/NSString+Utilities.h>
#import <SoObjects/SOGo/SOGoMailer.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import "SOGoMailAccount.h"
@@ -64,37 +65,8 @@
static NSString *contentTypeValue = @"text/plain; charset=utf-8";
static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc",
@"from", @"replyTo", nil};
@interface NSString (NGMimeHelpers)
- (NSString *) asQPSubjectString: (NSString *) encoding;
@end
@implementation NSString (NGMimeHelpers)
- (NSString *) asQPSubjectString: (NSString *) encoding;
{
NSString *qpString, *subjectString;
NSData *subjectData, *destSubjectData;
subjectData = [self dataUsingEncoding: NSUTF8StringEncoding];
destSubjectData = [subjectData dataByEncodingQuotedPrintable];
qpString = [[NSString alloc] initWithData: destSubjectData
encoding: NSASCIIStringEncoding];
[qpString autorelease];
if ([qpString length] > [self length])
subjectString = [NSString stringWithFormat: @"=?%@?Q?%@?=",
encoding, qpString];
else
subjectString = self;
return subjectString;
}
@end
@"from", @"replyTo",
nil};
@implementation SOGoDraftObject
@@ -128,6 +100,7 @@ static BOOL showTextAttachmentsInline = NO;
text = @"";
sourceURL = nil;
sourceFlag = nil;
inReplyTo = nil;
}
return self;
@@ -141,6 +114,7 @@ static BOOL showTextAttachmentsInline = NO;
[path release];
[sourceURL release];
[sourceFlag release];
[inReplyTo release];
[super dealloc];
}
@@ -190,7 +164,7 @@ static BOOL showTextAttachmentsInline = NO;
id headerValue;
unsigned int count;
for (count = 0; count < 6; count++)
for (count = 0; count < 7; count++)
{
headerValue = [newHeaders objectForKey: headerKeys[count]];
if (headerValue)
@@ -216,6 +190,11 @@ static BOOL showTextAttachmentsInline = NO;
return text;
}
- (void) setInReplyTo: (NSString *) newInReplyTo
{
ASSIGN (inReplyTo, newInReplyTo);
}
- (void) setSourceURL: (NSString *) newSourceURL
{
ASSIGN (sourceURL, newSourceURL);
@@ -237,6 +216,8 @@ static BOOL showTextAttachmentsInline = NO;
[infos setObject: headers forKey: @"headers"];
if (text)
[infos setObject: text forKey: @"text"];
if (inReplyTo)
[infos setObject: inReplyTo forKey: @"inReplyTo"];
if (IMAP4ID > -1)
[infos setObject: [NSNumber numberWithInt: IMAP4ID]
forKey: @"IMAP4ID"];
@@ -291,6 +272,10 @@ static BOOL showTextAttachmentsInline = NO;
value = [infoDict objectForKey: @"sourceFlag"];
if (value)
[self setSourceFlag: value];
value = [infoDict objectForKey: @"inReplyTo"];
if (value)
[self setInReplyTo: value];
}
- (NSString *) relativeImap4Name
@@ -498,15 +483,21 @@ static BOOL showTextAttachmentsInline = NO;
- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail
toAll: (BOOL) toAll
{
NSString *contentForReply;
NSString *contentForReply, *msgID;
NSMutableDictionary *info;
NGImap4Envelope *sourceEnvelope;
[sourceMail fetchCoreInfos];
info = [NSMutableDictionary dictionaryWithCapacity: 16];
[info setObject: [sourceMail subjectForReply] forKey: @"subject"];
sourceEnvelope = [sourceMail envelope];
[self _fillInReplyAddresses: info replyToAll: toAll
envelope: [sourceMail envelope]];
envelope: sourceEnvelope];
msgID = [sourceEnvelope messageID];
if ([msgID length] > 0)
[self setInReplyTo: msgID];
contentForReply = [sourceMail contentForReply];
[self setText: contentForReply];
[self setHeaders: info];
@@ -587,7 +578,7 @@ static BOOL showTextAttachmentsInline = NO;
- (BOOL) isValidAttachmentName: (NSString *) _name
{
static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", nil };
unsigned i;
NSRange r;
@@ -620,7 +611,8 @@ static BOOL showTextAttachmentsInline = NO;
withMetadata: (NSDictionary *) metadata
{
NSString *p, *name, *mimeType;
NSRange r;
if (![_attach isNotNull]) {
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
reason: @"Missing attachment content!"];
@@ -630,7 +622,13 @@ static BOOL showTextAttachmentsInline = NO;
return [NSException exceptionWithHTTPStatus:500 /* Server Error */
reason: @"Could not create folder for draft!"];
}
name = [metadata objectForKey: @"filename"];
r = [name rangeOfString: @"\\"
options: NSBackwardsSearch];
if (r.length > 0)
name = [name substringFromIndex: r.location + 1];
if (![self isValidAttachmentName: name])
return [self invalidAttachmentNameError: name];
@@ -1029,7 +1027,9 @@ static BOOL showTextAttachmentsInline = NO;
[map setObjects:[map objectsForKey: @"from"] forKey: @"reply-to"];
/* add subject */
if (inReplyTo)
[map setObject: inReplyTo forKey: @"in-reply-to"];
if ([(s = [headers objectForKey: @"subject"]) length] > 0)
[map setObject: [s asQPSubjectString: @"utf-8"]
forKey: @"subject"];

View File

@@ -32,6 +32,8 @@
#import <NGExtensions/NSNull+misc.h>
#import <NGImap4/NGImap4Connection.h>
#import <SoObjects/SOGo/SOGoUser.h>
#import "SOGoMailFolder.h"
#import "SOGoMailManager.h"
#import "SOGoDraftsFolder.h"
@@ -333,10 +335,32 @@ static BOOL useAltNamespace = NO;
return [NSString stringWithFormat: @"folder%@", inboxFolderName];
}
- (NSString *) _userFolderNameWithPurpose: (NSString *) purpose
{
NSUserDefaults *ud;
NSMutableDictionary *mailSettings;
NSString *folderName;
folderName = nil;
ud = [[context activeUser] userSettings];
mailSettings = [ud objectForKey: @"Mail"];
if (mailSettings)
folderName
= [mailSettings objectForKey: [NSString stringWithFormat: @"%@Folder",
purpose]];
return folderName;
}
- (NSString *) draftsFolderNameInContext: (id) _ctx
{
/* SOGo managed folder */
return [NSString stringWithFormat: @"folder%@", draftsFolderName];
NSString *folderName;
folderName = [self _userFolderNameWithPurpose: @"Drafts"];
if (!folderName)
folderName = draftsFolderName;
return [NSString stringWithFormat: @"folder%@", folderName];
}
- (NSString *) sieveFolderNameInContext: (id) _ctx
@@ -346,12 +370,24 @@ static BOOL useAltNamespace = NO;
- (NSString *) sentFolderNameInContext: (id)_ctx
{
return [NSString stringWithFormat: @"folder%@", sentFolderName];
NSString *folderName;
folderName = [self _userFolderNameWithPurpose: @"Sent"];
if (!folderName)
folderName = sentFolderName;
return [NSString stringWithFormat: @"folder%@", folderName];
}
- (NSString *) trashFolderNameInContext: (id)_ctx
{
return [NSString stringWithFormat: @"folder%@", trashFolderName];
NSString *folderName;
folderName = [self _userFolderNameWithPurpose: @"Trash"];
if (!folderName)
folderName = trashFolderName;
return [NSString stringWithFormat: @"folder%@", folderName];
}
- (SOGoMailFolder *) inboxFolderInContext: (id) _ctx

View File

@@ -20,7 +20,6 @@
*/
#import <NGObjWeb/SoObject+SoDAV.h>
#import <NGObjWeb/SoHTTPAuthenticator.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>

View File

@@ -63,7 +63,7 @@ static BOOL useAltNamespace = NO;
folder = [mailAccount sharedFolderName];
if (folder && [path hasPrefix: folder])
[self setOwner: @"anyone"];
[self setOwner: @"nobody"];
else
{
folder = [mailAccount otherUsersFolderName];
@@ -73,7 +73,7 @@ static BOOL useAltNamespace = NO;
if ([names count] > 1)
[self setOwner: [names objectAtIndex: 1]];
else
[self setOwner: @"anyone"];
[self setOwner: @"nobody"];
}
}
}
@@ -225,42 +225,6 @@ static BOOL useAltNamespace = NO;
/* name lookup */
- (id) lookupImap4Folder: (NSString *) _key
inContext: (id) _ctx
{
// TODO: we might want to check for existence prior controller creation
NSURL *sf;
SOGoMailFolder *newFolder;
/* check whether URL exists */
sf = [self imap4URL];
sf = [NSURL URLWithString: [_key substringFromIndex: 6]
relativeToURL: sf];
// - sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
// - relativeToURL:sf];
if ([[self imap4Connection] doesMailboxExistAtURL: sf])
newFolder = [SOGoMailFolder objectWithName: _key inContainer: self];
else
newFolder = nil;
/*
We may not return 404, confuses path traversal - but we still do in the
calling method. Probably the traversal process should be fixed to
support 404 exceptions (as stop traversal _and_ acquisition).
*/
return newFolder;
}
- (id) lookupImap4Message: (NSString *) _key
inContext: (id) _ctx
{
// TODO: we might want to check for existence prior controller creation
return [SOGoMailObject objectWithName: _key inContainer: self];
}
- (id) lookupName: (NSString *) _key
inContext: (id)_ctx
acquire: (BOOL) _acquire
@@ -268,13 +232,18 @@ static BOOL useAltNamespace = NO;
id obj;
if ([_key hasPrefix: @"folder"])
obj = [self lookupImap4Folder: _key inContext: _ctx];
obj = [SOGoMailFolder objectWithName: _key inContainer: self];
else
{
if (isdigit ([_key characterAtIndex: 0]))
obj = [self lookupImap4Message: _key inContext: _ctx];
if ([[self imap4Connection] doesMailboxExistAtURL: [self imap4URL]])
{
if (isdigit ([_key characterAtIndex: 0]))
obj = [SOGoMailObject objectWithName: _key inContainer: self];
else
obj = [super lookupName: _key inContext: _ctx acquire: NO];
}
else
obj = [super lookupName: _key inContext: _ctx acquire: NO];
obj = nil;
}
if (!obj && _acquire)
@@ -616,7 +585,7 @@ static BOOL useAltNamespace = NO;
return userPath;
}
- (NSString *) httpURLForAdvisoryToUser: (NSString *) uid;
- (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
{
SOGoUser *user;
NSString *otherUsersPath, *url;
@@ -640,7 +609,7 @@ static BOOL useAltNamespace = NO;
return url;
}
- (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid;
- (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
{
NSURL *selfURL, *userURL;

Some files were not shown because too many files have changed in this diff Show More