You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

511 lines
18 KiB

KMail design principles
=======================
This file is intended to guide the reader's way through the KMail
codebase. It should esp. be handy for people not hacking full-time on
KMail as well as people that want to trace bugs in parts of KMail
which they don't know well.
Contents:
- Kernel
- Identity
- Filters
- ConfigureDialog
- MDNs
- Folders
- Index
TODO: reader, composer, messages, accounts, ...
KERNEL
======
Files: kmkernel.{h,cpp}
Contact Zack Rusin <zack@kde.org> with questions...
The first thing you'll notice about KMail is the extensive use of
kernel->xxx() constructs. The "kernel" is a define in kmkernel.h
declared as :
#define kernel KMKernel::self()
KMKernel is the central object in KMail. It's always created before
any other class, therefore you are _guaranteed_ that KMKernel::self()
(and therefore "kernel" construct) won't return 0 (null).
KMKernel implements the KMailIface (our DCOP interface) and gives
access to all the core KMail functionality.
IDENTITY
========
Files: identity*, kmidentity.{h,cpp}, configuredialog.cpp,
signatureconfigurator.{h,cpp}
Contact Marc Mutz <mutz@kde.org> on questions...
Identities consists of various fields represented by
QStrings. Currently, those fields are hardcoded, but feel free to
implement KMIdentity as a map from strings to QVariants or somesuch.
One part of identities are signatures. They can represent four modes
(Signature::Type) of operation (disabled, text from file or command
and inline text), which correspond to the combo box in the
identitydialog.
KMIdentities are designed to be used through the IdentityManager:
const KMIdentity & ident =
kernel->identityManager()->identityForUoidOrDefault(...)
Make sure you assign to a _const_ reference, since the identityForFoo
methods are overloaded with non-const methods that access a different
list of identities in the manager that is used while configuring. That
is known source of errors when you use identityForFoo() as a parameter
to a method taking const KMIdentity &.
WARNING: Don't hold the reference longer than the current functions
scope or next return to the event loop. That's b/c the config dialog
is not modal and the user may hit apply/ok anytime between calls to
function that want to use the identity reference. Store the UOID
instead if you need to keep track of the identity. You may also want
to connect to one of the IdentityManager::changed() or :;deleted()
signals, if you want to do special processing in case the identity
changes.
Thus, in the ConfigureDialog, you will see non-const KMIdentity
references being used, while everywhere else (KMMessage,
IdentityCombo) const references are used.
The IdentityCombo is what you see in the composer. It's a
self-updating combo box of KMIdentity's. Use this if you want the user
to choose an identity, e.g. in the folder dialog.
Ihe IdentityListView is what you see in the config dialog's identity
management page. It's not meant to be used elsewhere, but is DnD
enabled (well, at the time of this writing, only drag-enabled). This
is going to be used to dnd identities around between KNode and KMail,
e.g.
The SignatureConfigurator is the third tab in the identity
dialog. It's separate since it is used by the identity manager to
request a new file/command if the current value somehow fails.
FILTER
=======
Contact Marc Mutz <mutz@kde.org> on questions...
Filters consist of a search pattern and a list of actions plus a few
flags to indicate when they are to be applied (kmfilter.h).
They are managed in a QPtrList<KMFilter>, called KMFilterMgr. This
filter magnager is responsible for loading and storing filters
(read/writeConfig) and for executing them (process). The unique
instance of the filter manager is held by the kernel
(KMKernel::filterMgr()).
The search pattern is a QPtrList of search rules (kmsearchpattern.h) and a
boolean operator that defines their relation (and/or).
A search rule consists of a field-QString, a "function"-enum and a
"contents" or "value" QString. The first gives the header (or
pseudoheader) to match against, the second says how to match (equals,
consists, is less than,...) and the third holds the pattern to match
against.
Currently, there are two types of search rules, whcih are mixed
together into a single class: String-valued and int-valued. The latter
is a hack to enable <size> and <age in days> pseudo-header matching.
KMSearchRules should better be organized like KMFilterActions are.
A filter action (kmfilteraction.h) inherits from KMFilterAction or one
of it's convenience sub-classes. They have three sub-interfaces: (1)
argument handling, (2) processing and (3) parameter widget handling.
Interface (1) consists of args{From,As}String(), name() and
isEmpty() and is used to read and write the arguments (if any) from/to
the config.
Interface (2) is used by the filter manager to execute the action
(process() / ReturnCode).
Interface (3) is used by the filter dialog to allow editing of
actions and consists of name(), label() and the
*ParamWidget*(). Complex parameter widgets are collected in
kmfawidget.{h,cpp}.
A typical call for applying filters is
KMKernel::filterMgr()
foreach message {
KMFilterMgr::process():
}
CONFIGURE DIALOG
================
Files: configuredialog*.{h,cpp} ( identitylistview.{h,cpp} )
Contact Marc Mutz <mutz@kde.org> on questions...
The configuredialog is made up of pages that in turn may consist of a
number of tabs. The genral rule of thumb is that each page and tab is
responsible for reading and writing the config options presented on
it, although in the future, that may be reduced to interacting with
the corresponding config manager instead. But that won't change the
basic principle.
Thus, there is an abstract base class ConfigurePage (defined in
configuredialog_p.h), which derives from QWidget. It has four methods
of which you have to reimplement at least the first two:
- void setup()
Re-read the config (from the config file or the manager) and update
the widgets correspondingly. That is, you should only create the
widgets in the ctor, not set the options yet. The reason for that is
that the config dialog, once created, is simply hidden and shown
subsequently, so we need a reset-like method anyway.
- void apply()
Read the config from the widgets and write it into the config file
or the corresponding config manager.
- void dismiss()
Called on cancel. You should clean up any temporary stuff you
needed. If you work with a config-manager backed page, you want to
call ConfigManager::rollback() here.
- void installProfile()
This is called when the user selected a profile and hit apply. A
profile is just another KConfig object. Therefore, this method
should be the same as setup(), except that you should only alter
widgets for configs that really exist in the profile.
For tabbed config pages, there exists a convenience class called
TabbedConfigurationPage, which (as of this writing only offers the
addTab() convenience method. It is planned to also provide
reimplemenations of setup, dismiss, apply and installProfile that just
call the same functions for each tab.
MDNs
====
Files: libkdenetwork/kmime_mdn.{h,cpp} and kmmessage.{h,cpp}, mostly
Contact Marc Mutz <mutz@kde.org> on questions...
MDNs (Message Disposition Notifications; RFC 2298) are a way to send
back information regarding received messages back to their
sender. Examples include "message read/deleted/forwarded/processed".
The code in kmime_mdn.{h,cpp} is responsible for creating the
message/disposition-notification body part (2nd child of
multipart/report that makes the MDN) and for providing the template
for human-readable text that goes into the text/plain part (1st child
of the multipart/report).
The code in KMMessage::createMDN() actually constructs a message
containing a MDN for this message, using the kmime_mdn helper
functions. It starts by checking the index for an already sent MDN,
since the RFC demands that MDNs be sent only once for every
message. If that test succeeds, it goes on to check various other
constraints as per RFC and if all goes well the message containing the
multipart/report is created.
If you need to use this functionality, see KMReaderWin::touchMsg() and
KMFilterAction::sendMDN() for examples. The touchMsg() code is invoked
on display of a message and sends a "displayed" MDN back (if so
configured), whereas the KMFilterAction method is a convenience helper
for the various filter actions that can provoke a MDN (move to trash,
redirect, forward, ...).
Folders
=======
Files: kmfolder*.{h,cpp} and *job.{h,cpp}
Contact Zack Rusin <zack@kde.org> with questions...
The inheritance hierarchy among KMail folder structure looks
as follows :
KMFolderNode
/ \
/ \
KMFolderDir \
KMFolder
|
KMFolderIndex
|
|
---< actual folder types: KMFolderImap, KMFolderMbox... >--
Besides the above mentioned classes one more relevant to our
discussion is KMFolderMgr which as the name suggest serves as the
folder manager.
At the base KMail's folder design starts with KMFolderNode which
inherits QObject. KMFolderNode is the base class encapsulating such
common folder properties as the name, boolean saying whether the
given folder is a directory storing the folders with mail or whether
the folder is a folder holding mail directly and finally a
KMFolderDir.
KMFolderDir is, as one might say, a directory abstration which manages
purely abstract KMFolder's. KMFolder's often don't have a disk based
image, they are entities existing only within KMail's design. To
manage the contents of one directory that may contain folders and/or
other directories KMFolderDir was created.
It inherits KMFolderNode and KMFolderNodeList. Yes, the inheritance is rather
bogus and has to be fixed especially if you'll notice that
KMFolderNode holds a pointer to the derived from it KMFolderDir.
KMFolderDir also inherits KMFolderNodeList which is a
QPtrList<KMFolderNode>. A special case of a KMFolderDir is known as
KMFolderRootDir and is supposed to represent the toplevel KMFolderDir
in the KMail's folder hierarchy.
KMFolderDir's serve as folder holders which are managed by
KMFolderMgr's. KMail contains three main KMFolderMgr's. They can be
accessed via KMKernel ( the "kernel" construct ). Those methods are :
1) KMFolderMgr *folderMgr() - which returns the folder manager for
the folders stored locally.
2) KMFolderMgr *imapFolderMgr() - which returns the folder manager
for all imap folders. They're handled a little differently because
for all imap messages only headers are cached locally while the
main contents of all messages is kept on the server.
3) KMFolderMgr *searchFolderMgr() - which returns the folder manager
for the search folders (the folders created by using the "find
messages" tool. Other email clients call that type of folders
"virtual folders".
Finally one has to remember FolderJob classes. These classes allow
one to have asynchronous operations on KMFolder's. You simply create
a Job on a heap, connect to one of its signals and wait till the job
finishes. Folders serve as FolderJob's factories. For example to
asynchronously expire messages in a folder you'd do:
FolderJob *job = parentFolder->createJob( 0,
FolderJob::tExpireMessages
);
job->start();
In this example we ignore signals emitted by the message, in case you
want to do something more interactive like retrieve the full message
from a folder you'd have to do something like :
FolderJob *job = folderParent->createJob( aMsg, tGetMessage );
connect( job, SIGNAL(messageRetrieved(KMMessage*)),
SLOT(msgWasRetrieved(KMMessage*)) );
job->start();
Index (old)
===========
Files: kmfolderindex.{h,cpp} and kmmsg{base,info}.{h,cpp}
Contact Marc Mutz <mutz@kde.org> or
Till Adam <till@adam-lilienthal.de> or
Don Sanders <sanders@kde.org>
with questions...
index := header *entry
header := magic LF NUL header-length byte-order-marker sizeof-long
magic := "# KMail-Index V" 1*DIGITS
header-length := Q_UINT32
byte-order-marker := Q_UINT32( 0x12345678 )
sizeof-long := Q_UINT32( 4 / 8 )
entry := tag length value
tag := Q_UINT32 ; little endian (native?)
length := Q_UINT16 ; little endian (native?)
value := unicode-string-value / ulong-value
unicode-string-value := 0*256QChar ; network-byte-order
ulong-value := unsigned_long ; little endian
Currently defined tag values are:
Msg*Part num. val type obtained by:
No 0 u -
From 1 u fromStrip().stripWhitespace()
Subject 2 u subject().stripWhitespace()
To 3 u toStrip().stripWhiteSpace()
ReplyToIdMD5 4 u replyToIdMD5().stripWhiteSpace()
IdMD5 5 u msgIdMD5().stripWhiteSpace()
XMark 6 u xmark().stripWhiteSpace()
Offset 7 l folderOffset() (not only mbox!)
LegacyStatus 8 l mLegacyStatus
Size 9 l msgSize()
Date 10 l date()
File 11 u fileName() (not only maildir!)
CryptoState 12 l (signatureState() << 16) | encryptionState())
MDNSent 13 l mdnSentState()
ReplyToAuxIdMD5 14 u replyToAuxIdMD5()
StrippedSubject 15 u strippedSubjectMD5().stripWhiteSpace()
Status 16 l status()
u: unicode-string-value; l: ulong-value
Proposed new (KDE 3.2 / KMail 1.6) entry format:
index := header *entry
entry := sync 1*( tag type content ) crc-tag crc-value
sync := Q_UINT16 (32?) ; resync mark, some magic bit pattern
; (together with preceding crc-tag provides
; 24(40)bits to resync on in case of index
; corruption)
tag := Q_UINT8
type := Q_UINT8
content := variable-length-content / fixed-length-content
crc-tag := tag type ; tag=CRC16, type=CRC16
crc-value := Q_UINT16 ; the CRC16 sum is calculated over all of
; 1*( tag type content )
variable-length-content := length *512byte padding
padding := *3NUL ; make the string a multiple of 4 octets in length
fixed-length-content := 1*byte
length := Q_UINT16 (Q_UINT8?)
byte := Q_UINT8
The type field is pseudo-structured:
bit: 7 6 5 4 3 2 1 0
+-----+-----+-----+-----+-----+-----+-----+-----+
| uniq | chunk | len |
+-----+-----+-----+-----+-----+-----+-----+-----+
uniq: 3 bits = max. 8 different types with same chunk size:
for chunk = (0)00 (LSB(base)=0: octets):
00(0) Utf8String
01(0) BitField
10(0) reserved
11(0) Extend
for chunk = (1)00 (LSB(base)=1: 16-octet blocks):
00(1) MD5(Hash/List)
01(1) reserved
10(1) reserved
11(1) Extend
for chunk = 01 (shorts):
000 Utf16String
001-110 reserved
111 Extend
for chunk = 10 (int32):
000 Utf32String (4; not to be used yet)
001 Size32
010 Offset32
011 SerNum/UOID
100 DateTime
101 Color (QRgb: (a,r,g,b))
110 reserved
111 Extend
for chunk = 11 (int64):
000 reserved
001 Size64
010 Offset64
011-110 reserved
111 Extend
len: length in chunks
000 (variable width -> Q_UINT16 with the real width follows)
001..111: fixed-width data of length 2^len (2^1=2..2^6=128)
You find all defined values for the type field in indexentrybase.cpp
Currently defined tags are:
tag type content
DateSent DateTime Date:
DateReceived DateTime last Received:'s date-time
FromDisplayName String decoded display-name of first From: addr
ToDisplayName String dto. for To:
CcDisplayName String dto. for Cc:
FromAddrSpecs String possibly IMAA-encoded, comma-separated addr-spec's of From:
ToAddrSpecs String dto. for To:
CcAddrSpecs String dto. for Cc:
Subject String decoded Subject:
BaseSubjectMD5 String md5(utf8(stripOffPrefixes(subject())))
BodyPeek String body preview
MaildirFile String Filename of maildir file for this messagea
MBoxOffset Offset(64) Offset in mbox file (pointing to From_)
MBoxLength Size(64) Length of message in mbox file (incl. From_)
Size Size(64) rfc2822-size of message (in mbox: excl. From_)
Status BitField (see below)
MessageIdMD5 MD5Hash MD5Hash of _normalized_ Message-Id:
MDNLink SerialNumber SerNum of MDN received for this message
DNSLink SerialNumber SerNUm of DSN received for this message
ThreadHeads SerialNumberList MD5Hash's of all (so far discovered)
_top-level thread parents_
ThreadParents SerialNumberList MD5Hash's of all (so far discovered)
thread parents
"String" is either Utf8String or (Utf16String or Latin1String),
depending on content
Currently allocated bits for the Status BitField are:
Bit Value: on(/off) (\imapflag)
# "std stati":
0 New (\Recent)
1 Read (\Seen)
2 Answered (\Answered)
3 Deleted (\Deleted)
4 Flagged (\Flagged)
5 Draft (\Draft)
6 Forwarded
7 reserved
# message properties:
8 HasAttachments
9 MDNSent ($MDNSent)
10..11 00: unspecified, 01: Low, 10: Normal, 11: High Priority
12..15 0001: Queued, 0010: Sent, 0011: DeliveryDelayed,
0100: Delivered, 0101: DeliveryFailed,
0110: DisplayedToReceiver, 0111: DeletedByReceiver,
1001: ProcessedByReceiver, 1010: ForwardedByReceiver,
1011-1110: reserved
1111: AnswerReceived
# signature / encryption state:
16..19 0001: FullyEncrypted, 0010: PartiallyEncrypted, 1xxx: Problem
20..23 0001: FullySigned, 0010: PartiallySigned, 1xxx: Problem
# "workflow stati":
24..25 01: Important, 10: ToDo, 11: Later (from Moz/Evo)
26..27 01: Work, 10: Personal, 11: reserved (dto.)
28..29 01: ThreadWatched, 10: ThreadIgnored, 11: reserved
30..31 reserved
All bits and combinations marked as reserved MUST NOT be altered if
set and MUST be set to zero (0) when creating the bitfield.