Exemple #1
0
def add_fake_message(
    db_session,
    namespace_id,
    thread=None,
    from_addr=None,
    to_addr=None,
    cc_addr=None,
    bcc_addr=None,
    received_date=None,
    subject="",
    body="",
    snippet="",
    g_msgid=None,
    add_sent_category=False,
):
    from inbox.contacts.processing import update_contacts_from_message
    from inbox.models import Category, Message

    m = Message()
    m.namespace_id = namespace_id
    m.from_addr = from_addr or []
    m.to_addr = to_addr or []
    m.cc_addr = cc_addr or []
    m.bcc_addr = bcc_addr or []
    m.received_date = received_date or datetime.utcnow()
    m.size = 0
    m.is_read = False
    m.is_starred = False
    m.body = body
    m.snippet = snippet
    m.subject = subject
    m.g_msgid = g_msgid

    if thread:
        thread.messages.append(m)
        update_contacts_from_message(db_session, m, thread.namespace.id)

        db_session.add(m)
        db_session.commit()

    if add_sent_category:
        category = Category.find_or_create(db_session,
                                           namespace_id,
                                           "sent",
                                           "sent",
                                           type_="folder")
        if category not in m.categories:
            m.categories.add(category)
        db_session.commit()

    return m
Exemple #2
0
def create_imap_message(db_session, account, folder, msg):
    """
    IMAP-specific message creation logic.

    Returns
    -------
    imapuid : inbox.models.backends.imap.ImapUid
        New db object, which links to new Message and Block objects through
        relationships. All new objects are uncommitted.

    """
    log.debug("creating message",
              account_id=account.id,
              folder_name=folder.name,
              mid=msg.uid)
    new_message = Message.create_from_synced(
        account=account,
        mid=msg.uid,
        folder_name=folder.name,
        received_date=msg.internaldate,
        body_string=msg.body,
    )

    # Check to see if this is a copy of a message that was first created
    # by the Nylas API. If so, don't create a new object; just use the old one.
    existing_copy = reconcile_message(new_message, db_session)
    if existing_copy is not None:
        new_message = existing_copy

    imapuid = ImapUid(account=account,
                      folder=folder,
                      msg_uid=msg.uid,
                      message=new_message)
    imapuid.update_flags(msg.flags)
    if msg.g_labels is not None:
        imapuid.update_labels(msg.g_labels)

    # Update the message's metadata
    with db_session.no_autoflush:
        is_draft = imapuid.is_draft and (folder.canonical_name == "drafts"
                                         or folder.canonical_name == "all")
        update_message_metadata(db_session, account, new_message, is_draft)

    update_contacts_from_message(db_session, new_message, account.namespace.id)

    return imapuid
Exemple #3
0
def update_draft(
    db_session,
    account,
    draft,
    to_addr=None,
    subject=None,
    body=None,
    blocks=None,
    cc_addr=None,
    bcc_addr=None,
    from_addr=None,
    reply_to=None,
):
    """
    Update draft with new attributes.
    """
    def update(attr, value=None):
        if value is not None:
            setattr(draft, attr, value)

            if attr == "body":
                # Update size, snippet too
                draft.size = len(value)
                draft.snippet = draft.calculate_html_snippet(value)

    update("to_addr", to_addr)
    update("cc_addr", cc_addr)
    update("bcc_addr", bcc_addr)
    update("reply_to", reply_to)
    update("from_addr", from_addr)
    update("subject", subject if subject else None)
    update("body", body if body else None)
    update("received_date", datetime.utcnow())

    # Remove any attachments that aren't specified
    new_block_ids = [b.id for b in blocks]
    for part in [x for x in draft.parts if x.block_id not in new_block_ids]:
        draft.parts.remove(part)
        db_session.delete(part)

    # Parts require special handling
    for block in blocks:
        # Don't re-add attachments that are already attached
        if block.id in [p.block_id for p in draft.parts]:
            continue
        draft.parts.append(block_to_part(block, draft, account.namespace))

    thread = draft.thread
    if len(thread.messages) == 1:
        # If there are no prior messages on the thread, update its subject and
        # dates to match the draft.
        thread.subject = draft.subject
        thread.subjectdate = draft.received_date
        thread.recentdate = draft.received_date

    # Remove previous message-contact associations, and create new ones.
    draft.contacts = []
    update_contacts_from_message(db_session, draft, account.namespace.id)

    # The draft we're updating may or may not be one authored through the API:
    # - Ours: is_created = True, Message-Id = public_id+version
    # - Not Ours: is_created = False, Message-Id = ???

    # Mark that the draft is now created by us
    draft.is_created = True

    # Save the current Message-Id so we know which draft to delete in syncback
    old_message_id_header = draft.message_id_header

    # Increment version and rebuild the message ID header.
    draft.version += 1
    draft.regenerate_nylas_uid()

    # Sync to remote
    schedule_action(
        "update_draft",
        draft,
        draft.namespace.id,
        db_session,
        version=draft.version,
        old_message_id_header=old_message_id_header,
    )
    db_session.commit()
    return draft
