Contact Server? [long]
Contact Server? [long]
- Subject: Contact Server? [long]
- From: Ondra Cada <email@hidden>
- Date: Thu, 14 Feb 2002 19:38:25 +0100
Hello,
I need a general contact server, so I am at verge of writing one. I'd like
to consult it with you to know whether such a thing might be accepted
generally -- and if so, whether you would suggest some API services I did not
thought of.
If you happen to be interested, let me please know. For reference, here is
the current API design (it's just an alpha release!):
//
// Interface.h
// ContactServer
//
// Created by ocs on Thu Feb 14 2002.
// Copyright (c) 2002 OCSoftware. All rights reserved.
//
// rev. alpha, 14.2.2002
//
#import <Foundation/Foundation.h>
/*
The Contact Server supports the basic Contacts API, needed for any
contacs-like functionality (especially, it contains UIDs and time marks,
needed for a SyncML server, which could be relatively easily implemented over
this API).
The data model is designed with vCards on mind; actually, contacts *are*
vCards, represented by property lists. The main difference between any plain
vCard server and this one is multiple hierarchy support:
It is often reasonable to structure records hierarchically, to express
shared common data -- like this (the vCard-like names in the following
example are not syntactically corect):
BEGIN
ORG:My Company
ADR:Nice Street 1
TEL;
FAX:+12-345678
BEGIN
ORG:;Marketing
TEL:+111-111111
BEGIN
N:Gates;Bill
TEL;CELL:+00-00000
END
BEGIN
N:Doe;John
END
END
BEGIN
ORG:;Programmers
E-MAIL:email@hidden
BEGIN
N:Jobs;Steve
...
The reason is just to be able to share some information easily between more
cards, like the company address and name shared between all three shown ones,
or the Marketing department phone shared betwixt Bill and John.
Although standard vCards do support hierarchy (by nesting cards), it is not
enough, since we often need *more* hierarchies braided: Steve Jobs, for
example, would have at least two "parent" cards: Apple and Pixar (not
speaking of "My Company" shown above ;). Even people who don't have so many
companies might have two "parent" cards: one of the company (shared with
co-workers), one of the family (shared with wife etc.).
Therefore, in this model, each record can have any number of parent records.
The implementation is straighforward: each record contains an array of links
to other records which are its parents (the server API presents array of
childs too, which is computed from parents automatically). Also, some records
can have a special tag which says "this record is used as parent only, not
to be shown by itself". In the previous example it would seem reasonable to
set this tag for both departments, but not for the company, having so
effectively four cards with the following information:
ORG:My Company
ADR:Nice Street 1
TEL;
FAX:+12-345678
N:Gates;Bill
ORG:My Company;Marketing
ADR:Nice Street 1
TEL:+111-111111
TEL;CELL:+00-00000
TEL;
FAX:+12-345678
N:Doe;John
ORG:My Company;Marketing
ADR:Nice Street 1
TEL:+111-111111
TEL;
FAX:+12-345678
N:Jobs;Steve
ORG:My Company;Programmers
ADR:Nice Street 1
MAIL:email@hidden
TEL;
FAX:+12-345678
Note: to distinguish the two views, the hierarchical one which represents
"how data are stored" and this flattened one, which represents "how data
should be seen", we speak of "records" in the former case, and of "cards" in
the latter. It can be said that card is always a record, with some properties
added (by inheritance); not each record though is a card (those who have the
"not shown by itself" tag aren't).
Record structure
================
From the API point of view, each record is an NSDictionary, which contains
values for the following keys:
*/
#define OCSCardUID @"UID" // the UID, NSString
#define OCSCardDate @"Revision" // date of the last revision, NSDate
#define OCSCardHide @"Hide" // if exists, the card should be hidden (serves
only as a parent)
#define OCSCardName @"Name" // just conveniency link to property FN or N or
ORG, first nonempty (or "" if all empty)
#define OCSCardProperties @"Properties" // NSArray of NSDictionaries, which contain
#define OCSCardPUID @"UID" // the property ID (unique inside a record), NSString
#define OCSCardPName @"Name" // name of the property, NSString: vCard
names supported (see (*))
#define OCSCardPAttributes @"Attributes" // NSDictionary of attributes:
vCard supported (see (**))
#define OCSCardPValue @"Value" // the property value, any object (see (*))
#define OCSCardPInheritance @"Inheritance" // for details see (***); if
exists, can have values
#define OCSCardPIPOverride @"Override" // parent value, always
overridden by child
#define OCSCardPIPOverrideIfMatch @"Match" // parent value, overridden
by child if matches
#define OCSCardPIPAdd @"Add" // parent value, always added to child
#define OCSCardPICOverride @"Child Over" // child value, override
parents whatever they contain
#define OCSCardParents @"Parents" // parent records, NSArray of UIDs of
these records
#define OCSCardChilds @"Childs" // child records, NSArray of UIDs of these records
/*
Records returned by the record: or (indirectly) recordEnumerator methods
contain these keys. At this API level there is no explicit inheritance: the
OCSCardProperties array contains record's own properties only. To get also
the inherited ones, the inheritedPropertiesOfRecord: is to be used.
Properties are identified by their IDs, which are unique inside a record.
Properties
==========
(*) The property names are those standard VCARD ones. If the value class is
not specified, an NSString is presumed:
*/
#define VCardFN @"FN" // name of the card to be displayed in lists
#define VCardN @"N" // name of the card, value is a dictionary with keys:
#define VCardN_Family @"Family name"
#define VCardN_Christian @"Christian name"
#define VCardN_Middle @"Middle name"
#define VCardN_Prefix @"Name prefix"
#define VCardN_Suffix @"Name suffix"
#define VCardADR @"ADR" // address, value is a dictionary with keys:
#define VCardADR_Post @"Post Office Address"
#define VCardADR_Extended @"Extended Address"
#define VCardADR_Street @"Street"
#define VCardADR_Locality @"Locality"
#define VCardADR_Region @"Region"
#define VCardADR_ZIP @"Postal Code"
#define VCardADR_Country @"Country"
#define VCardORG @"ORG" // company name, value is an array of strings
#define VCardTEL @"TEL" // phone number
#define VCardPHOTO @"PHOTO" // photo, NSImage
#define VCardBDAY @"BDAY" // ???
#define VCardLABEL @"LABEL" // ???
#define VCardLOGO @"LOGO" // logo, NSImage
#define VCardSOUND @"SOUND" // name or so for voice control, NSSound
// other property names can be freely used, but it is recommended to use the
prefix:
#define VCardProprietaryPrefix @"X-" // other property names should begin with this
#define VCardOCSPrefix @"X-OCSCARD-" // but *not* with this (used by the
server itself)
// pseudo-properties generated by server, see below for when and how they are used:
#define VCardOCSUID "X-OCSCARD-UID"
#define VCardOCSUIDIndex 0
#define VCardOCSRevision "X-OCSCARD-REVISION"
#define VCardOCSRevisionIndex 1
#define VCardOCSName "X-OCSCARD-NAME"
#define VCardOCSNameIndex 2
#define VCardOCSParents "X-OCSCARD-PARENTS"
#define VCardOCSParentsIndex 3
#define VCardOCSChilds "X-OCSCARD-CHILDS"
#define VCardOCSChildsIndex 4
#define VCardOCSGrouped "X-OCSCARD-PROPGROUPED"
#define VCardOCSGroupedIndex 5
/*
Property attributes
===================
(**) The property attributes are again those standard vCard ones (since we
don't need ENCODING or CHARSET with NSStrings, it actually means just TYPE).
The dictionary keys are attribute names; its values are NSSets of appropriate
values (since there can be more values for one attribute, like in
"TEL;WORK;CELL;PREF:..." which has three values of the attribute "TYPE").
*/
#define VCardAType @"TYPE" // name (key) of the TYPE attribute; appropriate
values can be
#define VCardATPref @"PREF" // this is the preferred one of all properties
of the same name
#define VCardATHome @"HOME" // the property describes home contact
#define VCardATWork @"WORK" // the property describes work contact
#define VCardATADom @"DOM" // domestic address
#define VCardATAInternational @"INTL" // international address
#define VCardATAPostal @"POSTAL" // postal address
#define VCardATAParcel @"PARCEL" // parcel address
#define VCardATTVoice @"VOICE" // voice phone number
#define VCardATTFax @"FAX" // fax phone number
#define VCardATTMessage @"MSG" // message phone number
#define VCardATTMobile @"CELL" // cellular phone number
#define VCardATTPager @"PAGER" // pager phone number
#define VCardATTBBS @"BBS" // BBS phone number
#define VCardATTModem @"MODEM" // modem phone number
#define VCardATTCar @"CAR" // car phone number
#define VCardATTISDN @"ISDN" // ISDN phone number
#define VCardATTVideo @"VIDEO" // videophone number
#define VCardATMAOL @"AOL" // AOL mail address
#define VCardATMAppleLink @"AppleLink" // AppleLink mail address
#define VCardATMATTMail @"ATTMail" // ATT mail address
#define VCardATMCIS @"CIS" // CIS mail address
#define VCardATMeWorld @"eWorld" // eWorld mail address
#define VCardATMInternet @"INTERNET" // Internet mail address
#define VCardATMIBMMail @"IBMMail" // IBM mail address
#define VCardATMMCIMail @"MCIMail" // MCI mail address
#define VCardATMPowershare @"POWERSHARE" // Powershare mail address
#define VCardATMProdigy @"PRODIGY" // Prodigy mail address
#define VCardATMTLX @"TLX" // TLX mail address
#define VCardATMX400 @"X400" // X400 mail address
#define VCardAOCSBelongsToOrgs @"X-OCSCARD-ORGS" // grouping, see
"VCardAOCSBelongsToOrgs" below
// other attribute names and values can be freely used, but it is
recommended to use the prefix:
#define VCardProprietaryPPrefix VCardProprietaryPrefix // other names should
begin with this
#define VCardOCSPPrefix VCardOCSPrefix // but *not* with this (used by the
server itself)
/*
Inheritance of properties
=========================
(***) So as the inheritance of properties is consistent, server performs it
(instead of letting clients to do so themselves), see the methods
inheritedPropertiesOfRecord: and composedPropertiesOfCardAtIndex:. The way
individual properties are inherited depends on the property type and
attributes, and can be further controlled by the OCSCardPInheritance value.
The complete inheritance algorithm is:
- if there is a property which contains
OCSCardPInheritance==OCSCardPICOverride, then no property of the same name
from any parent would be inherited (properties with the same name from the
record -- if any -- are presented of course);
- otherwise, properies of all parents are checked recursively.
- if a parent property contains OCSCardPInheritance==OCSCardPIPAdd, it
is added to the list;
- otherwise, if a parent property contains
OCSCardPInheritance==OCSCardPIPOverride, it is added to the list if and only
if there is no property of the same name in the list yet;
- otherwise, if a parent property contains
OCSCardPInheritance==OCSCardPIPOverrideIfMatch, it is added to the list if
and only if there is no property of the same "signature" in the list yet. The
"signature" contains
- property name;
- all property attributes and their values;
- the inherited (!) VCardORG value of the parent.
- otherwise, the behaviour depends on the property name:
VCardN: individual parts (family, christian,...) are merged in just like
they were individual properties with OCSCardPInheritance==OCSCardPIPOverride.
That allows eg. "John Doe" to be inherited, and "Mr." or "Mrs." prefix to be
added from a child record;
VCardORG: the whole value is first inherited as if it had
OCSCardPInheritance==OCSCardPIPOverrideIfMatch. Though, if it should be
overridden (ie. signatures match), its individual parts (distinguished by
their positions) are merged in just like they were individual properties with
OCSCardPInheritance==OCSCardPIPOverrideIfMatch. See the example above how it
supports merging company and department name;
VCardTEL, VCardADR: behaves exactly as if it contained
OCSCardPInheritance==OCSCardPIPOverrideIfMatch
any other behaves exactly as if it contained
OCSCardPInheritance==OCSCardPIPOverride.
For any property name the default behaviour can be redefined via the
OCSCardsDefaultInheritanceBehaviourDefault (see below).
Note that a valueless property is not presented; it allows to "hide" a
parent's property by setting a valueless property with
OCSCardPInheritance==OCSCardPICOverride of the same name in child.
Note also that this behaviour might make the result property list dependent
on the order of parents (eg. in case of
OCSCardPInheritance==OCSCardPIPOverride). That's why there is the
insertParent:... method, see below.
Conveniency pseudo-property
===========================
Since a vast majority of GUI front ends would rather access properties by
name or by kind than by UID, there is a VCardOCSGrouped pseudo-property
automatically generated at index VCardOCSGroupedIndex of the
composedPropertiesOfCardAtIndex: returned array (see the method definition
below for list of other, simpler pseudo-properties). The pseudo-property
contains the same properties as those returned in the array from index
VCardOCSGroupedIndex+1 on (there is no important memory overhead, since the
objects are shared via ids), but re-grouped by their types and names.
The pseudo-property value is an NSDictionary, whose keys are group names
(based on type and inheritance, described below). For each group the
appropriate value is another NSDictionary, whose keys are names of
properties, which belong to the group. For each name, the value is an NSArray
of the actual properties of given name in given group. All properties which
happen to have attribute VCardAType==VCardATPref are ordered before all
others.
The group name for each property is determined by the following algorithm:
(i) first, all inherited properties are checked and put into groups this way:
- if the property has an attribute VCardAType==VCardATHome, "HOME" is added
- if the property has an attribute VCardAType==VCardATWork, "WORK" is added
- the inherited (!) value of VCardORG of the record which owns the property
is added
(ii) all local properties are checked and put into groups this way:
- if the property has an attribute VCardAType==VCardATHome, "HOME" is added
- if the property has an attribute VCardAType==VCardATWork, "WORK" is added
- if there is no VCardAOCSBelongsToOrgs attribute, the property is added to
all groups whose names hasPrefix: of this property name
- otherwise, the property is added to all groups whose names hasPrefix: of
this property name with any of the VCardAOCSBelongsToOrgs values concatenated
The HOME/WORK grouping is straighforward. The remaining rules ensure that
different companies (or, say, clubs) are grouped separately (just like its
similar usage with OCSCardPIPOverrideIfMatch above ensures that all the
appropriate properties are inherited separately).
The VCardAOCSBelongsToOrgs attribute allows to assign properties to
inherited groups: presume Steve's card has two parents, Apple and Pixar. The
phones from both of them are properly separated to "Apple" and "Pixar"
groups, but we might want to add two different work cellular phones to
Steve's card, one to be grouped with Apple, the other with Pixar. To do that,
we would set Steve's card this way:
N:Steve
...
TEL;WORK;CELL;X-OCSCARD-ORGS=Pixar:11111
TEL;WORK;CELL;X-OCSCARD-ORGS=Apple;22222
TEL;WORK;CELL;33333
This way the number 11111 will be grouped just with Pixar, whilst 22222 only
with Apple. The number 33333 though will be shared by all "WORK" groups.
In those rare cases when a client doesn't need this all, but wants to use
composedPropertiesOfCardAtIndex:, and needs the slight performance gain
caused by not computing the pseudo-property value, it can disable its
generation by using a negative index (-index-1, ie. -1 for zero, -2 for
index==1, etc). If so, the array returned by composedPropertiesOfCardAtIndex:
contains just the other pseudo-properties at indexes
0..VCardOCSGroupedIndex-1, and from VCardOCSGroupedIndex up there are the
normal properties.
Consistency check
=================
Consistency check for methods which can change data: any client application
should at start (and after each re-read triggered by the
OCSCardsChangedNotification) record the lastChange date and use it as an
argument to any method which can change data. The server would refuse change
in case the date differs from the actual last change. This ensures
consistency even in case the distributed notification comes late (which is
quite possible with batch processing).
If a client does not want this check, it can disable it by using nil for the
check value, but it is not recommended unless you are quite sure there can't
be a consistency problem.
No Security: why should we?
===========================
I guess there is no need for security since mailboxes are freely readable
anyway, and any mischievous worm can use them to generate (probably) much
longer and better lists of valid addresses (and subjects!) than this API
allows.
I am open to discuss this point of course at email@hidden
*/
#define OCSCardsServerName @"cz.ocs.ContactServer"
// serves both as DO connection name for clients, and as the server's domain
name for defaults
@protocol OCSContactServerProtocol
// generic services
-(NSDate*)lastChange; // date and time of the last change of data inside the server
-(void)beginChangeBatch; // if not called, each change is performed
atomically and notification is sent immediately
-(void)commitChangeBatch; // perform all changes requested since
beginChangeBatch and notify now
-(void)rollbackChangeBatch; // performed also automatically when client dies
// finding and accessing records
-(int)numberOfRecords; // valid only until change (OCSCardsChangedNotification)
-(NSString*)recordUidAtIndex:(int)index; // ditto
-(NSEnumerator*)recordUidEnumerator; // ditto, enumerates record UIDs.
Intended to be used for sequential exports and alike, not for GUI front-ends.
Enumerator "locks" the server in the sense that no change of data is
possible (an attempt to do it would raise an exception) until the enumerator
is not deallocated. See also OCSCardsLockTimeoutDefault below
-(int)numberOfCards; // valid only until change; does not count hidden
records. Intended to be used from GUI front-ends
-(NSString*)cardUidAtIndex:(int)index; // ditto
// record contents
-(NSDictionary*)record:(NSString*)uid; // read-only access to all the values
(but inherited properties)
-(void)removeRecord:(NSString*)uid check:(NSDate*)check; // removes just
this record; if it has any childs, it is removed from their parents' lists
-(void)removeSubtreeOfRecord:(NSString*)uid check:(NSDate*)check; // removes
this record and all its childs recursively
-(NSString*)newRecord check:(NSDate*)check; // returns new record's UID
-(void)setHidden:(BOOL)hidden record:(NSString*)uid check:(NSDate*)check;
-(void)addParent:(NSString*)parentUid record:(NSString*)uid check:(NSDate*)check;
-(void)insertParent:(NSString*)parentUid record:(NSString*)uid
atIndex:(int)index check:(NSDate*)check; // insers into the given index in
the parent list
-(void)removeParent:(NSString*)parentUid record:(NSString*)uid
check:(NSDate*)check;
-(NSString*)addProperty:(NSDictionary*)property record:(NSString*)uid
check:(NSDate*)check; // returns new property's id. The property dictionary
contains OCSCardPName, OCSCardPAttributes, and OCSCardPValue as defined above
-(void)removeProperty:(NSString*)uid record:(NSString*)uid check:(NSDate*)check;
-(void)replaceProperty:(NSString*)uid with:(NSDictionary*)property
record:(NSString*)uid check:(NSDate*)check; // the property dictionary
doesn't need to contain all three items; can be used to change only the
value, or only the name, or only attributes, or so
// inheritance (access to "flattened" attributes)
-(NSArray*)inheritedPropertiesOfRecord:(NSString*)uid; // return an array of
*ALL* properties of a record, its own and inherited. Properties which don't
have any value are not presented here though (see (***) for an information
why such a property might exist)
-(NSString*)ownerOfProperty:property record:(NSString*)uid; // property can
be either the property dictionary or an NSString containing its ID. The
returned value is the UID of record which owns the property (can be uid or
any of its parents)
// conveniency methods
-(NSArray*)composedPropertiesOfCardAtIndex:(int)index; // [server
inheritedPropertiesOfRecord:[server cardUidAtIndex:index]], with some added
pseudo-properties: VCardOCSUID containing the record UID, always at the index
VCardOCSUIDIndex of the returned array; VCardOCSRevision containing the
latest of OCSCardDates of the record and all its parents, always at the index
VCardOCSRevisionIndex of the returned array; VCardOCSName containing
OCSCardName, always at the index VCardOCSNameIndex of the returned array;
VCardOCSParents containing OCSCardParents, always at the index
VCardOCSParentsIndex of the returned array; and VCardOCSChilds containing
OCSCardChilds, always at the index VCardOCSChildsIndex of the returned array.
Also there can be a conveniency pseudo-properties VCardOCSGrouped, described
above, at index VCardOCSGroupedIndex. Pseudo-properties have no OCSCardPUID
and no OCSCardPAttributes, and ownerOfProperty:record: would return nil if
used over them. This method is intended to be used by GUI front-ends,
containing all information relevant for their needs to display a record.
@end
// default names
#define OCSCardsStoreFilePathDefault @"StoreFilePath"
// the file from where server reads the data when started, and where it
commits any change. It cannot be changed while server runs
#define OCSCardsLockTimeoutDefault @"LockTimeout"
// the number of seconds after which a change lock (see the method
recordUidEnumerator) is broken. If a client obtained an enumerator and tries
to use it after this timeout, any usage would result in an exception
#define OCSCardsDefaultInheritanceBehaviourDefault @"DefaultInheritanceBehaviour"
// an NSDictionary, whose keys are property names, and values the default
inheritance behaviours to be used for them (ie. OCSCardPIPOverride,
OCSCardPIPOverrideIfMatch, OCSCardPIPAdd, or OCSCardPICOverride) if none is
given explicitly. Names which are not listed here get their default
behaviours as described at (***)
// notifications
#define OCSCardsChangedNotification @"OCSCardsChangedNotification"
// send through NSDistributedNotificationCenter whenever cards are changed
// exceptions
NOT FINISHED
---
Ondra Cada
OCSoftware: email@hidden
http://www.ocs.cz
2K Development: email@hidden
http://www.2kdevelopment.cz
private email@hidden
http://www.ocs.cz/oc
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.