예제 #1
0
 def __init__(self, uid=None, public_id=None, **kwargs):
     if not uid and not public_id:
         self.public_id = self.uid = generate_public_id()
     elif not uid:
         self.uid = generate_public_id()
     for key, value in kwargs.items():
         setattr(self, key, value)
예제 #2
0
def add_inbox_headers(msg, inbox_uid):
    """
    Set a custom `X-INBOX-ID` header so as to identify messages generated by
    Inbox.

    The header is set to a unique id generated randomly per message,
    and is needed for the correct reconciliation of sent messages on
    future syncs.

    Notes
    -----
    We generate the UUID as a base-36 encoded string, and is the same as the
    public_id of the message object.

    """

    our_uid = inbox_uid if inbox_uid else \
        generate_public_id()  # base-36 encoded string

    # Set our own custom header for tracking in `Sent Mail` folder
    msg.headers['X-INBOX-ID'] = our_uid
    msg.headers['Message-Id'] = '<{}@mailer.nylas.com>'.format(our_uid)

    # Potentially also use `X-Mailer`
    msg.headers['User-Agent'] = 'NylasMailer/{0}'.format(VERSION)
예제 #3
0
파일: test_events.py 프로젝트: dlitz/inbox
def test_api_update_invalid(db, api_client):
    acct = db.session.query(Account).filter_by(id=ACCOUNT_ID).one()
    ns_id = acct.namespace.public_id
    e_update_data = {'title': 'new title'}
    e_id = generate_public_id()
    e_put_resp = api_client.put_data('/events/' + e_id, e_update_data, ns_id)
    assert e_put_resp.status_code != 200
예제 #4
0
def test_api_update_invalid(db, api_client):
    acct = db.session.query(Account).filter_by(id=ACCOUNT_ID).one()
    ns_id = acct.namespace.public_id
    e_update_data = {'title': 'new title'}
    e_id = generate_public_id()
    e_put_resp = api_client.put_data('/events/' + e_id, e_update_data, ns_id)
    assert e_put_resp.status_code != 200
예제 #5
0
def test_api_delete_invalid(db, api_client, calendar):
    e_id = 'asdf'
    resp = api_client.delete('/events/' + e_id)
    assert resp.status_code != 200

    e_id = generate_public_id()
    resp = api_client.delete('/events/' + e_id)
    assert resp.status_code != 200
예제 #6
0
def test_api_delete_invalid(db, api_client, calendar):
    e_id = 'asdf'
    resp = api_client.delete('/events/' + e_id)
    assert resp.status_code != 200

    e_id = generate_public_id()
    resp = api_client.delete('/events/' + e_id)
    assert resp.status_code != 200
예제 #7
0
 def default_calendar(self):
     if not self._default_calendar:
         public_id = generate_public_id()
         new_cal = Calendar()
         new_cal.public_id = public_id
         new_cal.account = self
         new_cal.uid = public_id
         new_cal.read_only = False
         self._default_calendar = new_cal
     return self._default_calendar
예제 #8
0
파일: account.py 프로젝트: rbs-pli/inbox
 def default_calendar(self):
     if not self._default_calendar:
         public_id = generate_public_id()
         new_cal = Calendar()
         new_cal.public_id = public_id
         new_cal.account = self
         new_cal.uid = public_id
         new_cal.read_only = False
         self._default_calendar = new_cal
     return self._default_calendar
예제 #9
0
def upgrade():
    from inbox.sqlalchemy_ext.util import generate_public_id
    from inbox.models.session import session_scope

    # These all inherit HasPublicID
    from inbox.models import (
        Account, Block, Contact, Message, Namespace,
        SharedFolder, Thread, User, UserSession, HasPublicID)

    classes = [
        Account, Block, Contact, Message, Namespace,
        SharedFolder, Thread, User, UserSession]

    for c in classes:
        assert issubclass(c, HasPublicID)
        print '[{0}] adding public_id column... '.format(c.__tablename__),
        sys.stdout.flush()
        op.add_column(c.__tablename__, sa.Column(
            'public_id', mysql.BINARY(16), nullable=False))

        print 'adding index... ',
        op.create_index(
            'ix_{0}_public_id'.format(c.__tablename__),
            c.__tablename__,
            ['public_id'],
            unique=False)

        print 'Done!'
        sys.stdout.flush()

    print 'Finished adding columns. \nNow generating public_ids'

    with session_scope() as db_session:
        count = 0
        for c in classes:
            garbage_collect()
            print '[{0}] Loading rows. '.format(c.__name__),
            sys.stdout.flush()
            print 'Generating public_ids',
            sys.stdout.flush()
            for r in db_session.query(c).yield_per(chunk_size):
                count += 1
                r.public_id = generate_public_id()
                if not count % chunk_size:
                    sys.stdout.write('.')
                    sys.stdout.flush()
                    db_session.commit()
                    garbage_collect()
            sys.stdout.write(' Saving. '.format(c.__name__)),
            # sys.stdout.flush()
            sys.stdout.flush()
            db_session.commit()
            sys.stdout.write('Done!\n')
            sys.stdout.flush()
        print '\nUpdgraded OK!\n'