Exemple #4
0
def create_message_from_json(data, namespace, db_session, is_draft):
    """ Construct a Message instance from `data`, a dictionary representing the
    POST body of an API request. All new objects are added to the session, but
    not committed."""

    # Validate the input and get referenced objects (thread, attachments)
    # as necessary.
    to_addr = get_recipients(data.get("to"), "to")
    cc_addr = get_recipients(data.get("cc"), "cc")
    bcc_addr = get_recipients(data.get("bcc"), "bcc")
    from_addr = get_recipients(data.get("from"), "from")
    reply_to = get_recipients(data.get("reply_to"), "reply_to")

    if from_addr and len(from_addr) > 1:
        raise InputError("from_addr field can have at most one item")
    if reply_to and len(reply_to) > 1:
        raise InputError("reply_to field can have at most one item")

    subject = data.get("subject")
    if subject is not None and not isinstance(subject, basestring):
        raise InputError('"subject" should be a string')
    body = data.get("body", "")
    if not isinstance(body, basestring):
        raise InputError('"body" should be a string')
    blocks = get_attachments(data.get("file_ids"), namespace.id, db_session)
    reply_to_thread = get_thread(data.get("thread_id"), namespace.id,
                                 db_session)
    reply_to_message = get_message(data.get("reply_to_message_id"),
                                   namespace.id, db_session)
    if (reply_to_message is not None and reply_to_thread is not None
            and reply_to_message not in reply_to_thread.messages):
        raise InputError("Message {} is not in thread {}".format(
            reply_to_message.public_id, reply_to_thread.public_id))

    with db_session.no_autoflush:
        account = namespace.account
        dt = datetime.utcnow()
        uid = generate_public_id()
        to_addr = to_addr or []
        cc_addr = cc_addr or []
        bcc_addr = bcc_addr or []
        blocks = blocks or []
        if subject is None:
            # If this is a reply with no explicitly specified subject, set the
            # subject from the prior message/thread by default.
            # TODO(emfree): Do we want to allow changing the subject on a reply
            # at all?
            if reply_to_message is not None:
                subject = reply_to_message.subject
            elif reply_to_thread is not None:
                subject = reply_to_thread.subject
        subject = subject or ""

        message = Message()
        message.namespace = namespace
        message.is_created = True
        message.is_draft = is_draft
        message.from_addr = (from_addr if from_addr else
                             [(account.name, account.email_address)])
        # TODO(emfree): we should maybe make received_date nullable, so its
        # value doesn't change in the case of a drafted-and-later-reconciled
        # message.
        message.received_date = dt
        message.subject = subject
        message.body = body
        message.to_addr = to_addr
        message.cc_addr = cc_addr
        message.bcc_addr = bcc_addr
        message.reply_to = reply_to
        # TODO(emfree): this is different from the normal 'size' value of a
        # message, which is the size of the entire MIME message.
        message.size = len(body)
        message.is_read = True
        message.is_sent = False
        message.public_id = uid
        message.version = 0
        message.regenerate_nylas_uid()

        # Set the snippet
        message.snippet = message.calculate_html_snippet(body)

        # Associate attachments to the draft message
        for block in blocks:
            message.parts.append(block_to_part(block, message, namespace))

        update_contacts_from_message(db_session, message, namespace.id)

        if reply_to_message is not None:
            message.is_reply = True
            _set_reply_headers(message, reply_to_message)
            thread = reply_to_message.thread
            message.reply_to_message = reply_to_message
        elif reply_to_thread is not None:
            message.is_reply = True
            thread = reply_to_thread
            # Construct the in-reply-to and references headers from the last
            # message currently in the thread.
            previous_messages = [m for m in thread.messages if not m.is_draft]
            if previous_messages:
                last_message = previous_messages[-1]
                message.reply_to_message = last_message
                _set_reply_headers(message, last_message)
        else:
            # If this isn't a reply to anything, create a new thread object for
            # the draft.  We specialize the thread class so that we can, for
            # example, add the g_thrid for Gmail later if we reconcile a synced
            # message with this one. This is a huge hack, but works.
            message.is_reply = False
            thread_cls = account.thread_cls
            thread = thread_cls(
                subject=message.subject,
                recentdate=message.received_date,
                namespace=namespace,
                subjectdate=message.received_date,
            )

        message.thread = thread

    db_session.add(message)
    if is_draft:
        schedule_action("save_draft",
                        message,
                        namespace.id,
                        db_session,
                        version=message.version)
    db_session.flush()
    return message