1# About this Document (Discouragement for Cheaters) 2 3You need to read the whole thing. If you are writing a protocol, you have 4to understand filters, chains, callbacks, the works. Filter authors can skip 5the "Writing a Protocol" section, I suppose, but other than that, read it all. 6Trust me, it's worth it. Also, if something doesn't make sense, or at any point 7something becomes difficult that you feel probably shouldn't be, drop us a 8line at zoidberg@bug-br.org.br – we're glad to help. 9 10## General Architecture - Introduction to Chains 11 12When you create an account in E-mail preferences, it creates two chains: 13one inbound, one outbound. Each chain consists of a number of stored references 14to mail filters, the generic type of Mail Daemon add-on, and their settings, 15in the form of a flattened `BMessage`. The chain also stores global chain meta 16data, also in a flattened `BMessage`, and various auxiliary information like 17the chain name, and whether it is an outbound or inbound chain. The 18distinction is important only to (1) set the color of the status bar when the 19chain is run and (2) identify to the daemon which chains to run when it is 20asked to fetch or to send mail. Each chain is identified by a unique unsigned 2132-bit integer, the chain ID. 22 23## Introduction to Filters 24 25Every MDR add-on is conceptually a filter, and, programmatically, derived 26from the `Mail::Filter` class (which is to be found in `MailAddon.h`). An MDR 27filter is not the standard sort of e-mail filter (a sorter, etc.), but is 28defined to be any sort of entity that modifies, parses, or otherwise cares 29about an e-mail message in the process of it being sent (or received). Into 30this very broad definition, it is possible to fit all the add-ons it is 31possible (for us, at any rate, with our inadequate minds) to imagine: 32protocols, standard e-mail filters, notification windows, message saving, 33etc. In fact, the vast, vast majority of what happens when a message is 34transferred happens not in the daemon itself, but in one of its many add-ons. 35 36## Introduction to ChainRunner and Callbacks 37 38The `Mail::ChainRunner` class exists, as the name would imply, to run chains. 39It is of great use to you, the MDR add-on author. It publishes a variety of 40useful public routines (like `ShowError()`) that will be described in their 41appropriate sections, and does a number of other things that will also be 42described later. But it does do one thing that is of general importance and 43interest, and as near in importance for you to understand as filters: 44callbacks. 45 46Callbacks are called at the completion (successful or otherwise) of some 47aspect of chain execution. 48 49At the moment of completion, the callback's Callback() routine is called 50with the error code that completed whatever it is the callback was waiting 51for (`B_OK` or one of the `B_MAIL_*` family in MailAddon.h generally indicate 52successful completion), and the callback is then destroyed. Callbacks come 53in three kinds: message, process, and chain, and are registered by the 54Register*Callback() routines of ChainRunner. These types of callbacks are 55called, respectively, at the termination of a message transfer, a block of 56message transfers (e.g. after all new messages are fetched off the server), 57or the chain (just before all the add-ons are to be destroyed). These are 58useful for a whole variety of tasks, and are used, for example, in such 59things as deleting messages after they are fetched in POP3. 60 61## How to Write a Filter 62 63The `Mail::Filter` class has two important hooks (actually, it only has two 64hooks, but they are quite important one): `InitCheck()` and `ProcessMailMessage()`. 65`InitCheck()` corresponds to the standard Be API `InitCheck()` function: 66after construction of your filter (which, for things like protocols, 67may involve complicated things like connecting to a server), `InitCheck()` 68is called. If something is wrong (say, you couldn't connect to the server), 69return an appropriate error code, and, if out_message does not equal `NULL`, 70set it to an appropriate human readable error message. If it does equal 71`NULL`, it is suggested that you call ChainRunner's `ShowError()` routine 72(see Error Reporting for more information). If `InitCheck()` returns an error, 73construction of the chain stops, all filters are deleted, and `ChainRunner` 74packs up and goes home. 75 76After successful construction of all the filters in the chain, 77`ProcessMailMessage()` is called for each message that passes through it. 78It takes what looks, at first glance, like a bewildering array of arguments, 79but they generally make sense and most filter applications don't need to use 80them all anyway. 81 82 * `BPositionIO** io_message`: This is where the message is to be written to 83 (or read from). Astute observers will note that it is a pointer to a 84 pointer, and will question either our sanity or my typing, depending 85 on their frames of mind and personalities, among other things. But 86 this aspect allows you to modify the argument in unexpected (and, 87 naturally, very useful) ways. IMAP and POP3 replace the argument with 88 their own reader that retrieves data from the server as it is requested. 89 The Outbox filter swaps the argument for a BFile pointing to the message 90 to be fetched. Note that if you do swap it, you become responsible for 91 the deletion of the old argument. If you don't, there will be memory 92 leaks and other untold havoc. (Further information on replacing 93 `io_message` is available under How to Write a Protocol) 94 95 * `BEntry *io_entry`: This tells you where, on disk, the contents of the 96 message are kept. Useful for debugging purposes and for moving it about, 97 although this last is not reccomended. For information on why not, see 98 `io_headers` and `io_folder` (the next two, for the lazy). 99 100 * `BMessage *io_headers`: This contains a list of various kinds of random 101 junk in addition to a list of the headers of the message (after it's been 102 through the Parser filter, which means it's blank for protocols, and full 103 of yummy data for everyone else). The headers are stored as strings, with 104 the key the header tag in whatever case it was in the message header (the 105 subject, for instance, can be found with `FindString("Subject")`). If you 106 modify these entries, they are written to disk in whatever form you leave 107 them. In addition, there are several MDR-added entries (the previously 108 mentioned "random junk"). These are the THREAD, NAME, SIZE, and DESTINATION 109 fields. THREAD is the message thread (the subject with, Re:, Fwd:, etc. 110 removed), NAME is the name of the sender (as displayed in Tracker in the 111 "Name" attribute), SIZE (stored as a size_t) is the complete message size 112 (in bytes), and DESTINATION, which may or may not have been added, is an 113 override value for where, on disk, the message should be stored. You can 114 add this to have the message be placed somewhere other than the user's 115 defined inbox. 116 117 * `BPath *io_folder`: This defines the subfolder of the user's inbox to 118 which the message will be added, expressed relative to the inbox. IMAP 119 uses it for the folder structure (it sets it to the name of the IMAP folder), 120 and POP3 leaves it blank. If left blank, it will not be placed in a 121 subfolder. 122 123 * `const char *io_uid`: This is the unique id of the message, in some form 124 that makes sense to the protocol. Usually of no concern to any filter. 125 126After processing the message, `ProcessMailMessage()` returns either `B_OK`, 127a descriptive error code, or one of the constants at the top of `MailAddon.h` 128(`B_MAIL_DISCARD`, `B_MAIL_END_FETCH`, or `B_MAIL_END_CHAIN`). `B_OK` causes 129the message to continue down the chain, `B_MAIL_DISCARD` causes it to be 130deleted from disk and from the server and terminates the processing of the 131message, error codes terminates the processing of the message as well, 132`B_MAIL_END_FETCH` terminates the fetching of all remaining messages in this 133fetch block, and `B_MAIL_END_CHAIN` indicates a catastrophic error has 134occurred that requires the chain to be destroyed and the connection closed. 135 136## Instantiating and Configuring the Filter 137 138MDR uses three symbols in a filter, two of which are optional. They are 139described below: 140 141`instantiate_mailfilter`: This is called to instantiate a new copy of your 142filter. It is passed a copy of the filter's settings and a pointer to the 143calling `ChainRunner`. 144 145`instantiate_config_panel`: This is passed a copy of your filter's settings 146and the chain meta data. From it, you should return a BView with configuration 147options. E-mail prefs will call `ResizeToPreferred()` on it after it is 148instantiated. To save, the prefs app will call `Archive()`. The passed 149`BMessage *` becomes your settings. 150 151`descriptive_name`: This is passed the settings of the filter, and a 152`char * buffer`. If this routine returns `B_OK`, the contents of the buffer 153will replace the name of the add-on in E-mail prefs. 154 155## How to Write a Protocol 156 157While it is possible to write a protocol using nothing but the 158`Mail::Filter` hooks, this is the Bad Way™ to do it. Instead of forcing you 159through that, we've created the spectacularly useful `Mail::Protocol` class 160(found, unsurprisingly, in `MailProtocol.h`). `Mail::Protocol` has two hooks, 161`GetMessage()` and `DeleteMessage()`, a few member items, and a number of 162important conventions. The MDR side of a mail protocol is fairly simple and 163easy to understand; the network side of things may not be, and the best we can 164do there is wish you luck. But you (hopefully) won't be cursing MDR. 165 166### Part I: Starting the Connection (or, what to do in your constructor) 167 168When your protocol is instantiated by `instantiate_mailfilter()`, you are 169expected to initiate the connection. Information on this is contained in your 170settings in a standard format, and can be written to your settings in that 171format by `Mail::ProtocolConfigView`. The existance of this class makes your 172life easy (you can return one from instantiate_config_panel and not worry 173about configuration any further). The format is described at the end of this 174section. After successfully establishing the connection, you are expected to 175add the unique ids of every message on the server to the protected data member 176unique_ids. This is a StringList, a special class we've created just for MDR. 177It uses simple operators like `+=`, and shouldn't require too much work to 178understand. The header is StringList.h, in the support subdirectory. 179After adding all the unique ids, you need to tell ChainRunner to get the new 180messages. You do this as follows: 181 182``` 183StringList to_dl; 184manifest->NotHere(*unique_ids, &to_dl); 185runner->GetMessages(&to_dl, maildrop_size); 186``` 187 188where maildrop_size is the combined total length (in bytes) of all the 189messages on the server. If you don't know this, or determining it would be 190complicated, slow, awkward, or just plain annoying, you can pass `-1`, in 191which case the status bar will advance by message count instead of transferred 192bytes. 193 194### Part II: Protocol Settings Format 195 196``` 197server (string): The IP address or hostname of the server 198 199port (int32): The port on the server to connect to, if the user has specified one 200 201flavor (int32): The 0-based index of the protocol flavor the user has chosen. If you didn't give the 202 user a choice of flavors in ProtocolConfigView, you can ignore this with impunity. 203 204username (string): The user name entered in config. 205 206password & cpasswd (string): These give you the password, which may or may not have been stored 207 encrypted. Use this code to get the password in plain text (stored in the password variable): 208 209 const char *password = settings->FindString("password"); 210 char *passwd = get_passwd(settings, "cpasswd"); 211 if (passwd) 212 password = passwd; 213 214auth_method (int32): The 0-based index of the authentication method the user has chosen. If you 215 didn't give the user a choice of methods in ProtocolConfigView, you can ignore this with 216 impunity. 217``` 218 219### Part III: Fetching Messages (or, what to do in GetMessage()) 220 221In your protocol's `GetMessage()` routine, you fetch the message indicated by 222uid, into out_file. If your protocol is of the type that has multiple folders, 223you can indicate that to future filters by setting out_folder_location to the 224name of the folder in which the message is found. That's all you need to do. 225 226Things get more complicated (you knew they would) if you want to support 227partial message downloading. To do this, you need to replace out_file with 228some sort of `BPositionIO` derivative that reads the message as required. Every 229byte read from your `BPositionIO` derivative must also be written in the on-disk 230representation of the message, that is, the old out_file argument. Second, 231when your sub-class is deleted, you must delete the old out_file. Third, when 232anyone does a Seek() operation referenced from SEEK_END, you must download the 233whole message. You also need to add to out_headers an int32 named SIZE 234containing the size, in bytes, of the complete message. 235 236### Part IV: Deleting Messages 237 238This is really simple. When `DeleteMessage()` is called, you delete the 239message indicated by uid. You also need to modify the unique_ids list. To do 240this, just do `(*unique_ids) -= uid;`. 241 242### Part V: The Rest of It 243 244As far as MDR is concerned, there is no rest of it. Everything else on the 245BeOS side of things is taken care of by Mail::Protocol. Then there's the 246network.... we'll leave you to that, and bother you no further, except to 247ask you to read the next two sections: 248 249### RemoteStorageProtocol 250 251For IMAP-like protocols (that is, remotely stored mail systems with multiple 252mailboxes), we provide you with the RemoteStorageProtocol class. It handles 253most everything on the BeOS side. You need simply to implement RSP's six hook 254functions: GetMessage(), AddMessage(), DeleteMessage(), CopyMessage(), 255CreateMailbox(), and DeleteMailbox(). These should be fairly self-explanatory. 256A couple of notes are in order, however. 257 258First, unique ids MUST NOT contain a '/' character. If they do, everything 259will go to hell. 260 261Second, when you fill the unique_ids structure, use the following format: 262`mailbox/id`. Mailbox names can contain / characters, and foo/bar will be 263interpreted as a nested directory. 264 265Second, you needn't remove anything from unique_ids in DeleteMessage(). 266That's handled for you. 267 268Third, hooks like CopyMessage() and AddMessage() are passed the unique id 269in a BString pointer. Fill this with the unique id the copy/uploaded message 270receives once it's on the server. 271 272### Progress Reporting 273 274When your protocol receives a message, or a part of it, it is important 275(from the user's point of view) that you display that fact. ChainRunner 276provides a very simple way to do this, in its ReportProgress() function. 277ReportProgress() takes three arguments: the number of bytes received since the 278last call, the number of messages received since the last call, and an update 279message. If you just want to inform the user of something happening 280("Logging in", for instance), you can leave the bytes and messages argument 281blank. 282 283### Error Reporting 284 285To report an error to the user, you need to call ChainRunner's `ShowError()` 286method with some human-readable string describing the error condition. In 287addition, you should take whatever action is necessary to report the error to 288MDR in machine-understandable form, such as returning an error code, or calling 289ChainRunner's Stop() method. Note that Stop() adds a message to the end of the 290queue – you need to return a fatal error from ProcessMailMessage() to interrupt 291a mail fetch in progress. 292