예제 #10
0
def test_api_delete_invalid(db, api_client):
    acct = db.session.query(Account).filter_by(id=ACCOUNT_ID).one()
    ns_id = acct.namespace.public_id

    e_id = 'asdf'
    resp = api_client.delete('/events/' + e_id, ns_id=ns_id)
    assert resp.status_code != 200

    e_id = generate_public_id()
    resp = api_client.delete('/events/' + e_id, ns_id=ns_id)
    assert resp.status_code != 200
예제 #11
0
파일: test_events.py 프로젝트: dlitz/inbox
def test_api_delete_invalid(db, api_client):
    acct = db.session.query(Account).filter_by(id=ACCOUNT_ID).one()
    ns_id = acct.namespace.public_id

    e_id = 'asdf'
    resp = api_client.delete('/events/' + e_id, ns_id=ns_id)
    assert resp.status_code != 200

    e_id = generate_public_id()
    resp = api_client.delete('/events/' + e_id, ns_id=ns_id)
    assert resp.status_code != 200
예제 #12
0
def test_namespace_id_validation(api_client, db, default_namespace):
    actual_namespace_id, = db.session.query(Namespace.public_id).first()
    r = api_client.client.get('/n/{}'.format(actual_namespace_id))
    assert r.status_code == 200

    fake_namespace_id = generate_public_id()
    r = api_client.client.get('/n/{}'.format(fake_namespace_id))
    assert r.status_code == 404

    malformed_namespace_id = 'this string is definitely not base36-decodable'
    r = api_client.client.get('/n/{}'.format(malformed_namespace_id))
    assert r.status_code == 400
예제 #13
0
 def default_calendar(self):
     if not self._default_calendar:
         public_id = generate_public_id()
         new_cal = Calendar()
         new_cal.public_id = public_id
         new_cal.namespace = self.namespace
         new_cal.uid = public_id
         new_cal.read_only = False
         new_cal.name = 'default'
         new_cal.provider_name = 'inbox'
         self._default_calendar = new_cal
     return self._default_calendar
예제 #14
0
파일: account.py 프로젝트: apolmig/inbox
 def default_calendar(self):
     if not self._default_calendar:
         public_id = generate_public_id()
         new_cal = Calendar()
         new_cal.public_id = public_id
         new_cal.namespace = self.namespace
         new_cal.uid = public_id
         new_cal.read_only = False
         new_cal.name = 'default'
         new_cal.provider_name = 'inbox'
         self._default_calendar = new_cal
     return self._default_calendar
def test_namespace_id_validation(api_client, db, default_namespace):
    actual_namespace_id, = db.session.query(Namespace.public_id).first()
    r = api_client.client.get('/n/{}'.format(actual_namespace_id))
    assert r.status_code == 200

    fake_namespace_id = generate_public_id()
    r = api_client.client.get('/n/{}'.format(fake_namespace_id))
    assert r.status_code == 404

    malformed_namespace_id = 'this string is definitely not base36-decodable'
    r = api_client.client.get('/n/{}'.format(malformed_namespace_id))
    assert r.status_code == 400
