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
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. |
|
|
|
|