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=''): from inbox.models import Message from inbox.contacts.process_mail import update_contacts_from_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 if thread: thread.messages.append(m) update_contacts_from_message(db_session, m, thread.namespace) db_session.add(m) db_session.commit() return m
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='', add_sent_category=False): from inbox.models import Message, Category from inbox.contacts.process_mail import update_contacts_from_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 if thread: thread.messages.append(m) update_contacts_from_message(db_session, m, thread.namespace) 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
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=None): from inbox.models import Message from inbox.contacts.process_mail import update_contacts_from_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.sanitized_body = '' m.snippet = '' m.subject = subject or '' if thread: thread.messages.append(m) update_contacts_from_message(db_session, m, thread.namespace) db_session.add(m) db_session.commit() return m
def test_threading_limit(db, folder_sync_engine, monkeypatch): """Test that custom threading doesn't produce arbitrarily long threads, which eventually break things.""" from inbox.models import Message, Thread # Shorten bound to make test faster MAX_THREAD_LENGTH = 10 monkeypatch.setattr( 'inbox.mailsync.backends.imap.generic.MAX_THREAD_LENGTH', MAX_THREAD_LENGTH) namespace_id = folder_sync_engine.namespace_id msg = MockRawMessage([]) for i in range(3 * MAX_THREAD_LENGTH): m = Message() m.namespace_id = namespace_id m.received_date = datetime.datetime.utcnow() m.references = [] m.size = 0 m.body = '' m.from_addr = [("Karim Hamidou", "*****@*****.**")] m.to_addr = [("Eben Freeman", "*****@*****.**")] m.snippet = '' m.subject = 'unique subject' db.session.add(m) folder_sync_engine.add_message_to_thread(db.session, m, msg) db.session.commit() new_threads = db.session.query(Thread). \ filter(Thread.subject == 'unique subject').all() assert len(new_threads) == 3 assert all(len(thread.messages) == MAX_THREAD_LENGTH for thread in new_threads)
def gmail_message(): received_date = datetime.datetime.utcfromtimestamp(10**9 + 1) new_msg = Message() new_msg.to_addr = ((u'Somebody', u'*****@*****.**'), (u'Somebody', u'*****@*****.**'),) new_msg.thread_id = 1 new_msg.size = 22 new_msg.is_draft = False new_msg.decode_error = False new_msg.sanitized_body = 'Are you there?' new_msg.snippet = 'Are you there?' new_msg.received_date = received_date return new_msg
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
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='', add_sent_category=False): from inbox.models import Message, Category from inbox.contacts.process_mail import update_contacts_from_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 if thread: thread.messages.append(m) update_contacts_from_message(db_session, m, thread.namespace) 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
def message(): received_date = datetime.datetime.utcfromtimestamp(10**9 + 1) new_msg = Message() new_msg.from_addr = (('Some Dude', '*****@*****.**'),) new_msg.to_addr = (('Somebody Else', '*****@*****.**'),) new_msg.cc_addr = (('A Bystander', '*****@*****.**'),) new_msg.bcc_addr = (('The NSA', '*****@*****.**'),) new_msg.thread_id = 1 new_msg.size = 22 new_msg.is_draft = False new_msg.decode_error = False new_msg.sanitized_body = 'Are you there?' new_msg.snippet = 'Are you there?' new_msg.received_date = received_date return new_msg
def add_fake_message(db_session, thread, from_addr=None, to_addr=None, cc_addr=None, bcc_addr=None): m = Message() 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 = datetime.utcnow() m.size = 0 m.sanitized_body = '' m.snippet = '' m.thread = thread account_id = thread.namespace.account_id update_contacts_from_message(db_session, m, account_id) db_session.add(m) db_session.commit() return m
def add_fake_message(db_session, thread, from_addr=None, to_addr=None, cc_addr=None, bcc_addr=None): 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 = datetime.utcnow() m.size = 0 m.sanitized_body = '' m.snippet = '' m.thread = thread update_contacts_from_message(db_session, m, thread.namespace) db_session.add(m) db_session.commit() return m
def add_fake_message(db_session, namespace_id, thread, from_addr=None, to_addr=None, cc_addr=None, bcc_addr=None, received_date=None, subject=None): from inbox.models import Message from inbox.contacts.process_mail import update_contacts_from_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.sanitized_body = '' m.snippet = '' m.subject = subject or '' m.thread = thread update_contacts_from_message(db_session, m, thread.namespace) db_session.add(m) db_session.commit() return m
def test_threading_limit(db, folder_sync_engine, monkeypatch): """Test that custom threading doesn't produce arbitrarily long threads, which eventually break things.""" from inbox.models import Message, Thread, Account # Shorten bound to make test faster MAX_THREAD_LENGTH = 10 monkeypatch.setattr( 'inbox.mailsync.backends.imap.generic.MAX_THREAD_LENGTH', MAX_THREAD_LENGTH) namespace_id = folder_sync_engine.namespace_id account = db.session.query(Account).get(folder_sync_engine.account_id) account.namespace.create_canonical_tags() account.inbox_folder = Folder(account=account, name='Inbox', canonical_name='inbox') folder = account.inbox_folder msg = MockRawMessage([]) for i in range(3 * MAX_THREAD_LENGTH): m = Message() m.namespace_id = namespace_id m.received_date = datetime.datetime.utcnow() m.references = [] m.size = 0 m.sanitized_body = '' m.from_addr = [("Karim Hamidou", "*****@*****.**")] m.to_addr = [("Eben Freeman", "*****@*****.**")] m.snippet = '' m.subject = 'unique subject' uid = ImapUid(message=m, account=account, msg_uid=2222 + i, folder=folder) folder_sync_engine.add_message_attrs(db.session, uid, msg) db.session.add(m) db.session.commit() new_threads = db.session.query(Thread). \ filter(Thread.subject == 'unique subject').all() assert len(new_threads) == 3 assert all(len(thread.messages) == MAX_THREAD_LENGTH for thread in new_threads)
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: if 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_inbox_uid() # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) update_contacts_from_message(db_session, message, namespace) 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
def create_and_save_draft(db_session, account, to_addr=None, subject=None, body=None, blocks=None, cc_addr=None, bcc_addr=None, new_tags=None, thread=None, is_reply=False, syncback=True): """Create a draft object and commit it to the database.""" with db_session.no_autoflush: dt = datetime.utcnow() uid = generate_public_id() version = generate_public_id() to_addr = to_addr or [] cc_addr = cc_addr or [] bcc_addr = bcc_addr or [] blocks = blocks or [] body = body or '' if subject is None and thread is not None: # Set subject from thread by default. subject = thread.subject subject = subject or '' message = Message() message.namespace = account.namespace message.is_created = True message.is_draft = True message.state = 'draft' message.from_addr = [(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.sanitized_body = body message.to_addr = to_addr message.cc_addr = cc_addr message.bcc_addr = bcc_addr # 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.is_reply = is_reply message.public_id = uid message.version = version message.inbox_uid = version # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = account.namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) db_session.add(part) update_contacts_from_message(db_session, message, account.namespace) if is_reply: message.is_reply = True # Construct the in-reply-to and references headers from the last # message currently in the thread. _set_reply_headers(message, thread) if thread is None: # 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. thread_cls = account.thread_cls thread = thread_cls(subject=message.subject, recentdate=message.received_date, namespace=account.namespace, subjectdate=message.received_date) db_session.add(thread) message.thread = thread thread.apply_tag(account.namespace.tags['drafts']) if new_tags: tags_to_keep = {tag for tag in thread.tags if not tag.user_created} thread.tags = new_tags | tags_to_keep if syncback: schedule_action('save_draft', message, message.namespace.id, db_session) db_session.add(message) db_session.commit() return message
def create_draft(data, namespace, db_session, syncback): """ Construct a draft object (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: if 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 = True 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_inbox_uid() # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) update_contacts_from_message(db_session, message, namespace) 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 syncback: schedule_action('save_draft', message, namespace.id, db_session, version=message.version) db_session.flush() return message
def create_and_save_draft(db_session, account, to_addr=None, subject=None, body=None, blocks=None, cc_addr=None, bcc_addr=None, new_tags=None, thread=None, is_reply=False, syncback=True): """Create a draft object and commit it to the database.""" with db_session.no_autoflush: dt = datetime.utcnow() uid = generate_public_id() version = generate_public_id() to_addr = to_addr or [] cc_addr = cc_addr or [] bcc_addr = bcc_addr or [] blocks = blocks or [] body = body or '' if subject is None and thread is not None: # Set subject from thread by default. subject = thread.subject subject = subject or '' message = Message() message.namespace = account.namespace message.is_created = True message.is_draft = True message.state = 'draft' message.from_addr = [(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.sanitized_body = body message.to_addr = to_addr message.cc_addr = cc_addr message.bcc_addr = bcc_addr # 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.is_reply = is_reply message.public_id = uid message.version = version message.inbox_uid = version # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = account.namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) db_session.add(part) update_contacts_from_message(db_session, message, account.namespace) if is_reply: message.is_reply = True # Construct the in-reply-to and references headers from the last # message currently in the thread. _set_reply_headers(message, thread) if thread is None: # 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. thread_cls = account.thread_cls thread = thread_cls( subject=message.subject, recentdate=message.received_date, namespace=account.namespace, subjectdate=message.received_date) db_session.add(thread) message.thread = thread thread.apply_tag(account.namespace.tags['drafts']) if new_tags: tags_to_keep = {tag for tag in thread.tags if not tag.user_created} thread.tags = new_tags | tags_to_keep if syncback: schedule_action('save_draft', message, message.namespace.id, db_session) db_session.add(message) db_session.commit() return message