def upgrade():
    op.add_column("transaction",
                  sa.Column("public_id", mysql.BINARY(16), nullable=True))
    op.add_column(
        "transaction",
        sa.Column("object_public_id", sa.String(length=191), nullable=True),
    )
    op.create_index("ix_transaction_public_id",
                    "transaction", ["public_id"],
                    unique=False)

    # TODO(emfree) reflect
    from inbox.ignition import main_engine
    from inbox.models.session import session_scope
    from inbox.sqlalchemy_ext.util import b36_to_bin, generate_public_id

    engine = main_engine(pool_size=1, max_overflow=0)
    Base = declarative_base()
    Base.metadata.reflect(engine)

    class Transaction(Base):
        __table__ = Base.metadata.tables["transaction"]

    with session_scope(versioned=False) as db_session:
        count = 0
        (num_transactions, ) = db_session.query(sa.func.max(
            Transaction.id)).one()
        print("Adding public ids to {} transactions".format(num_transactions))
        for pointer in range(0, num_transactions + 1, 500):
            for entry in db_session.query(Transaction).filter(
                    Transaction.id >= pointer, Transaction.id < pointer + 500):
                entry.public_id = b36_to_bin(generate_public_id())
                count += 1
                if not count % 500:
                    sys.stdout.write(".")
                    sys.stdout.flush()
                    db_session.commit()
                    garbage_collect()

    op.alter_column("transaction",
                    "public_id",
                    existing_type=mysql.BINARY(16),
                    nullable=False)

    op.add_column(
        "transaction",
        sa.Column("public_snapshot", sa.Text(length=4194304), nullable=True),
    )
    op.add_column(
        "transaction",
        sa.Column("private_snapshot", sa.Text(length=4194304), nullable=True),
    )
    op.drop_column("transaction", u"additional_data")
def upgrade():
    op.add_column('transaction',
                  sa.Column('public_id', mysql.BINARY(16), nullable=True))
    op.add_column(
        'transaction',
        sa.Column('object_public_id', sa.String(length=191), nullable=True))
    op.create_index('ix_transaction_public_id',
                    'transaction', ['public_id'],
                    unique=False)

    from inbox.sqlalchemy_ext.util import generate_public_id, b36_to_bin
    # TODO(emfree) reflect
    from inbox.models.session import session_scope
    from inbox.ignition import main_engine
    engine = main_engine(pool_size=1, max_overflow=0)
    Base = declarative_base()
    Base.metadata.reflect(engine)

    class Transaction(Base):
        __table__ = Base.metadata.tables['transaction']

    with session_scope(versioned=False,
                       ignore_soft_deletes=False) as db_session:
        count = 0
        num_transactions, = db_session.query(sa.func.max(Transaction.id)).one()
        print 'Adding public ids to {} transactions'.format(num_transactions)
        for pointer in range(0, num_transactions + 1, 500):
            for entry in db_session.query(Transaction).filter(
                    Transaction.id >= pointer, Transaction.id < pointer + 500):
                entry.public_id = b36_to_bin(generate_public_id())
                count += 1
                if not count % 500:
                    sys.stdout.write('.')
                    sys.stdout.flush()
                    db_session.commit()
                    garbage_collect()

    op.alter_column('transaction',
                    'public_id',
                    existing_type=mysql.BINARY(16),
                    nullable=False)

    op.add_column(
        'transaction',
        sa.Column('public_snapshot', sa.Text(length=4194304), nullable=True))
    op.add_column(
        'transaction',
        sa.Column('private_snapshot', sa.Text(length=4194304), nullable=True))
    op.drop_column('transaction', u'additional_data')
def upgrade():
    op.add_column('transaction',
                  sa.Column('public_id', mysql.BINARY(16), nullable=True))
    op.add_column('transaction',
                  sa.Column('object_public_id', sa.String(length=191),
                            nullable=True))
    op.create_index('ix_transaction_public_id', 'transaction', ['public_id'],
                    unique=False)

    from inbox.sqlalchemy_ext.util import generate_public_id, b36_to_bin
    # TODO(emfree) reflect
    from inbox.models.session import session_scope
    from inbox.ignition import main_engine
    engine = main_engine(pool_size=1, max_overflow=0)
    Base = declarative_base()
    Base.metadata.reflect(engine)

    class Transaction(Base):
        __table__ = Base.metadata.tables['transaction']

    with session_scope(versioned=False) as db_session:
        count = 0
        num_transactions, = db_session.query(sa.func.max(Transaction.id)).one()
        print 'Adding public ids to {} transactions'.format(num_transactions)
        for pointer in range(0, num_transactions + 1, 500):
            for entry in db_session.query(Transaction).filter(
                    Transaction.id >= pointer,
                    Transaction.id < pointer + 500):
                entry.public_id = b36_to_bin(generate_public_id())
                count += 1
                if not count % 500:
                    sys.stdout.write('.')
                    sys.stdout.flush()
                    db_session.commit()
                    garbage_collect()

    op.alter_column('transaction', 'public_id',
                    existing_type=mysql.BINARY(16), nullable=False)

    op.add_column('transaction', sa.Column('public_snapshot',
                                           sa.Text(length=4194304),
                                           nullable=True))
    op.add_column('transaction', sa.Column('private_snapshot',
                                           sa.Text(length=4194304),
                                           nullable=True))
    op.drop_column('transaction', u'additional_data')
