Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
283 lines
10 KiB
JavaScript
Executable file
283 lines
10 KiB
JavaScript
Executable file
/**
|
|
* Centralize the creation of our header and body object representations.
|
|
*
|
|
* We provide constructor functions which take input objects that should
|
|
* basically look like the output object, but the function enforces
|
|
* consistency and provides the ability to assert about the state of the
|
|
* representation at the call-site. We discussed making sure to check
|
|
* representations when we are inserting records into our database, but we
|
|
* might also want to opt to do it at creation time too so we can explode
|
|
* slightly closer to the source of the problem.
|
|
*
|
|
* This module will also provide representation checking functions to make
|
|
* sure all the data structures are well-formed/have no obvious problems.
|
|
*
|
|
* @module mailapi/db/mail_rep
|
|
**/
|
|
|
|
define(function() {
|
|
|
|
/*
|
|
* @typedef[HeaderInfo @dict[
|
|
* @key[id]{
|
|
* An id allocated by the back-end that names the message within the folder.
|
|
* We use this instead of the server-issued UID because if we used the UID
|
|
* for this purpose then we would still need to issue our own temporary
|
|
* speculative id's for offline operations and would need to implement
|
|
* renaming and it all gets complicated.
|
|
* }
|
|
* @key[srvid]{
|
|
* The server-issued UID for the folder, or 0 if the folder is an offline
|
|
* header.
|
|
* }
|
|
* @key[suid]{
|
|
* Basically "account id/folder id/message id", although technically the
|
|
* folder id includes the account id.
|
|
* }
|
|
* @key[guid String]{
|
|
* This is the message-id header value of the message.
|
|
* }
|
|
* @key[author NameAddressPair]
|
|
* @key[to #:optional @listof[NameAddressPair]]
|
|
* @key[cc #:optional @listof[NameAddressPair]]
|
|
* @key[bcc #:optional @listof[NameAddressPair]]
|
|
* @key[replyTo #:optional String]{
|
|
* The contents of the reply-to header.
|
|
* }
|
|
* @key[date DateMS]
|
|
* @key[flags @listof[String]]
|
|
* @key[hasAttachments Boolean]
|
|
* @key[subject @oneof [String null]]
|
|
* @key[snippet @oneof[
|
|
* @case[null]{
|
|
* We haven't tried to generate a snippet yet.
|
|
* }
|
|
* @case['']{
|
|
* We tried to generate a snippet, but got nothing useful. Note that we
|
|
* may try and generate a snippet from a partial body fetch; this does not
|
|
* indicate that we should avoid computing a better snippet. Whenever the
|
|
* snippet is falsey and we have retrieved more body data, we should
|
|
* always try and derive a snippet.
|
|
* }
|
|
* @case[String]{
|
|
* A non-empty string means we managed to produce some snippet data. It
|
|
* is still appropriate to regenerate the snippet if more body data is
|
|
* fetched since our snippet may be a fallback where we chose quoted text
|
|
* instead of authored text, etc.
|
|
* }
|
|
* ]]
|
|
* ]]
|
|
*/
|
|
function makeHeaderInfo(raw) {
|
|
// All messages absolutely need the following; the caller needs to make up
|
|
// values if they're missing.
|
|
if (!raw.author)
|
|
throw new Error('No author?!');
|
|
if (!raw.date)
|
|
throw new Error('No date?!');
|
|
// We also want/require a valid id, but we check that at persistence time
|
|
// since POP3 assigns the id/suid slightly later on. We check the suid at
|
|
// that point too. (Checked in FolderStorage.addMessageHeader.)
|
|
|
|
return {
|
|
id: raw.id,
|
|
srvid: raw.srvid || null,
|
|
suid: raw.suid || null,
|
|
guid: raw.guid || null,
|
|
author: raw.author,
|
|
to: raw.to || null,
|
|
cc: raw.cc || null,
|
|
bcc: raw.bcc || null,
|
|
replyTo: raw.replyTo || null,
|
|
date: raw.date,
|
|
flags: raw.flags || [],
|
|
hasAttachments: raw.hasAttachments || false,
|
|
// These can be empty strings which are falsey, so no ||
|
|
subject: (raw.subject != null) ? raw.subject : null,
|
|
snippet: (raw.snippet != null) ? raw.snippet : null,
|
|
imapMissingInSyncRange: raw.imapMissingInSyncRange || null
|
|
};
|
|
}
|
|
|
|
/*
|
|
* @typedef[BodyInfo @dict[
|
|
* @key[date DateMS]{
|
|
* Redundantly stored date info for block splitting purposes. We pretty
|
|
* much need this no matter what because our ordering is on the tuples of
|
|
* dates and UIDs, so we could have trouble efficiently locating our header
|
|
* from the body without this.
|
|
* }
|
|
* @key[size Number]
|
|
* @key[attaching #:optional AttachmentInfo]{
|
|
* Because of memory limitations, we need to encode and attach attachments
|
|
* in small pieces. An attachment in the process of being attached is
|
|
* stored here until fully processed. Its 'file' field contains a list of
|
|
* Blobs.
|
|
* }
|
|
* @key[attachments @listof[AttachmentInfo]]{
|
|
* Proper attachments for explicit downloading.
|
|
* }
|
|
* @key[relatedParts @oneof[null @listof[AttachmentInfo]]]{
|
|
* Attachments for inline display in the contents of the (hopefully)
|
|
* multipart/related message.
|
|
* }
|
|
* @key[references @oneof[null @listof[String]]]{
|
|
* The contents of the references header as a list of de-quoted ('<' and
|
|
* '>' removed) message-id's. If there was no header, this is null.
|
|
* }
|
|
* @key[bodyReps @listof[BodyPartInfo]]
|
|
* ]]{
|
|
* Information on the message body that is only for full message display.
|
|
* The to/cc/bcc information may get moved up to the header in the future,
|
|
* but our driving UI doesn't need it right now.
|
|
* }
|
|
*/
|
|
function makeBodyInfo(raw) {
|
|
if (!raw.date)
|
|
throw new Error('No date?!');
|
|
if (!raw.attachments || !raw.bodyReps)
|
|
throw new Error('No attachments / bodyReps?!');
|
|
|
|
return {
|
|
date: raw.date,
|
|
size: raw.size || 0,
|
|
attachments: raw.attachments,
|
|
relatedParts: raw.relatedParts || null,
|
|
references: raw.references || null,
|
|
bodyReps: raw.bodyReps
|
|
};
|
|
}
|
|
|
|
/*
|
|
* @typedef[BodyPartInfo @dict[
|
|
* @key[type @oneof['plain' 'html']]{
|
|
* The type of body; this is actually the MIME sub-type.
|
|
* }
|
|
* @key[part String]{
|
|
* IMAP part number.
|
|
* }
|
|
* @key[sizeEstimate Number]
|
|
* @key[amountDownloaded Number]
|
|
* @key[isDownloaded Boolean]
|
|
* @key[_partInfo #:optional RawIMAPPartInfo]
|
|
* @key[content]{
|
|
* The representation for 'plain' values is a `quotechew.js` processed
|
|
* body representation which is pair-wise list where the first item in each
|
|
* pair is a packed integer identifier and the second is a string containing
|
|
* the text for that block.
|
|
*
|
|
* The body representation for 'html' values is an already sanitized and
|
|
* already quote-normalized String representation that could be directly
|
|
* fed into innerHTML safely if you were so inclined. See `htmlchew.js`
|
|
* for more on that process.
|
|
* }
|
|
* ]]
|
|
*/
|
|
function makeBodyPart(raw) {
|
|
// We don't persist body types to our representation that we don't understand.
|
|
if (raw.type !== 'plain' &&
|
|
raw.type !== 'html')
|
|
throw new Error('Bad body type: ' + raw.type);
|
|
// 0 is an okay body size, but not giving us a guess is not!
|
|
if (raw.sizeEstimate === undefined)
|
|
throw new Error('Need size estimate!');
|
|
|
|
return {
|
|
type: raw.type,
|
|
part: raw.part || null,
|
|
sizeEstimate: raw.sizeEstimate,
|
|
amountDownloaded: raw.amountDownloaded || 0,
|
|
isDownloaded: raw.isDownloaded || false,
|
|
_partInfo: raw._partInfo || null,
|
|
content: raw.content || ''
|
|
};
|
|
}
|
|
|
|
|
|
/*
|
|
* @typedef[AttachmentInfo @dict[
|
|
* @key[name String]{
|
|
* The filename of the attachment, if any.
|
|
* }
|
|
* @key[contentId String]{
|
|
* The content-id of the attachment if this is a related part for inline
|
|
* display.
|
|
* }
|
|
* @key[type String]{
|
|
* The (full) mime-type of the attachment.
|
|
* }
|
|
* @key[part String]{
|
|
* The IMAP part number for fetching the attachment.
|
|
* }
|
|
* @key[encoding String]{
|
|
* The encoding of the attachment so we know how to decode it. For
|
|
* ActiveSync, the server takes care of this for us so there is no encoding
|
|
* from our perspective. (Although the attachment may get base64 encoded
|
|
* for transport in the inline case, but that's a protocol thing and has
|
|
* nothing to do with the message itself.)
|
|
* }
|
|
* @key[sizeEstimate Number]{
|
|
* Estimated file size in bytes. Gets updated to be the correct size on
|
|
* attachment download.
|
|
* }
|
|
* @key[file @oneof[
|
|
* @case[null]{
|
|
* The attachment has not been downloaded, the file size is an estimate.
|
|
* }
|
|
* @case[@list["device storage type" "file path"]{
|
|
* The DeviceStorage type (ex: pictures) and the path to the file within
|
|
* device storage.
|
|
* }
|
|
* @case[HTMLBlob]{
|
|
* The Blob that contains the attachment. It can be thought of as a
|
|
* handle/name to access the attachment. IndexedDB in Gecko stores the
|
|
* blobs as (quota-tracked) files on the file-system rather than inline
|
|
* with the record, so the attachments don't need to count against our
|
|
* block size since they are not part of the direct I/O burden for the
|
|
* block.
|
|
* }
|
|
* @case[@listof[HTMLBlob]]{
|
|
* For draft messages, a list of one or more pre-base64-encoded attachment
|
|
* pieces that were sliced up in chunks due to Gecko's inability to stream
|
|
* Blobs to disk off the main thread.
|
|
* }
|
|
* ]]
|
|
* @key[charset @oneof[undefined String]]{
|
|
* The character set, for example "ISO-8859-1". If not specified, as is
|
|
* likely for binary attachments, this should be null.
|
|
* }
|
|
* @key[textFormat @oneof[undefined String]]{
|
|
* The text format, for example, "flowed" for format=flowed. If not
|
|
* specified, as is likely for binary attachments, this should be null.
|
|
* }
|
|
* ]]
|
|
*/
|
|
function makeAttachmentPart(raw) {
|
|
// Something is very wrong if there is no size estimate.
|
|
if (raw.sizeEstimate === undefined)
|
|
throw new Error('Need size estimate!');
|
|
|
|
return {
|
|
// XXX ActiveSync may leave this null, although it's conceivable the
|
|
// server might do normalization to save us. This needs a better treatment.
|
|
// IMAP generates a made-up name for us if there isn't one.
|
|
name: (raw.name != null) ? raw.name : null,
|
|
contentId: raw.contentId || null,
|
|
type: raw.type || 'application/octet-stream',
|
|
part: raw.part || null,
|
|
encoding: raw.encoding || null,
|
|
sizeEstimate: raw.sizeEstimate,
|
|
file: raw.file || null,
|
|
charset: raw.charset || null,
|
|
textFormat: raw.textFormat || null
|
|
};
|
|
}
|
|
|
|
return {
|
|
makeHeaderInfo: makeHeaderInfo,
|
|
makeBodyInfo: makeBodyInfo,
|
|
makeBodyPart: makeBodyPart,
|
|
makeAttachmentPart: makeAttachmentPart
|
|
};
|
|
|
|
}); // end define
|