예제 #19
0
def create_draft_from_mime(account, raw_mime, db_session):
    our_uid = generate_public_id()  # base-36 encoded string
    new_headers = ('X-INBOX-ID: {0}-0\r\n'
                   'Message-Id: <{0}[email protected]>\r\n'
                   'User-Agent: NylasMailer/{1}\r\n').format(our_uid, VERSION)
    new_body = new_headers + raw_mime

    with db_session.no_autoflush:
        msg = Message.create_from_synced(account, '', '',
                                         datetime.utcnow(), new_body)

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

        if msg.subject is not None and not \
                                        isinstance(msg.subject, basestring):
            raise InputError('"subject" should be a string')

        if not isinstance(msg.body, basestring):
            raise InputError('"body" should be a string')

        if msg.references or msg.in_reply_to:
            msg.is_reply = True

        thread_cls = account.thread_cls
        msg.thread = thread_cls(
            subject=msg.subject,
            recentdate=msg.received_date,
            namespace=account.namespace,
            subjectdate=msg.received_date)
        if msg.attachments:
                attachment_tag = account.namespace.tags['attachment']
                msg.thread.apply_tag(attachment_tag)

        msg.is_created = True
        msg.is_sent = True
        msg.is_draft = False
        msg.is_read = True
    db_session.add(msg)
    db_session.flush()
    return msg
예제 #20
0
def create_draft_from_mime(account, raw_mime, db_session):
    our_uid = generate_public_id()  # base-36 encoded string
    new_headers = ('X-INBOX-ID: {0}-0\r\n'
                   'Message-Id: <{0}[email protected]>\r\n'
                   'User-Agent: NylasMailer/{1}\r\n').format(our_uid, VERSION)
    new_body = new_headers + raw_mime

    with db_session.no_autoflush:
        msg = Message.create_from_synced(account, '', '', datetime.utcnow(),
                                         new_body)

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

        if msg.subject is not None and not \
                                        isinstance(msg.subject, basestring):
            raise InputError('"subject" should be a string')

        if not isinstance(msg.body, basestring):
            raise InputError('"body" should be a string')

        if msg.references or msg.in_reply_to:
            msg.is_reply = True

        thread_cls = account.thread_cls
        msg.thread = thread_cls(subject=msg.subject,
                                recentdate=msg.received_date,
                                namespace=account.namespace,
                                subjectdate=msg.received_date)
        if msg.attachments:
            attachment_tag = account.namespace.tags['attachment']
            msg.thread.apply_tag(attachment_tag)

        msg.is_created = True
        msg.is_sent = True
        msg.is_draft = False
        msg.is_read = True
    db_session.add(msg)
    db_session.flush()
    return msg
예제 #21
0
파일: message.py 프로젝트: olofster/inbox
def add_inbox_headers(msg, inbox_uid):
    """
    Set a custom `X-INBOX-ID` header so as to identify messages generated by
    Inbox.

    The header is set to a unique id generated randomly per message,
    and is needed for the correct reconciliation of sent messages on
    future syncs.

    Notes
    -----
    We generate the UUID as a base-36 encoded string, and is the same as the
    public_id of the message object.

    """
    # Set our own custom header for tracking in `Sent Mail` folder
    msg.headers['X-INBOX-ID'] = inbox_uid if inbox_uid else \
        generate_public_id()  # base-36 encoded string

    # Potentially also use `X-Mailer`
    msg.headers['User-Agent'] = 'Inbox/{0}'.format(VERSION)
예제 #22
0
파일: base.py 프로젝트: bsorin/inbox
def update_draft(db_session, account, original_draft, to_addr=None,
                 subject=None, body=None, blocks=None, cc_addr=None,
                 bcc_addr=None, tags=None):
    """
    Update draft.

    To maintain our messages are immutable invariant, we create a new draft
    message object.


    Returns
    -------
    Message
        The new draft message object.

    Notes
    -----
    Messages, including draft messages, are immutable in Inbox.
    So to update a draft, we create a new draft message object and
    return its public_id (which is different than the original's).

    """

    def update(attr, value=None):
        if value is not None:
            setattr(original_draft, attr, value)

            if attr == 'sanitized_body':
                # Update size, snippet too
                original_draft.size = len(value)
                original_draft.snippet = original_draft.calculate_html_snippet(
                    value)

    update('to_addr', to_addr)
    update('cc_addr', cc_addr)
    update('bcc_addr', bcc_addr)
    update('subject', subject if subject else None)
    update('sanitized_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 filter(lambda x: x.block_id not in new_block_ids,
                       original_draft.parts):
        original_draft.parts.remove(part)
        db_session.delete(part)

    # Parts, tags 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 original_draft.parts]:
            continue
        part = Part(block=block)
        part.namespace_id = account.namespace.id
        part.content_disposition = 'attachment'
        part.is_inboxapp_attachment = True
        original_draft.parts.append(part)

        db_session.add(part)

    thread = original_draft.thread
    if tags:
        tags_to_keep = {tag for tag in thread.tags if not tag.user_created}
        thread.tags = tags | tags_to_keep

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

    # Delete previous version on remote
    schedule_action('delete_draft', original_draft,
                    original_draft.namespace.id, db_session,
                    inbox_uid=original_draft.inbox_uid,
                    message_id_header=original_draft.message_id_header)

    # Update version  + inbox_uid (is_created is already set)
    version = generate_public_id()
    update('version', version)
    update('inbox_uid', version)

    # Sync to remote
    schedule_action('save_draft', original_draft, original_draft.namespace.id,
                    db_session)

    db_session.commit()

    return original_draft
예제 #23
0
def test_api_update_invalid(db, api_client, calendar):
    e_update_data = {'title': 'new title'}
    e_id = generate_public_id()
    e_put_resp = api_client.put_data('/events/' + e_id, e_update_data)
    assert e_put_resp.status_code != 200
예제 #24
0
파일: base.py 프로젝트: raoulwissink/inbox
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,
    parent_draft=None,
):
    """
    Create a draft object and commit it to the database.
    """
    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 []
    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 = SpoolMessage()
    message.from_addr = [(account.sender_name, account.email_address)]
    message.created_date = dt
    # 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.is_sent = False
    message.state = "draft"
    if parent_draft is not None:
        message.parent_draft_id = parent_draft.id
    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_draft = True
    message.is_read = True
    message.inbox_uid = uid
    message.public_id = uid

    # Set the 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()
        part.namespace_id = account.namespace.id
        part.content_disposition = "attachment"
        part.content_type = block.content_type
        part.is_inboxapp_attachment = True
        part.data = block.data
        message.parts.append(part)
        db_session.add(part)

    # TODO(emfree) Update contact data here.

    if is_reply:
        message.is_reply = True
        # If we're updating a draft, copy the in-reply-to and references
        # headers from the parent. Otherwise, construct them from the last
        # message currently in the thread.
        if parent_draft is not None:
            message.in_reply_to = parent_draft.in_reply_to
            message.references = parent_draft.references
        else:
            # Make sure that the headers are constructed from an actual
            # previous message on the thread, not another draft
            non_draft_messages = [m for m in thread.messages if not m.is_draft]
            if non_draft_messages:
                last_message = non_draft_messages[-1]
                message.in_reply_to = last_message.message_id_header
                message.references = last_message.references + "\t" + last_message.message_id_header
    if thread is None:
        # Create a new thread object for the draft.
        thread = Thread(
            subject=message.subject,
            recentdate=message.received_date,
            namespace=account.namespace,
            subjectdate=message.received_date,
        )
        db_session.add(thread)

    message.thread = thread
    # This triggers an autoflush, so we need to execute it after setting
    # message.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

    schedule_action("save_draft", message, message.namespace.id, db_session)

    db_session.add(message)
    db_session.commit()
    return message
예제 #25
0
파일: base.py 프로젝트: nixon1333/inbox
def update_draft(db_session, account, original_draft, to=None, subject=None,
                 body=None, blocks=None, cc=None, bcc=None, tags=None):
    """
    Update draft.

    To maintain our messages are immutable invariant, we create a new draft
    message object.


    Returns
    -------
    Message
        The new draft message object.

    Notes
    -----
    Messages, including draft messages, are immutable in Inbox.
    So to update a draft, we create a new draft message object and
    return its public_id (which is different than the original's).

    """

    def update(attr, value=None):
        if value is not None:
            setattr(original_draft, attr, value)

            if attr == 'sanitized_body':
                # Update size, snippet too
                original_draft.size = len(value)
                original_draft.calculate_html_snippet(value)

    update('to_addr', _parse_recipients(to) if to else None)
    update('cc_addr', _parse_recipients(cc) if cc else None)
    update('bcc_addr', _parse_recipients(bcc) if bcc else None)
    update('subject', subject if subject else None)
    update('sanitized_body', body if body else None)
    update('received_date', datetime.utcnow())

    # Parts, tags require special handling
    for block in blocks:
        part = Part()
        part.namespace_id = account.namespace.id
        part.content_disposition = 'attachment'
        part.content_type = block.content_type
        part.is_inboxapp_attachment = True
        part.data = block.data
        part.filename = block.filename
        original_draft.parts.append(part)

        db_session.add(part)

    thread = original_draft.thread
    if tags:
        tags_to_keep = {tag for tag in thread.tags if not tag.user_created}
        thread.tags = tags | tags_to_keep

    # Delete previous version on remote
    schedule_action('delete_draft', original_draft,
                    original_draft.namespace.id, db_session,
                    inbox_uid=original_draft.inbox_uid)

    # Update version  + inbox_uid, sync to remote
    version = generate_public_id()
    update('version', version)
    update('inbox_uid', version)

    schedule_action('save_draft', original_draft, original_draft.namespace.id,
                    db_session)

    db_session.commit()

    return original_draft
예제 #26
0
def test_api_update_invalid(db, api_client, calendar):
    e_update_data = {'title': 'new title'}
    e_id = generate_public_id()
    e_put_resp = api_client.put_data('/events/' + e_id, e_update_data)
    assert e_put_resp.status_code != 200
예제 #27
0
def update_draft(db_session,
                 account,
                 original_draft,
                 to_addr=None,
                 subject=None,
                 body=None,
                 blocks=None,
                 cc_addr=None,
                 bcc_addr=None,
                 tags=None):
    """
    Update draft.

    To maintain our messages are immutable invariant, we create a new draft
    message object.


    Returns
    -------
    Message
        The new draft message object.

    Notes
    -----
    Messages, including draft messages, are immutable in Inbox.
    So to update a draft, we create a new draft message object and
    return its public_id (which is different than the original's).

    """
    def update(attr, value=None):
        if value is not None:
            setattr(original_draft, attr, value)

            if attr == 'sanitized_body':
                # Update size, snippet too
                original_draft.size = len(value)
                original_draft.snippet = original_draft.calculate_html_snippet(
                    value)

    update('to_addr', to_addr)
    update('cc_addr', cc_addr)
    update('bcc_addr', bcc_addr)
    update('subject', subject if subject else None)
    update('sanitized_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 filter(lambda x: x.block_id not in new_block_ids,
                       original_draft.parts):
        original_draft.parts.remove(part)
        db_session.delete(part)

    # Parts, tags 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 original_draft.parts]:
            continue
        part = Part(block=block)
        part.namespace_id = account.namespace.id
        part.content_disposition = 'attachment'
        part.is_inboxapp_attachment = True
        original_draft.parts.append(part)

        db_session.add(part)

    thread = original_draft.thread
    if tags:
        tags_to_keep = {tag for tag in thread.tags if not tag.user_created}
        thread.tags = tags | tags_to_keep

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

    # Delete previous version on remote
    schedule_action('delete_draft',
                    original_draft,
                    original_draft.namespace.id,
                    db_session,
                    inbox_uid=original_draft.inbox_uid,
                    message_id_header=original_draft.message_id_header)

    # Update version  + inbox_uid (is_created is already set)
    version = generate_public_id()
    update('version', version)
    update('inbox_uid', version)

    # Sync to remote
    schedule_action('save_draft', original_draft, original_draft.namespace.id,
                    db_session)

    db_session.commit()

    return original_draft
예제 #28
0
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
예제 #29
0
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,
                          parent_draft=None,
                          syncback=True):
    """
    Create a draft object and commit it to the database.
    """
    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 []
    body = body or ''
    if subject is None and thread is not None:
        # Set subject from thread by default.
        subject = thread.subject
    subject = subject or ''

    # Sets is_draft = True, state = 'draft'
    message = Message.create_draft_message()

    message.from_addr = [(account.sender_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
    if parent_draft is not None:
        message.parent_draft_id = parent_draft.id
    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.inbox_uid = uid
    message.public_id = uid

    # Set the 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()
        part.namespace_id = account.namespace.id
        part.content_disposition = 'attachment'
        part.content_type = block.content_type
        part.is_inboxapp_attachment = True
        part.data = block.data
        part.filename = block.filename
        message.parts.append(part)
        db_session.add(part)

    # TODO(emfree) Update contact data here.

    if is_reply:
        message.is_reply = True
        # If we're updating a draft, copy the in-reply-to and references
        # headers from the parent. Otherwise, construct them from the last
        # message currently in the thread.
        if parent_draft is not None:
            message.in_reply_to = parent_draft.in_reply_to
            message.references = parent_draft.references
        else:
            _set_reply_headers(message, thread)
    if thread is None:
        # Create a new thread object for the draft.
        thread = Thread(subject=message.subject,
                        recentdate=message.received_date,
                        namespace=account.namespace,
                        subjectdate=message.received_date)
        db_session.add(thread)

    message.thread = thread
    # This triggers an autoflush, so we need to execute it after setting
    # message.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
예제 #30
0
파일: base.py 프로젝트: toke/sync-engine
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
예제 #31
0
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
예제 #32
0
def upgrade():
    # These all inherit HasPublicID
    from inbox.models import (
        Account,
        Block,
        Contact,
        HasPublicID,
        Message,
        Namespace,
        SharedFolder,
        Thread,
        User,
        UserSession,
    )
    from inbox.models.session import session_scope
    from inbox.sqlalchemy_ext.util import generate_public_id

    classes = [
        Account,
        Block,
        Contact,
        Message,
        Namespace,
        SharedFolder,
        Thread,
        User,
        UserSession,
    ]

    for c in classes:
        assert issubclass(c, HasPublicID)
        print "[{0}] adding public_id column... ".format(c.__tablename__),
        sys.stdout.flush()
        op.add_column(c.__tablename__,
                      sa.Column("public_id", mysql.BINARY(16), nullable=False))

        print "adding index... ",
        op.create_index(
            "ix_{0}_public_id".format(c.__tablename__),
            c.__tablename__,
            ["public_id"],
            unique=False,
        )

        print "Done!"
        sys.stdout.flush()

    print "Finished adding columns. \nNow generating public_ids"

    with session_scope() as db_session:
        count = 0
        for c in classes:
            garbage_collect()
            print "[{0}] Loading rows. ".format(c.__name__),
            sys.stdout.flush()
            print "Generating public_ids",
            sys.stdout.flush()
            for r in db_session.query(c).yield_per(chunk_size):
                count += 1
                r.public_id = generate_public_id()
                if not count % chunk_size:
                    sys.stdout.write(".")
                    sys.stdout.flush()
                    db_session.commit()
                    garbage_collect()
            sys.stdout.write(" Saving. ".format(c.__name__)),
            # sys.stdout.flush()
            sys.stdout.flush()
            db_session.commit()
            sys.stdout.write("Done!\n")
            sys.stdout.flush()
        print "\nUpdgraded OK!\n"
예제 #33
0
파일: base.py 프로젝트: nixon1333/inbox
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."""
    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 ''

    # Sets is_draft = True, state = 'draft'
    message = Message.create_draft_message()

    message.from_addr = [(account.sender_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.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()
        part.namespace_id = account.namespace.id
        part.content_disposition = 'attachment'
        part.content_type = block.content_type
        part.is_inboxapp_attachment = True
        part.data = block.data
        part.filename = block.filename
        message.parts.append(part)
        db_session.add(part)

    # TODO(emfree) Update contact data here.

    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.
        thread = Thread(
            subject=message.subject,
            recentdate=message.received_date,
            namespace=account.namespace,
            subjectdate=message.received_date)
        db_session.add(thread)

    message.thread = thread
    # This triggers an autoflush, so we need to execute it after setting
    # message.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