├── .arcconfig ├── .gitattributes ├── .gitignore ├── .lgtm ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── PULL_REQUEST_TEMPLATE ├── README.md ├── Vagrantfile ├── Vagrantfile.local.example ├── alembic.ini ├── arclib ├── __phutil_library_init__.php ├── __phutil_library_map__.php └── src │ ├── InboxServerLintEngine.php │ ├── MGArcanistPyLintLinter.php │ └── PytestTestEngine.php ├── bin ├── __init__.py ├── backfix-duplicate-categories.py ├── backfix-generic-imap-separators.py ├── balance-fleet ├── check-attachments ├── clear-all-heartbeats ├── clear-db ├── clear-heartbeat-status ├── clear-kv ├── contact-search-backfill ├── contact-search-delete-index ├── contact-search-service ├── correct-autoincrements ├── create-db ├── create-encryption-keys ├── create-test-db ├── deferred-migration-service ├── delete-account-data ├── delete-marked-accounts ├── detect-missing-sync-host ├── dump-databases ├── full_coverage.sh ├── get-account-loads ├── get-accounts-for-host ├── get-id ├── get-object ├── inbox-api ├── inbox-auth ├── inbox-console ├── inbox-start ├── load-dump ├── migrate-db ├── mysql-prompt ├── populate-sync-queue ├── purge-transaction-log ├── restart-forgotten-accounts ├── set-desired-host ├── set-throttled ├── stamp-db ├── syncback-service ├── syncback-stats ├── unschedule-account-syncs ├── update-categories └── verify-db ├── debian ├── changelog ├── compat ├── control ├── inbox-sync.triggers └── rules ├── etc ├── config-dev.json ├── config-test-prod.json ├── config-test.json ├── db-config-test-phab.json ├── db-config-test-prod.json ├── db-config-test.json ├── my.cnf ├── mysql_ec2.conf ├── secrets-dev.yml └── secrets-test.yml ├── inbox ├── __init__.py ├── actions │ ├── __init__.py │ ├── backends │ │ ├── __init__.py │ │ ├── generic.py │ │ └── gmail.py │ └── base.py ├── api │ ├── __init__.py │ ├── err.py │ ├── filtering.py │ ├── kellogs.py │ ├── mime_types.txt │ ├── ns_api.py │ ├── sending.py │ ├── srv.py │ ├── update.py │ ├── validation.py │ └── wsgi.py ├── auth │ ├── __init__.py │ ├── base.py │ ├── generic.py │ ├── gmail.py │ └── oauth.py ├── basicauth.py ├── config.py ├── console.py ├── contacts │ ├── __init__.py │ ├── algorithms.py │ ├── carddav.py │ ├── crud.py │ ├── google.py │ ├── icloud.py │ ├── process_mail.py │ ├── remote_sync.py │ ├── search.py │ └── vcard.py ├── crispin.py ├── events │ ├── __init__.py │ ├── actions │ │ ├── __init__.py │ │ ├── backends │ │ │ ├── __init__.py │ │ │ └── gmail.py │ │ └── base.py │ ├── google.py │ ├── ical.py │ ├── recurring.py │ ├── remote_sync.py │ ├── timezones.py │ └── util.py ├── folder_edge_cases.py ├── heartbeat │ ├── __init__.py │ ├── config.py │ ├── status.py │ └── store.py ├── ignition.py ├── instrumentation.py ├── mailsync │ ├── __init__.py │ ├── backends │ │ ├── __init__.py │ │ ├── base.py │ │ ├── gmail.py │ │ └── imap │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ ├── generic.py │ │ │ └── monitor.py │ ├── frontend.py │ ├── gc.py │ └── service.py ├── models │ ├── __init__.py │ ├── account.py │ ├── action_log.py │ ├── backends │ │ ├── __init__.py │ │ ├── generic.py │ │ ├── gmail.py │ │ ├── imap.py │ │ ├── oauth.py │ │ └── outlook.py │ ├── base.py │ ├── block.py │ ├── calendar.py │ ├── category.py │ ├── constants.py │ ├── contact.py │ ├── data_processing.py │ ├── event.py │ ├── folder.py │ ├── label.py │ ├── message.py │ ├── meta.py │ ├── metadata.py │ ├── mixins.py │ ├── namespace.py │ ├── roles.py │ ├── search.py │ ├── secret.py │ ├── session.py │ ├── thread.py │ ├── transaction.py │ ├── util.py │ └── when.py ├── providers.py ├── s3 │ ├── __init__.py │ ├── backends │ │ ├── __init__.py │ │ ├── gmail.py │ │ └── imap.py │ ├── base.py │ └── exc.py ├── scheduling │ ├── __init__.py │ ├── deferred_migration.py │ ├── event_queue.py │ └── queue.py ├── search │ ├── __init__.py │ ├── backends │ │ ├── __init__.py │ │ ├── generic.py │ │ ├── gmail.py │ │ └── imap.py │ └── base.py ├── security │ ├── __init__.py │ ├── blobstorage.py │ └── oracles.py ├── sendmail │ ├── __init__.py │ ├── base.py │ ├── generic.py │ ├── gmail.py │ ├── message.py │ └── smtp │ │ ├── __init__.py │ │ ├── postel.py │ │ └── util.py ├── sqlalchemy_ext │ ├── __init__.py │ └── util.py ├── sync │ ├── __init__.py │ └── base_sync.py ├── test │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── base.py │ │ ├── test_account.py │ │ ├── test_auth.py │ │ ├── test_calendars.py │ │ ├── test_contacts.py │ │ ├── test_data_processing.py │ │ ├── test_drafts.py │ │ ├── test_event_participants.py │ │ ├── test_event_when.py │ │ ├── test_events.py │ │ ├── test_events_recurring.py │ │ ├── test_files.py │ │ ├── test_filtering.py │ │ ├── test_folders.py │ │ ├── test_folders_labels.py │ │ ├── test_invalid_account.py │ │ ├── test_messages.py │ │ ├── test_searching.py │ │ ├── test_sending.py │ │ ├── test_streaming.py │ │ ├── test_threads.py │ │ ├── test_validation.py │ │ └── test_views.py │ ├── auth │ │ ├── __init__.py │ │ ├── providers │ │ │ ├── __init__.py │ │ │ └── mock_gmail.py │ │ ├── test_generic_auth.py │ │ ├── test_gmail_auth.py │ │ ├── test_gmail_auth_credentials.py │ │ ├── test_imap_smtp_auth.py │ │ └── test_ssl_auth.py │ ├── conftest.py │ ├── contacts │ │ ├── test_process_mail.py │ │ └── test_remote_sync.py │ ├── data │ │ ├── .agignore │ │ ├── LetMeSendYouEmail.wav │ │ ├── __init__.py │ │ ├── andra-moi-ennepe.txt │ │ ├── general_test_provider_resolution.json │ │ ├── invite.ics │ │ ├── long-non-ascii-filename.txt │ │ ├── muir.jpg │ │ ├── piece-jointe.jpg │ │ ├── raw_message_with_bad_attachment.txt │ │ ├── raw_message_with_filename_attachment.txt │ │ ├── raw_message_with_ical_invite.txt │ │ ├── raw_message_with_inline_attachment.txt │ │ ├── raw_message_with_long_content_id.txt │ │ ├── raw_message_with_long_message_id.txt │ │ ├── raw_message_with_many_recipients.txt │ │ ├── raw_message_with_name_attachment.txt │ │ ├── raw_message_with_outlook_emoji.txt │ │ ├── raw_message_with_outlook_emoji_inline.txt │ │ ├── self_signed_cert.key │ │ └── self_signed_cert.pem │ ├── events │ │ ├── fixtures │ │ │ ├── bogus_sequence_number.ics │ │ │ ├── event_with_no_participants.ics │ │ │ ├── event_without_sequence.ics │ │ │ ├── gcal_recur.ics │ │ │ ├── gcal_v1.ics │ │ │ ├── gcal_v2.ics │ │ │ ├── google_cancelled1.ics │ │ │ ├── google_cancelled2.ics │ │ │ ├── icloud_cancelled1.ics │ │ │ ├── icloud_cancelled2.ics │ │ │ ├── icloud_oneday_event.ics │ │ │ ├── invalid_rsvp.ics │ │ │ ├── invalid_rsvp2.ics │ │ │ ├── invite_w_rsvps1.ics │ │ │ ├── invite_w_rsvps2.ics │ │ │ ├── invite_w_rsvps3.ics │ │ │ ├── invite_w_rsvps_4.ics │ │ │ ├── iphone_through_exchange.ics │ │ │ ├── meetup_infinite.ics │ │ │ ├── multiple_events.ics │ │ │ ├── multiple_summaries.ics │ │ │ ├── self_sent_v1.ics │ │ │ ├── self_sent_v2.ics │ │ │ └── windows_event.ics │ │ ├── test_datetime.py │ │ ├── test_events_util.py │ │ ├── test_google_events.py │ │ ├── test_ics_parsing.py │ │ ├── test_inviting.py │ │ ├── test_merge.py │ │ ├── test_recurrence.py │ │ ├── test_rsvp.py │ │ └── test_sync.py │ ├── general │ │ ├── __init__.py │ │ ├── test_account.py │ │ ├── test_address_canonicalization.py │ │ ├── test_category.py │ │ ├── test_concurrency.py │ │ ├── test_draft_creation.py │ │ ├── test_filename_truncation.py │ │ ├── test_html_parsing.py │ │ ├── test_ignition.py │ │ ├── test_lock.py │ │ ├── test_message_parsing.py │ │ ├── test_mutable_json_type.py │ │ ├── test_namespace.py │ │ ├── test_paths.py │ │ ├── test_provider_export.py │ │ ├── test_provider_resolution.py │ │ ├── test_relationships.py │ │ ├── test_required_folders.py │ │ ├── test_sync_engine_exit.py │ │ ├── test_thread_creation.py │ │ ├── test_threading.py │ │ └── test_util.py │ ├── heartbeat │ │ └── test_heartbeat.py │ ├── imap │ │ ├── __init__.py │ │ ├── data.py │ │ ├── network │ │ │ ├── __init__.py │ │ │ ├── test_actions_syncback.py │ │ │ ├── test_drafts_syncback.py │ │ │ └── test_send.py │ │ ├── test_actions.py │ │ ├── test_crispin_client.py │ │ ├── test_delete_handling.py │ │ ├── test_folder_state.py │ │ ├── test_folder_sync.py │ │ ├── test_full_imap_enabled.py │ │ ├── test_labels.py │ │ ├── test_pooling.py │ │ ├── test_save_folder_names.py │ │ ├── test_smtp.py │ │ └── test_update_metadata.py │ ├── providers │ │ └── __init__.py │ ├── pytest.ini │ ├── scheduling │ │ ├── conftest.py │ │ ├── test_sync_start_logic.py │ │ └── test_syncback_logic.py │ ├── search │ │ ├── __init__.py │ │ └── conftest.py │ ├── security │ │ ├── test_blobstorage.py │ │ ├── test_secret.py │ │ └── test_smtp_ssl.py │ ├── system │ │ ├── .gitignore │ │ ├── __init__.py │ │ ├── client.py │ │ ├── conftest.py │ │ ├── google_auth_helper.py │ │ ├── outlook_auth_helper.py │ │ ├── random_words.py │ │ ├── test_auth.py │ │ ├── test_drafts.py │ │ ├── test_events.py │ │ ├── test_google_events.py │ │ ├── test_labels.py │ │ └── test_sending.py │ ├── transactions │ │ ├── test_action_scheduling.py │ │ ├── test_delta_sync.py │ │ ├── test_thread_versioning.py │ │ ├── test_transaction_creation.py │ │ └── test_transaction_deletion.py │ ├── util │ │ ├── __init__.py │ │ ├── base.py │ │ └── crispin.py │ └── webhooks │ │ ├── __init__.py │ │ └── test_gpush_calendar_notifications.py ├── transactions │ ├── __init__.py │ ├── actions.py │ ├── delta_sync.py │ └── search.py ├── util │ ├── __init__.py │ ├── addr.py │ ├── blockstore.py │ ├── concurrency.py │ ├── db.py │ ├── debug.py │ ├── encoding.py │ ├── file.py │ ├── fleet.py │ ├── html.py │ ├── itert.py │ ├── misc.py │ ├── rdb.py │ ├── sharding.py │ ├── startup.py │ ├── stats.py │ ├── testutils.py │ ├── threading.py │ └── url.py └── webhooks │ ├── __init__.py │ └── gpush_notifications.py ├── migrations ├── README ├── env.py ├── script.py.mako └── versions │ ├── 000_g_msgid_g_thrid_as_integers.py │ ├── 001_rename_message_id_to_message_id_header.py │ ├── 002_store_g_thrid_as_biginteger_instead_of_.py │ ├── 003_expand_littlejson.py │ ├── 004_drafts_as_required_folder.py │ ├── 005_import_old_accounts.py │ ├── 006_add_search_tokens.py │ ├── 007_per_provider_table_split.py │ ├── 008_store_userinfo_from_oauth.py │ ├── 009_multiple_contact_providers.py │ ├── 010_store_raw_contact_data.py │ ├── 011_use_server_default.py │ ├── 012_move_google_userinfo_fields_to_.py │ ├── 013_add_spool_msg.py │ ├── 014_contact_ranking_signals.py │ ├── 015_generalize_from_sender_header_field.py │ ├── 016_extra_transaction_data.py │ ├── 017_haspublicid.py │ ├── 018_message_contact_association.py │ ├── 019_blocks_to_parts.py │ ├── 020_store_webhook_parameters.py │ ├── 021_add_references_column_to_message_table.py │ ├── 022_store_imapuid_msg_uid_as_biginteger_.py │ ├── 022_webhooks_and_filters.py │ ├── 023_tighten_nullable_constraints_on_.py │ ├── 024_remote_folders_and_inbox_tags_split.py │ ├── 025_remove_user_sharedfolder_and_usersession.py │ ├── 026_add_audit_timestamps_to_all_objects.py │ ├── 027_imapuid_soft_deletes.py │ ├── 028_tag_api_migration.py │ ├── 029_set_inbox_folder_exposed_name.py │ ├── 030_add_is_read_attribute_to_messages.py │ ├── 031_add_indexes_to_timestamps.py │ ├── 032_tighten_easuid.py │ ├── 033_add_more_indexes.py │ ├── 034_cascade_folder_deletes_to_imapuid.py │ ├── 035_add_columns_for_drafts_support_to_.py │ ├── 036_replace_usertag_by_generic_tag.py │ ├── 037_shorten_addresses.py │ ├── 038_add_public_ids_to_transactions.py │ ├── 039_change_easfoldersync_unique_constraint.py │ ├── 040_gmailaccount.py │ ├── 041_add_sync_status_columns_to_foldersync.py │ ├── 042_simplify_tags_schema.py │ ├── 043_columns_for_sync_running_stopped_killed.py │ ├── 044_update_drafts_schema.py │ ├── 045_new_password_storage.py │ ├── 046_yahoo.py │ ├── 047_store_more_on_threads.py │ ├── 048_remove_storage_of_access_token.py │ ├── 049_store_less_on_threads_after_all.py │ ├── 050_imap_table_cleanups.py │ ├── 051_store_secrets_in_local_vault.py │ ├── 052_store_google_client_id_and_secret_on_.py │ ├── 053_canonicalize_addresses.py │ ├── 054_dont_specially_store_mailing_list_.py │ ├── 055_add_account_liveness.py │ ├── 056_message_unique_constraint.py │ ├── 057_consolidate_account_sync_status_columns.py │ ├── 058_enforce_length_limit_of_255_on_message_.py │ ├── 059_add_action_log.py │ ├── 060_cascade_folder_deletes_to_easuid.py │ ├── 061_remove_easfoldersyncstatus_folder_rows_.py │ ├── 062_up_max_length_of_message_message_id_header.py │ ├── 063_drop_misc_keyval_column_on_parts.py │ ├── 064_make_address_fields_non_null.py │ ├── 065_add_multi_column_transaction_index.py │ ├── 066_kill_spoolmessage.py │ ├── 067_add_executed_status_to_action_log.py │ ├── 068_outlook.py │ ├── 069_aol.py │ ├── 070_fix_folder_easfoldersyncstatus_unique_constraints.py │ ├── 071_more_sync_states.py │ ├── 072_recompute_snippets.py │ ├── 073_generic_providers.py │ ├── 074_add_eas_thrid_index.py │ ├── 075_drop_contacts_search_signals.py │ ├── 076_add_thread_order_column.py │ ├── 077_add_supports_condstore_column_to_.py │ ├── 078_events.py │ ├── 079_events_longer_uids.py │ ├── 080_longer_event_summaries.py │ ├── 081_move_imapfolder_highestmodseq_to_bigint.py │ ├── 082_event_participants.py │ ├── 083_calendars_event_owners.py │ ├── 084_mutable_drafts.py │ ├── 085_add_attachment_tag.py │ ├── 086_event_date_times.py │ ├── 087_fix_account_foreign_keys.py │ ├── 088_calendar_descriptions.py │ ├── 089_revert_encryption.py │ ├── 090_parts_block_ids.py │ ├── 091_remove_webhooks.py │ ├── 092_fix_outlookaccount_typo.py │ ├── 093_add_folder_identifier.py │ ├── 094_eas_passwords.py │ ├── 095_secret_storage.py │ ├── 096_migrate_secret_data.py │ ├── 097_secrets_endgame.py │ ├── 098_add_throttling_support.py │ ├── 099_add_namespace_id_to_message.py │ ├── 100_make_message_namespace_id_nonnull.py │ ├── 101_add_namespace_to_contacts.py │ ├── 102_add_namespace_to_events.py │ ├── 103_add_namespace_to_calendars.py │ ├── 104_add_message_inbox_uid_index.py │ ├── 105_add_subject_indexes.py │ ├── 106_add_more_indexes.py │ ├── 107_drop_eas_state.py │ ├── 108_easaccount_username.py │ ├── 109_add_retries_column_to_the_actionlog.py │ ├── 110_add_thread_index.py │ ├── 111_add_account_name_column.py │ ├── 112_imap_delete_cascades.py │ ├── 113_add_custom_imap_overrides.py │ ├── 114_eas_twodevices_pledge.py │ ├── 115_eas_twodevices_turn.py │ ├── 116_eas_twodevices_prestige.py │ ├── 117_fix_easuid_delete_cascades.py │ ├── 118_store_label_information_per_uid.py │ ├── 119_store_full_message_body.py │ ├── 120_simplify_transaction_log.py │ ├── 121_add_searchindexcursor.py │ ├── 122_add_easeventuid.py │ ├── 123_remove_gmail_inbox_syncs.py │ ├── 124_remove_soft_deleted_objects.py │ ├── 125_refactor_participants_table.py │ ├── 126_add_account_sync_contacts_events.py │ ├── 127_remove_easeventuid.py │ ├── 128_fix_cascades.py │ ├── 129_make_folder_name_case_sensitive.py │ ├── 130_add_message_index.py │ ├── 131_update_transaction_indices.py │ ├── 132_add_cascade_delete_part_block_id.py │ ├── 133_add_unique_account_constraint.py │ ├── 134_add_message_index.py │ ├── 135_add_thread_tag_index_to_tagitem.py │ ├── 136_add_actionlog_index.py │ ├── 137_add_versions.py │ ├── 138_add_participants_column.py │ ├── 139_add_ns_index_to_contact_and_event.py │ ├── 140_relax_participants_by_email_constraint.py │ ├── 141_remote_remote_contacts.py │ ├── 142_add_sync_run_bit.py │ ├── 143_add_reply_to_message_id.py │ ├── 144_update_calendar_index.py │ ├── 145_drop_event_constraint.py │ ├── 146_update_google_calendar_uids.py │ ├── 147_add_cleaned_subject.py │ ├── 148_add_last_modified_column_for_events.py │ ├── 149_add_emailed_events_calendar.py │ ├── 150_add_polymorphic_events.py │ ├── 151_remove_message_thread_order.py │ ├── 152_add_message_id_to_event.py │ ├── 153_revert_account_unique_constraint.py │ ├── 154_add_message_indices.py │ ├── 155_add_status_column.py │ ├── 156_drop_cancelled_column.py │ ├── 157_update_eas_schema.py │ ├── 158_update_eas_schema_part_2.py │ ├── 159_update_eas_schema_part_3.py │ ├── 160_split_actionlog.py │ ├── 161_update_eas_schema_part_3_for_prod.py │ ├── 162_update_folder_unique_constraint.py │ ├── 163_drop_transaction_snapshot.py │ ├── 164_add_decode_error_index.py │ ├── 165_add_compacted_body.py │ ├── 166_migrate_body_format.py │ ├── 167_create_index_for_querying_messages_by_.py │ ├── 168_drop_message_sanitized_body.py │ ├── 169_update_easuid_schema.py │ ├── 170_update_easuid_schema_2.py │ ├── 171_update_easuid_schema_3.py │ ├── 172_update_easuid_schema_4.py │ ├── 173_add_owner2.py │ ├── 174_backfill_owner2.py │ ├── 175_fix_recurring_override_cascade.py │ ├── 176_add_run_state_folderstatus.py │ ├── 177_add_run_state_eas_folderstatus.py │ ├── 178_add_reply_to_messagecontactassociation.py │ ├── 179_longer_event_descriptions.py │ ├── 180_migrate_event_descriptions.py │ ├── 181_drop_short_event_descriptions.py │ ├── 182_add_data_processing_cache_table.py │ ├── 183_change_event_sync_timestamp.py │ ├── 184_create_gmail_auth_credentials_table.py │ ├── 185_backfill_gmail_auth_credentials_table.py │ ├── 186_new_tables_for_folders_overhaul.py │ ├── 187_migrate_data_for_folders_overhaul.py │ ├── 188_create_sequence_number_column.py │ ├── 189_add_initial_sync_start_end_column.py │ ├── 190_eas_add_device_retirement.py │ ├── 191_add_new_events_and_calendars_flags.py │ ├── 192_add_receivedrecentdate_column_to_threads.py │ ├── 193_calculate_receivedrecentdate_for_threads.py │ ├── 194_extend_eas_folder_id.py │ ├── 195_remove_receivedrecentdate_column.py │ ├── 196_create_outlook_account_column.py │ ├── 197_add_message_categories_change_counter.py │ ├── 198_eas_foldersyncstatus_startstop_columns.py │ ├── 199_save_imap_uidnext.py │ ├── 200_update_imapfolderinfo.py │ ├── 201_add_sync_email_bit_to_account.py │ ├── 202_drop_sync_raw_data_column.py │ ├── 203_deleted_at_constraint.py │ ├── 204_remove_deleted_at_constraint.py │ ├── 205_fix_categories_cascade.py │ ├── 206_add_phone_numbers_table.py │ ├── 207_add_contact_search_index_service_cursor_.py │ ├── 208_drop_easuid_uniqueconstraint.py │ ├── 209_recreate_easuid_index.py │ ├── 210_drop_message_full_body_id_fk.py │ ├── 211_drop_message_full_body_id.py │ ├── 212_add_columns_for_smtp_imap_specific_auth.py │ ├── 213_add_metadata_table.py │ ├── 214_introduce_accounttransaction.py │ ├── 215_add_actionlog_status_type_index.py │ ├── 216_add_folder_separator_column_for_generic_.py │ ├── 217_add_genericaccount_ssl_required.py │ ├── 218_modify_metadata_indexes.py │ ├── 219_accounttransaction_namespace_id_cascade.py │ ├── 220_folder_separators_again.py │ ├── 221_fix_category_column_defaults.py │ ├── 222_remove_unused_transaction_indices.py │ ├── 223_time_mixins_fix.py │ ├── 224_namespace_id_idx_transaction.py │ ├── 225_drop_messagecategory_foreign_keys.py │ ├── 226_add_queryable_value_column_to_metadata.py │ ├── 227_remove_message_foreignkeys.py │ ├── 228_increase_gmailaccount_token_length.py │ ├── 229_drop_transaction_foreign_keys.py │ ├── 230_drop_block_foreign_keys.py │ ├── 231_drop_contact_foreign_keys.py │ ├── 232_add_thread_deleted_at.py │ ├── 233_revert_drop_block_foreign_keys.py │ ├── 234_change_contact_uid_collation.py │ ├── 235_change_imapfolderinfo_column.py │ ├── 236_add_desired_sync_host.py │ └── 237_add_new_contacts_index.py ├── requirements.txt ├── setup.py ├── setup.sh ├── tests ├── __init__.py ├── api │ ├── __init__.py │ ├── base.py │ ├── conftest.py │ ├── test_account.py │ ├── test_auth.py │ ├── test_calendars.py │ ├── test_contacts.py │ ├── test_data_processing.py │ ├── test_drafts.py │ ├── test_event_participants.py │ ├── test_event_when.py │ ├── test_events.py │ ├── test_events_recurring.py │ ├── test_files.py │ ├── test_filtering.py │ ├── test_folders.py │ ├── test_folders_labels.py │ ├── test_invalid_account.py │ ├── test_messages.py │ ├── test_searching.py │ ├── test_sending.py │ ├── test_streaming.py │ ├── test_threads.py │ ├── test_validation.py │ └── test_views.py ├── auth │ ├── __init__.py │ ├── providers │ │ ├── __init__.py │ │ └── mock_gmail.py │ ├── test_generic_auth.py │ ├── test_gmail_auth.py │ ├── test_gmail_auth_credentials.py │ ├── test_imap_smtp_auth.py │ └── test_ssl_auth.py ├── conftest.py ├── contacts │ ├── test_process_mail.py │ └── test_remote_sync.py ├── data │ ├── .agignore │ ├── LetMeSendYouEmail.wav │ ├── __init__.py │ ├── andra-moi-ennepe.txt │ ├── first-message │ ├── general_test_provider_resolution.json │ ├── invite.ics │ ├── long-non-ascii-filename.txt │ ├── muir.jpg │ ├── piece-jointe.jpg │ ├── raw_message │ ├── raw_message_with_bad_attachment │ ├── raw_message_with_filename_attachment │ ├── raw_message_with_ical_invite │ ├── raw_message_with_inline_attachment │ ├── raw_message_with_long_content_id │ ├── raw_message_with_long_message_id │ ├── raw_message_with_many_recipients │ ├── raw_message_with_name_attachment │ ├── raw_message_with_outlook_emoji │ ├── raw_message_with_outlook_emoji_inline │ ├── self_signed_cert.key │ ├── self_signed_cert.pem │ └── test_folders.shelf ├── events │ ├── fixtures │ │ ├── bogus_sequence_number.ics │ │ ├── event_with_no_participants.ics │ │ ├── event_without_sequence.ics │ │ ├── gcal_recur.ics │ │ ├── gcal_v1.ics │ │ ├── gcal_v2.ics │ │ ├── google_cancelled1.ics │ │ ├── google_cancelled2.ics │ │ ├── icloud_cancelled1.ics │ │ ├── icloud_cancelled2.ics │ │ ├── icloud_oneday_event.ics │ │ ├── invalid_rsvp.ics │ │ ├── invalid_rsvp2.ics │ │ ├── invite_w_rsvps1.ics │ │ ├── invite_w_rsvps2.ics │ │ ├── invite_w_rsvps3.ics │ │ ├── invite_w_rsvps_4.ics │ │ ├── iphone_through_exchange.ics │ │ ├── meetup_infinite.ics │ │ ├── multiple_events.ics │ │ ├── multiple_summaries.ics │ │ ├── self_sent_v1.ics │ │ ├── self_sent_v2.ics │ │ └── windows_event.ics │ ├── test_datetime.py │ ├── test_google_events.py │ ├── test_ics_parsing.py │ ├── test_inviting.py │ ├── test_merge.py │ ├── test_recurrence.py │ ├── test_rsvp.py │ ├── test_sync.py │ └── test_util.py ├── general │ ├── __init__.py │ ├── test_account.py │ ├── test_address_canonicalization.py │ ├── test_category.py │ ├── test_concurrency.py │ ├── test_draft_creation.py │ ├── test_filename_truncation.py │ ├── test_html_parsing.py │ ├── test_ignition.py │ ├── test_lock.py │ ├── test_message_parsing.py │ ├── test_mutable_json_type.py │ ├── test_namespace.py │ ├── test_paths.py │ ├── test_provider_export.py │ ├── test_provider_resolution.py │ ├── test_relationships.py │ ├── test_required_folders.py │ ├── test_sync_engine_exit.py │ ├── test_thread_creation.py │ ├── test_threading.py │ └── test_util.py ├── heartbeat │ ├── conftest.py │ └── test_heartbeat.py ├── imap │ ├── __init__.py │ ├── data.py │ ├── network │ │ ├── __init__.py │ │ ├── test_actions_syncback.py │ │ ├── test_drafts_syncback.py │ │ └── test_send.py │ ├── test_actions.py │ ├── test_crispin_client.py │ ├── test_delete_handling.py │ ├── test_folder_state.py │ ├── test_folder_sync.py │ ├── test_full_imap_enabled.py │ ├── test_labels.py │ ├── test_pooling.py │ ├── test_save_folder_names.py │ ├── test_smtp.py │ └── test_update_metadata.py ├── providers │ └── __init__.py ├── pytest.ini ├── scheduling │ ├── conftest.py │ ├── test_sync_start_logic.py │ └── test_syncback_logic.py ├── search │ ├── __init__.py │ └── conftest.py ├── security │ ├── test_blobstorage.py │ ├── test_secret.py │ └── test_smtp_ssl.py ├── system │ ├── .gitignore │ ├── __init__.py │ ├── client.py │ ├── conftest.py │ ├── google_auth_helper.py │ ├── outlook_auth_helper.py │ ├── random_words.py │ ├── test_auth.py │ ├── test_drafts.py │ ├── test_events.py │ ├── test_google_events.py │ ├── test_labels.py │ └── test_sending.py ├── transactions │ ├── test_action_scheduling.py │ ├── test_delta_sync.py │ ├── test_thread_versioning.py │ ├── test_transaction_creation.py │ └── test_transaction_deletion.py ├── util │ ├── __init__.py │ ├── base.py │ └── crispin.py └── webhooks │ ├── __init__.py │ └── test_gpush_calendar_notifications.py └── tox.ini /.arcconfig: -------------------------------------------------------------------------------- 1 | { 2 | "project_id" : "inbox", 3 | "conduit_uri" : "https://phab.nylas.com/", 4 | "lint.engine" : "InboxServerLintEngine", 5 | "unit.engine" : "PytestTestEngine", 6 | "load" : [ 7 | "arclib" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.ics binary 5 | -------------------------------------------------------------------------------- /.lgtm: -------------------------------------------------------------------------------- 1 | approvals = 1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - /home/travis/virtualenv 4 | before_install: 5 | - mysql -u root -e "CREATE USER 'inboxtest'@'%' IDENTIFIED BY 'inboxtest'" 6 | - mysql -u root -e "CREATE USER 'inboxtest'@'localhost' IDENTIFIED BY 'inboxtest'" 7 | - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'inboxtest'@'%'" 8 | - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'inboxtest'@'localhost'" 9 | 10 | install: 11 | - sudo -H pip install flake8 12 | - sudo -H ./setup.sh | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }' 13 | script: 14 | - flake8 --select=F inbox 15 | - pylint -d all -e w0631 inbox 16 | - NYLAS_ENV=test py.test inbox/test -m "not networkrequired" 17 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include inbox/docs/api/*.mdown inbox/docs/gettingstarted/*.mdown inbox/api/mime_types.txt etc/* 2 | recursive-include inbox *.html 3 | # added by check_manifest.py 4 | include *.example 5 | include *.md 6 | include *.sh 7 | include *.txt 8 | include *.yml 9 | include LICENSE 10 | include Vagrantfile 11 | include alembic.ini 12 | include tox.ini 13 | recursive-include arclib *.php 14 | recursive-include bin *.sh 15 | recursive-include debian *.triggers 16 | recursive-include migrations *.mako 17 | recursive-include migrations *.py 18 | recursive-include inbox *.agignore 19 | recursive-include inbox *.ics 20 | recursive-include inbox *.ini 21 | recursive-include inbox *.jpg 22 | recursive-include inbox *.py 23 | recursive-include inbox *.sql 24 | recursive-include inbox *.wav 25 | recursive-include inbox *.txt 26 | recursive-include inbox *.json 27 | recursive-include inbox *.key 28 | recursive-include inbox *.pem 29 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | Summary: 4 | 5 | Test Plan: 6 | 7 | Reviewers: 8 | Please add the reviewer as an assignee to this PR on the right 9 | -------------------------------------------------------------------------------- /Vagrantfile.local.example: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # You can define your own VM settings by saving this file as Vagrantfile.local 5 | 6 | VAGRANTFILE_API_VERSION = "2" unless defined? VAGRANTFILE_API_VERSION 7 | 8 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 9 | 10 | # Use fewer memory and CPU resources 11 | #config.vm.provider :virtualbox do |vbox, override| 12 | # vbox.memory = 512 13 | # vbox.cpus = 1 14 | #end 15 | #config.vm.provider :vmware_fusion do |vmware, override| 16 | # vmware.vmx["memsize"] = "512" 17 | # vmware.vmx["numvcpus"] = "1" 18 | #end 19 | 20 | # Don't restrict port 5555 forwarding to localhost (note missing 'host_ip' parameter) 21 | #config.vm.network "forwarded_port", guest: 5555, host: 5555 22 | 23 | # Disable forwarded port 24 | #config.vm.network "forwarded_port", guest: 5555, host: 5555, disabled: true 25 | 26 | # Mount /home/foo/shared_folder as /shared on the guest. 27 | #config.vm.synced_folder "/home/foo/shared_folder", "/shared" 28 | 29 | end 30 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = migrations 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # set to 'true' to run the environment during 11 | # the 'revision' command, regardless of autogenerate 12 | # revision_environment = false 13 | 14 | 15 | # Logging configuration 16 | [loggers] 17 | keys = root,sqlalchemy,alembic 18 | 19 | [handlers] 20 | keys = console 21 | 22 | [formatters] 23 | keys = generic 24 | 25 | [logger_root] 26 | level = WARN 27 | handlers = console 28 | qualname = 29 | 30 | [logger_sqlalchemy] 31 | level = WARN 32 | handlers = 33 | qualname = sqlalchemy.engine 34 | 35 | [logger_alembic] 36 | level = INFO 37 | handlers = 38 | qualname = alembic 39 | 40 | [handler_console] 41 | class = StreamHandler 42 | args = (sys.stderr,) 43 | level = NOTSET 44 | formatter = generic 45 | 46 | [formatter_generic] 47 | format = %(levelname)-5.5s [%(name)s] %(message)s 48 | datefmt = %H:%M:%S 49 | -------------------------------------------------------------------------------- /arclib/__phutil_library_init__.php: -------------------------------------------------------------------------------- 1 | 2, 11 | 'class' => 12 | array( 13 | 'InboxServerLintEngine' => 'src/InboxServerLintEngine.php', 14 | 'MGArcanistPyLintLinter' => 'src/MGArcanistPyLintLinter.php', 15 | 'PytestTestEngine' => 'src/PytestTestEngine.php', 16 | ), 17 | 'function' => 18 | array( 19 | ), 20 | 'xmap' => 21 | array( 22 | 'InboxServerLintEngine' => 'ArcanistLintEngine', 23 | 'MGArcanistPyLintLinter' => 'ArcanistLinter', 24 | 'PytestTestEngine' => 'ArcanistUnitTestEngine', 25 | ), 26 | )); 27 | -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/bin/__init__.py -------------------------------------------------------------------------------- /bin/clear-heartbeat-status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import click 4 | from sys import exit 5 | 6 | from inbox.config import config 7 | from inbox.heartbeat.status import clear_heartbeat_status 8 | from nylas.logging import configure_logging, get_logger 9 | 10 | configure_logging(config.get('LOGLEVEL')) 11 | log = get_logger() 12 | 13 | 14 | @click.command() 15 | @click.option('--host', '-h', type=str) 16 | @click.option('--port', '-p', type=int, default=6379) 17 | @click.option('--account-id', '-a', type=int, required=True) 18 | @click.option('--folder-id', '-f', type=int) 19 | @click.option('--device-id', '-d', type=int) 20 | def main(host, port, account_id, folder_id, device_id): 21 | print 'Clearing heartbeat status...' 22 | n = clear_heartbeat_status(account_id, folder_id, device_id, host, port) 23 | print '{} folders cleared.'.format(n) 24 | exit(0) 25 | 26 | 27 | if __name__ == '__main__': 28 | main() 29 | -------------------------------------------------------------------------------- /bin/clear-kv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import click 4 | from sys import exit 5 | 6 | from inbox.heartbeat.config import (STATUS_DATABASE, REPORT_DATABASE, 7 | get_redis_client, _get_redis_client) 8 | 9 | 10 | @click.command() 11 | @click.option('--host', '-h', type=str) 12 | @click.option('--port', '-p', type=int, default=6379) 13 | def main(host, port): 14 | if host: 15 | status_client = _get_redis_client(host, port, STATUS_DATABASE) 16 | report_client = _get_redis_client(host, port, REPORT_DATABASE) 17 | else: 18 | status_client = get_redis_client(STATUS_DATABASE) 19 | report_client = get_redis_client(REPORT_DATABASE) 20 | status_client.flushdb() 21 | report_client.flushdb() 22 | exit(0) 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /bin/contact-search-backfill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import click 3 | 4 | from gevent import monkey 5 | monkey.patch_all() 6 | import gevent_openssl 7 | gevent_openssl.monkey_patch() 8 | 9 | from inbox.contacts.search import index_namespace 10 | 11 | from nylas.logging import get_logger, configure_logging 12 | configure_logging() 13 | log = get_logger() 14 | 15 | 16 | @click.command() 17 | @click.argument('namespace_ids', nargs=-1) 18 | def main(namespace_ids): 19 | """ 20 | Idempotently index the given namespace_ids. 21 | 22 | """ 23 | for namespace_id in namespace_ids: 24 | log.info("indexing namespace {namespace_id}".format( 25 | namespace_id=namespace_id)) 26 | index_namespace(namespace_id) 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /bin/contact-search-delete-index: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import click 3 | 4 | from inbox.contacts.search import delete_namespace_indexes as delete_indexes 5 | 6 | from nylas.logging import get_logger, configure_logging 7 | configure_logging() 8 | log = get_logger() 9 | 10 | 11 | @click.command() 12 | @click.argument('namespace_ids') 13 | def delete_namespace_indexes(namespace_ids): 14 | """ 15 | Delete the CloudSearch indexes for a list of namespaces, specified by id. 16 | 17 | """ 18 | delete_indexes(namespace_ids) 19 | 20 | 21 | if __name__ == '__main__': 22 | delete_namespace_indexes() 23 | -------------------------------------------------------------------------------- /bin/create-test-db: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON synctest.* TO inboxtest@localhost IDENTIFIED BY 'inboxtest'" 5 | mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON synctest_1.* TO inboxtest@localhost IDENTIFIED BY 'inboxtest'" 6 | mysql -uroot -proot -e 'GRANT ALL PRIVILEGES ON `test%`.* TO inboxtest@localhost IDENTIFIED BY "inboxtest"' 7 | -------------------------------------------------------------------------------- /bin/deferred-migration-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Watches a redis priority queue for deferred account migrations to execute.""" 3 | 4 | import gevent.monkey 5 | gevent.monkey.patch_all() 6 | 7 | from setproctitle import setproctitle 8 | from inbox.scheduling.deferred_migration import DeferredAccountMigrationExecutor 9 | from nylas.logging import configure_logging 10 | configure_logging() 11 | 12 | 13 | def main(): 14 | setproctitle('deferred-migration-service') 15 | print "Starting DeferredAccountMigrationExecutor..." 16 | dame = DeferredAccountMigrationExecutor() 17 | dame.start() 18 | print "Done" 19 | dame.join() 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /bin/detect-missing-sync-host: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import click 4 | 5 | from sqlalchemy.orm import load_only 6 | 7 | from inbox.models.account import Account 8 | from inbox.models.session import global_session_scope 9 | 10 | 11 | @click.command() 12 | def main(): 13 | """ 14 | Detects accounts with sync_state and sync_host inconsistent with 15 | sync_should_run bit. (At one point, this could happen if, say, an account 16 | was _started_ on a new host without being first stopped on its previous 17 | host.) 18 | 19 | """ 20 | with global_session_scope() as db_session: 21 | for acc in db_session.query(Account).options( 22 | load_only('sync_state', 'sync_should_run', 'sync_host', 'desired_sync_host'))\ 23 | .filter(Account.sync_state == 'stopped'): 24 | 25 | if acc.desired_sync_host is not None: 26 | print "account {} assigned to {} but has sync_state 'stopped'"\ 27 | " ({}, {})"\ 28 | .format(acc.id, acc.sync_host, 29 | acc.sync_should_run, acc.sync_host) 30 | 31 | 32 | if __name__ == '__main__': 33 | main() 34 | -------------------------------------------------------------------------------- /bin/dump-databases: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mysqldump --databases inbox inbox_1 inbox_2 inbox_3 -uroot -proot 3 | -------------------------------------------------------------------------------- /bin/get-accounts-for-host: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import click 4 | 5 | from inbox.models.session import global_session_scope 6 | from inbox.models.account import Account 7 | 8 | 9 | @click.command() 10 | @click.argument('hostname') 11 | def main(hostname): 12 | with global_session_scope() as db_session: 13 | account_ids = db_session.query(Account.id).filter(Account.sync_host == hostname) 14 | 15 | print "Accounts being synced by {}:".format(hostname) 16 | for account_id in account_ids: 17 | print account_id[0] 18 | db_session.commit() 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /bin/inbox-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from gevent import monkey 3 | monkey.patch_all() 4 | import gevent_openssl 5 | gevent_openssl.monkey_patch() 6 | from setproctitle import setproctitle 7 | setproctitle('inbox-console') 8 | 9 | import click 10 | 11 | from nylas.logging import get_logger 12 | log = get_logger() 13 | 14 | from inbox.console import start_console, start_client_console 15 | 16 | 17 | @click.command() 18 | @click.option('-e', '--email_address', default=None, 19 | help='Initialize a crispin client for a particular account.') 20 | @click.option('-c', '--client', is_flag=True, 21 | help='Start a repl with an APIClient') 22 | def console(email_address, client): 23 | """ REPL for Nylas. """ 24 | if client: 25 | start_client_console(email_address) 26 | else: 27 | start_console(email_address) 28 | 29 | 30 | if __name__ == '__main__': 31 | console() 32 | -------------------------------------------------------------------------------- /bin/load-dump: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mysql -uroot -proot < $1 3 | -------------------------------------------------------------------------------- /bin/populate-sync-queue: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Continuously populates the queue of accounts to be synced. Run one of these per 4 | deployment, or several for availability. (It is safe to run multiple 5 | concurrently.) Currently, this script populates queues for all configured 6 | zones. It could be modified so that different zones are populated 7 | independently, if needed. 8 | """ 9 | import gevent 10 | import gevent.monkey 11 | gevent.monkey.patch_all() 12 | from setproctitle import setproctitle 13 | from inbox.config import config 14 | from inbox.scheduling.queue import QueuePopulator 15 | from nylas.logging import configure_logging 16 | configure_logging() 17 | 18 | 19 | def main(): 20 | setproctitle('scheduler') 21 | zones = {h.get('ZONE') for h in config['DATABASE_HOSTS']} 22 | threads = [] 23 | for zone in zones: 24 | populator = QueuePopulator(zone) 25 | threads.append(gevent.spawn(populator.run)) 26 | 27 | gevent.joinall(threads) 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | -------------------------------------------------------------------------------- /bin/verify-db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from inbox.config import config 3 | from inbox.ignition import EngineManager, verify_db 4 | 5 | 6 | def main(): 7 | database_hosts = config.get_required('DATABASE_HOSTS') 8 | database_users = config.get_required('DATABASE_USERS') 9 | # Do not include disabled shards since application services do not use them. 10 | engine_manager = EngineManager(database_hosts, database_users, 11 | include_disabled=False) 12 | 13 | for host in database_hosts: 14 | for shard in host['SHARDS']: 15 | if shard.get('DISABLED'): 16 | continue 17 | key = int(shard['ID']) 18 | engine = engine_manager.engines[key] 19 | schema = shard['SCHEMA_NAME'] 20 | 21 | print 'Verifying database: {}'.format(schema) 22 | verify_db(engine, schema, key) 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | inbox-sync (0.4) unstable; urgency=low 2 | 3 | * Initial release. 4 | 5 | -- Rob McQueen Tue, 20 Jan 2015 06:50:32 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: inbox-sync 2 | Section: python 3 | Priority: extra 4 | Maintainer: Reliability Team 5 | Build-Depends: debhelper (>= 9), python, dh-virtualenv (>= 0.8) 6 | Standards-Version: 3.9.5 7 | 8 | Package: inbox-sync 9 | Architecture: any 10 | Pre-Depends: dpkg (>= 1.16.1), python2.7-minimal | python2.6-minimal, ${misc:Pre-Depends} 11 | Depends: ${python:Depends}, ${misc:Depends} 12 | Description: Nylas Sync Service 13 | runs the Nylas Sync Engine 14 | -------------------------------------------------------------------------------- /debian/inbox-sync.triggers: -------------------------------------------------------------------------------- 1 | # Register interest in Python interpreter changes (Python 2 for now); and 2 | # don't make the Python package dependent on the virtualenv package 3 | # processing (noawait) 4 | interest-noawait /usr/bin/python2.6 5 | interest-noawait /usr/bin/python2.7 6 | 7 | # Also provide a symbolic trigger for all dh-virtualenv packages 8 | interest dh-virtualenv-interpreter-update 9 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python-virtualenv -------------------------------------------------------------------------------- /etc/config-test-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "DATABASE_HOSTS": [ 3 | { 4 | "HOSTNAME": "localhost", 5 | "ZONE": "testzone", 6 | "PORT": 3306, 7 | "SHARDS": [ 8 | { 9 | "ID": 0, 10 | "SCHEMA_NAME": "test_prod", 11 | "OPEN": true 12 | }, 13 | { 14 | "ID": 1, 15 | "SCHEMA_NAME": "test_1_prod", 16 | "OPEN": false 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /etc/db-config-test-phab.json: -------------------------------------------------------------------------------- 1 | { 2 | "DATABASE_HOSTS": [ 3 | { 4 | "HOSTNAME": "127.0.0.1", 5 | "PORT": 3306, 6 | "ZONE": "testzone", 7 | "SHARDS": [ 8 | { 9 | "ID": 0, 10 | "SCHEMA_NAME": "synctest_phab", 11 | "OPEN": true 12 | }, 13 | { 14 | "ID": 1, 15 | "SCHEMA_NAME": "synctest_1_phab", 16 | "OPEN": false 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /etc/db-config-test-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "DATABASE_HOSTS": [ 3 | { 4 | "HOSTNAME": "127.0.0.1", 5 | "PORT": 3306, 6 | "ZONE": "testzone", 7 | "SHARDS": [ 8 | { 9 | "ID": 0, 10 | "SCHEMA_NAME": "synctest_prod", 11 | "OPEN": true 12 | }, 13 | { 14 | "ID": 1, 15 | "SCHEMA_NAME": "synctest_1_prod", 16 | "OPEN": false 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /etc/db-config-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "DATABASE_HOSTS": [ 3 | { 4 | "HOSTNAME": "127.0.0.1", 5 | "PORT": 3306, 6 | "ZONE": "testzone", 7 | "SHARDS": [ 8 | { 9 | "ID": 0, 10 | "SCHEMA_NAME": "synctest", 11 | "OPEN": true 12 | }, 13 | { 14 | "ID": 1, 15 | "SCHEMA_NAME": "synctest_1", 16 | "OPEN": false 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /etc/my.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | host=localhost 3 | port=3306 4 | user=inbox 5 | password=inbox 6 | default-character-set=utf8mb4 7 | 8 | [mysqld] 9 | collation-server=utf8mb4_general_ci 10 | init-connect='SET NAMES utf8mb4' 11 | character-set-server=utf8mb4 12 | sql-mode='traditional' 13 | transaction-isolation=READ-COMMITTED 14 | innodb-online-alter-log-max-size=4294967296 15 | innodb-lock-wait-timeout=50 16 | net-read-timeout=60 17 | max-allowed-packet=67108864 18 | innodb-log-file-size=671088640 19 | max-connect-errors=10000000 20 | 21 | slow-query-log=1 22 | slow_query_log_file=/var/log/mysql/mysql-slow.log 23 | long_query_time=2 24 | log-queries-not-using-indexes 25 | -------------------------------------------------------------------------------- /etc/mysql_ec2.conf: -------------------------------------------------------------------------------- 1 | [client] 2 | host=localhost 3 | port=3306 4 | user=inbox 5 | password=inbox 6 | default-character-set=utf8mb4 7 | 8 | [mysqld] 9 | collation-server=utf8mb4_general_ci 10 | init-connect='SET NAMES utf8mb4' 11 | character-set-server=utf8mb4 12 | sql-mode='traditional' 13 | transaction-isolation=READ-COMMITTED 14 | 15 | log_slow_queries=/var/log/mysql/mysql-slow.log 16 | long_query_time=2 17 | log-queries-not-using-indexes 18 | 19 | datadir = /mnt/inboxapp/mysql 20 | 21 | innodb_data_home_dir = /mnt/inboxapp/mysql/ 22 | innodb_data_file_path = ibdata1:10M:autoextend 23 | innodb_log_group_home_dir = /mnt/inboxapp/mysql/ 24 | -------------------------------------------------------------------------------- /etc/secrets-dev.yml: -------------------------------------------------------------------------------- 1 | --- 2 | GOOGLE_OAUTH_CLIENT_ID: 986659776516-fg79mqbkbktf5ku10c215vdij918ra0a.apps.googleusercontent.com 3 | GOOGLE_OAUTH_CLIENT_SECRET: zgY9wgwML0kmQ6mmYHYJE05d 4 | MS_LIVE_OAUTH_CLIENT_ID: 0000000048157D75 5 | MS_LIVE_OAUTH_CLIENT_SECRET: W69jkmY8Lp1CbRqn-H7TtRXLDLU7XBxb 6 | # Hexl-encoded static keys used to encrypt blocks in S3, secrets in database: 7 | BLOCK_ENCRYPTION_KEY: 43933ee4aff59913b7cd7204d87ee18cd5d0faea4df296cb7863f9f28525f7cd 8 | SECRET_ENCRYPTION_KEY: 5f2356f7e2dfc4ccc93458d27147f97b954a56cc0554273cb6fee070cbadd050 9 | CONTACTS_SEARCH: 10 | SENTRY_DSN: "" 11 | 12 | DATABASE_USERS: 13 | "localhost": 14 | USER: root 15 | PASSWORD: root 16 | "127.0.0.1": 17 | USER: root 18 | PASSWORD: root 19 | -------------------------------------------------------------------------------- /etc/secrets-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | GOOGLE_OAUTH_CLIENT_ID: 986659776516-fg79mqbkbktf5ku10c215vdij918ra0a.apps.googleusercontent.com 3 | GOOGLE_OAUTH_CLIENT_SECRET: zgY9wgwML0kmQ6mmYHYJE05d 4 | MS_LIVE_OAUTH_CLIENT_ID: 000000004C12138C 5 | MS_LIVE_OAUTH_CLIENT_SECRET: tjMNyu7ACbE8DOt0LE30Ptk7muNdPosG 6 | # Hexl-encoded static keys used to encrypt blocks in S3, secrets in database: 7 | BLOCK_ENCRYPTION_KEY: 0ba4c7da83f474d2b33c8725416e444db632a1684705bc2fb7da5058e93668c9 8 | SECRET_ENCRYPTION_KEY: 1f5be7969a7ea9abf8da443151269fe2c25f1d0e81c7ee239c67991a55a33553 9 | -------------------------------------------------------------------------------- /inbox/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | 5 | try: 6 | from inbox.client import APIClient 7 | __all__ = ['APIClient'] 8 | except ImportError: 9 | pass 10 | -------------------------------------------------------------------------------- /inbox/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/actions/__init__.py -------------------------------------------------------------------------------- /inbox/actions/backends/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | An action module *must* meet the following requirement: 3 | 4 | 1. Specify the provider it implements as the module-level PROVIDER variable. 5 | For example, 'gmail', 'imap', 'eas', 'yahoo' etc. 6 | 7 | 2. Live in the 'inbox.actions.backends' module tree. 8 | 9 | """ 10 | # Allow out-of-tree action submodules. 11 | from pkgutil import extend_path 12 | __path__ = extend_path(__path__, __name__) 13 | from inbox.util.misc import register_backends 14 | module_registry = register_backends(__name__, __path__) 15 | -------------------------------------------------------------------------------- /inbox/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/api/__init__.py -------------------------------------------------------------------------------- /inbox/api/wsgi.py: -------------------------------------------------------------------------------- 1 | from inbox.config import config 2 | 3 | import nylas.api.wsgi 4 | 5 | from nylas.api.wsgi import (NylasWSGIHandler, NylasWSGIWorker, 6 | NylasGunicornLogger) 7 | 8 | nylas.api.wsgi.MAX_BLOCKING_TIME = config.get('MAX_BLOCKING_TIME', 9 | nylas.api.wsgi.MAX_BLOCKING_TIME) 10 | nylas.api.wsgi.LOGLEVEL = config.get('LOGLEVEL', 11 | nylas.api.wsgi.LOGLEVEL) 12 | 13 | # legacy names for backcompat 14 | InboxWSGIWorker = NylasWSGIWorker 15 | GunicornLogger = NylasGunicornLogger 16 | 17 | 18 | __all__ = ['NylasWSGIHandler', 'NylasWSGIWorker', 'NylasGunicornLogger', 19 | 'InboxWSGIWorker', 'GunicornLogger'] 20 | -------------------------------------------------------------------------------- /inbox/auth/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Per-provider auth modules. 3 | 4 | An auth module *must* meet the following requirement: 5 | 6 | 1. Specify the provider it implements as the module-level PROVIDER variable. 7 | For example, 'gmail', 'imap', 'eas', 'yahoo' etc. 8 | 9 | 2. Live in the 'auth/' directory. 10 | 11 | 3. Register an AuthHandler class as an entry point in setup.py 12 | """ 13 | # Allow out-of-tree auth submodules. 14 | from pkgutil import extend_path 15 | from inbox.util.misc import register_backends 16 | __path__ = extend_path(__path__, __name__) 17 | module_registry = register_backends(__name__, __path__) 18 | -------------------------------------------------------------------------------- /inbox/basicauth.py: -------------------------------------------------------------------------------- 1 | # TODO(emfree): this is now legitimately just a grab-bag of nebulous 2 | # exceptions. Rename module and clean up. 3 | 4 | 5 | class AuthError(Exception): 6 | pass 7 | 8 | 9 | class SSLNotSupportedError(AuthError): 10 | pass 11 | 12 | 13 | class ConnectionError(AuthError): 14 | pass 15 | 16 | 17 | class ValidationError(AuthError): 18 | pass 19 | 20 | 21 | class NotSupportedError(AuthError): 22 | pass 23 | 24 | 25 | class OAuthError(ValidationError): 26 | pass 27 | 28 | 29 | class ConfigurationError(Exception): 30 | pass 31 | 32 | 33 | class UserRecoverableConfigError(Exception): 34 | pass 35 | 36 | 37 | class SettingUpdateError(Exception): 38 | pass 39 | 40 | 41 | class GmailSettingError(ValidationError): 42 | pass 43 | 44 | 45 | class ImapSupportDisabledError(ValidationError): 46 | 47 | def __init__(self, reason=None): 48 | super(ImapSupportDisabledError, self).__init__(reason) 49 | self.reason = reason 50 | 51 | 52 | class AccessNotEnabledError(Exception): 53 | pass 54 | 55 | 56 | class AppPasswordError(ValidationError): 57 | pass 58 | -------------------------------------------------------------------------------- /inbox/contacts/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/contacts/crud.py: -------------------------------------------------------------------------------- 1 | """Utility functions for creating, reading, updating and deleting contacts. 2 | Called by the API.""" 3 | import uuid 4 | 5 | from inbox.models import Contact 6 | 7 | INBOX_PROVIDER_NAME = 'inbox' 8 | 9 | 10 | def create(namespace, db_session, name, email): 11 | contact = Contact( 12 | namespace=namespace, 13 | provider_name=INBOX_PROVIDER_NAME, 14 | uid=uuid.uuid4().hex, 15 | name=name, 16 | email_address=email) 17 | db_session.add(contact) 18 | db_session.commit() 19 | return contact 20 | 21 | 22 | def read(namespace, db_session, contact_public_id): 23 | return db_session.query(Contact).filter( 24 | Contact.public_id == contact_public_id, 25 | Contact.namespace_id == namespace.id).first() 26 | 27 | 28 | def update(namespace, db_session, contact_public_id, name, email): 29 | raise NotImplementedError 30 | 31 | 32 | def delete(namespace, db_session, contact_public_id): 33 | raise NotImplementedError 34 | -------------------------------------------------------------------------------- /inbox/events/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/events/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/events/actions/__init__.py -------------------------------------------------------------------------------- /inbox/events/actions/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree action submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | from inbox.util.misc import register_backends 5 | module_registry = register_backends(__name__, __path__) 6 | -------------------------------------------------------------------------------- /inbox/events/actions/backends/gmail.py: -------------------------------------------------------------------------------- 1 | """ Operations for syncing back local Calendar changes to Gmail. """ 2 | 3 | from inbox.events.google import GoogleEventsProvider 4 | 5 | PROVIDER = 'gmail' 6 | 7 | __all__ = ['remote_create_event', 'remote_update_event', 'remote_delete_event'] 8 | 9 | 10 | def remote_create_event(account, event, db_session, extra_args): 11 | provider = GoogleEventsProvider(account.id, account.namespace.id) 12 | result = provider.create_remote_event(event, **extra_args) 13 | # The events crud API assigns a random uid to an event when creating it. 14 | # We need to update it to the value returned by the Google calendar API. 15 | event.uid = result['id'] 16 | db_session.commit() 17 | 18 | 19 | def remote_update_event(account, event, db_session, extra_args): 20 | provider = GoogleEventsProvider(account.id, account.namespace.id) 21 | provider.update_remote_event(event, **extra_args) 22 | 23 | 24 | def remote_delete_event(account, event_uid, calendar_name, calendar_uid, 25 | db_session, extra_args): 26 | provider = GoogleEventsProvider(account.id, account.namespace.id) 27 | provider.delete_remote_event(calendar_uid, event_uid, **extra_args) 28 | -------------------------------------------------------------------------------- /inbox/heartbeat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/heartbeat/__init__.py -------------------------------------------------------------------------------- /inbox/mailsync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/mailsync/__init__.py -------------------------------------------------------------------------------- /inbox/mailsync/backends/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Per-provider backend modules. 3 | 4 | A backend module *must* meet the following requirements: 5 | 6 | 1. Specify the provider it implements as the module-level `PROVIDER` variable. 7 | For example, 'gmail', 'imap', 'eas', 'yahoo' etc. 8 | 9 | 2. Implement a sync monitor class which inherits from the 10 | BaseMailSyncMonitor class. 11 | 12 | 3. Specify the name of the sync monitor class as the module-level 13 | `SYNC_MONITOR_CLASS` variable. 14 | 15 | 4. The module may install a submodule tree of arbitrary depth, but the 16 | `PROVIDER` and `SYNC_MONITOR_CLS` variables must be defined in a direct 17 | submodule of `backends`, and that top-level module must import the 18 | referenced class. 19 | 20 | """ 21 | # Allow out-of-tree backend submodules. 22 | from pkgutil import extend_path 23 | __path__ = extend_path(__path__, __name__) 24 | from inbox.util.misc import register_backends 25 | module_registry = register_backends(__name__, __path__) 26 | -------------------------------------------------------------------------------- /inbox/mailsync/backends/imap/__init__.py: -------------------------------------------------------------------------------- 1 | from inbox.mailsync.backends.imap import common 2 | from inbox.mailsync.backends.imap.monitor import ImapSyncMonitor 3 | 4 | __all__ = ['common', 'ImapSyncMonitor'] 5 | 6 | 7 | PROVIDER = 'generic' 8 | SYNC_MONITOR_CLS = 'ImapSyncMonitor' 9 | -------------------------------------------------------------------------------- /inbox/models/backends/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Per-provider table modules. 3 | """ 4 | # Allow out-of-tree table submodules. 5 | from pkgutil import extend_path 6 | __path__ = extend_path(__path__, __name__) 7 | from inbox.util.misc import register_backends 8 | module_registry = register_backends(__name__, __path__) 9 | -------------------------------------------------------------------------------- /inbox/models/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, BigInteger 2 | from sqlalchemy.ext.declarative import as_declarative, declared_attr 3 | from sqlalchemy.orm.exc import DetachedInstanceError 4 | 5 | from inbox.models.mixins import CreatedAtMixin 6 | 7 | 8 | @as_declarative() 9 | class MailSyncBase(CreatedAtMixin): 10 | """ 11 | Provides automated table name, primary key column, and created_at timestamp. 12 | 13 | """ 14 | id = Column(BigInteger, primary_key=True, autoincrement=True) 15 | 16 | @declared_attr 17 | def __tablename__(cls): 18 | return cls.__name__.lower() 19 | 20 | @declared_attr 21 | def __table_args__(cls): 22 | return {'extend_existing': True} 23 | 24 | def __repr__(self): 25 | try: 26 | return "<{} (id: {})>".format(self.__module__ + "." + self.__class__.__name__, self.id) 27 | except DetachedInstanceError: 28 | # SQLAlchemy has expired all values for this object and is trying 29 | # to refresh them from the database, but has no session for the 30 | # refresh. 31 | return "<{} (id: detached)>".format(self.__module__ + "." + self.__class__.__name__) 32 | -------------------------------------------------------------------------------- /inbox/models/constants.py: -------------------------------------------------------------------------------- 1 | # Size constants, set in this file to avoid annoying circular import 2 | # errors 3 | MAX_INDEXABLE_LENGTH = 191 4 | MAX_FOLDER_NAME_LENGTH = MAX_INDEXABLE_LENGTH 5 | MAX_LABEL_NAME_LENGTH = MAX_INDEXABLE_LENGTH 6 | -------------------------------------------------------------------------------- /inbox/models/search.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, ForeignKey 2 | 3 | from inbox.models.base import MailSyncBase 4 | from inbox.models.mixins import UpdatedAtMixin, DeletedAtMixin 5 | from inbox.models.transaction import Transaction 6 | 7 | 8 | class ContactSearchIndexCursor(MailSyncBase, UpdatedAtMixin, 9 | DeletedAtMixin): 10 | """ 11 | Store the id of the last Transaction indexed into CloudSearch. 12 | Is namespace-agnostic. 13 | 14 | """ 15 | transaction_id = Column(ForeignKey(Transaction.id), nullable=True, 16 | index=True) 17 | -------------------------------------------------------------------------------- /inbox/s3/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/s3/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/s3/base.py: -------------------------------------------------------------------------------- 1 | def get_raw_from_provider(message): 2 | """Get the raw contents of a message from the provider.""" 3 | account = message.account 4 | return account.get_raw_message_contents(message) 5 | -------------------------------------------------------------------------------- /inbox/s3/exc.py: -------------------------------------------------------------------------------- 1 | class S3Exception(Exception): 2 | pass 3 | 4 | 5 | class EmailFetchException(S3Exception): 6 | pass 7 | 8 | 9 | class EmailDeletedException(EmailFetchException): 10 | """Raises an error when the message is deleted on the remote.""" 11 | pass 12 | 13 | 14 | class TemporaryEmailFetchException(EmailFetchException): 15 | """A class for temporary errors when trying to fetch emails. 16 | Exchange notably seems to need warming up before fetching data.""" 17 | pass 18 | -------------------------------------------------------------------------------- /inbox/scheduling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/scheduling/__init__.py -------------------------------------------------------------------------------- /inbox/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/search/__init__.py -------------------------------------------------------------------------------- /inbox/search/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | from inbox.util.misc import register_backends 5 | module_registry = register_backends(__name__, __path__) 6 | -------------------------------------------------------------------------------- /inbox/search/backends/generic.py: -------------------------------------------------------------------------------- 1 | from inbox.search.backends.imap import IMAPSearchClient 2 | 3 | __all__ = ['IMAPSearchClient'] 4 | 5 | PROVIDER = 'generic' 6 | SEARCH_CLS = 'IMAPSearchClient' 7 | -------------------------------------------------------------------------------- /inbox/search/base.py: -------------------------------------------------------------------------------- 1 | def get_search_client(account): 2 | from inbox.search.backends import module_registry 3 | 4 | search_mod = module_registry.get(account.provider) 5 | search_cls = getattr(search_mod, search_mod.SEARCH_CLS) 6 | search_client = search_cls(account) 7 | return search_client 8 | 9 | 10 | class SearchBackendException(Exception): 11 | """Raised if there's an error proxying the search request to the 12 | provider.""" 13 | 14 | def __init__(self, message, http_code, server_error=None): 15 | self.message = message 16 | self.http_code = http_code 17 | self.server_error = server_error 18 | super(SearchBackendException, self).__init__( 19 | message, http_code, server_error) 20 | 21 | 22 | class SearchStoreException(Exception): 23 | """Raised if there's an error proxying the search request to the provider. 24 | This is a special EAS case where the Status code for the Store element has 25 | an error""" 26 | def __init__(self, err_code): 27 | self.err_code = err_code 28 | super(SearchStoreException, self).__init__(err_code) 29 | -------------------------------------------------------------------------------- /inbox/security/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/security/__init__.py -------------------------------------------------------------------------------- /inbox/sendmail/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Per-provider backend modules for sending mail. 3 | 4 | A backend module *must* meet the following requirements: 5 | 6 | 1. Specify the provider it implements as the module-level `PROVIDER` variable. 7 | For example, 'gmail', 'eas' etc. 8 | 9 | 2. Specify the name of the sendmail class as the module-level 10 | `SENDMAIL_CLS` variable. 11 | 12 | """ 13 | # Allow out-of-tree backend submodules. 14 | from pkgutil import extend_path 15 | __path__ = extend_path(__path__, __name__) 16 | from inbox.util.misc import register_backends 17 | module_registry = register_backends(__name__, __path__) 18 | -------------------------------------------------------------------------------- /inbox/sendmail/generic.py: -------------------------------------------------------------------------------- 1 | from inbox.sendmail.smtp.postel import SMTPClient 2 | 3 | __all__ = ['SMTPClient'] 4 | 5 | PROVIDER = 'generic' 6 | SENDMAIL_CLS = 'SMTPClient' 7 | -------------------------------------------------------------------------------- /inbox/sendmail/gmail.py: -------------------------------------------------------------------------------- 1 | from inbox.sendmail.smtp.postel import SMTPClient 2 | 3 | __all__ = ['SMTPClient'] 4 | 5 | PROVIDER = 'gmail' 6 | SENDMAIL_CLS = 'SMTPClient' 7 | -------------------------------------------------------------------------------- /inbox/sendmail/smtp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/sendmail/smtp/__init__.py -------------------------------------------------------------------------------- /inbox/sqlalchemy_ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/sqlalchemy_ext/__init__.py -------------------------------------------------------------------------------- /inbox/sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/sync/__init__.py -------------------------------------------------------------------------------- /inbox/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/test/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/api/__init__.py -------------------------------------------------------------------------------- /inbox/test/auth/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/test/auth/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | -------------------------------------------------------------------------------- /inbox/test/conftest.py: -------------------------------------------------------------------------------- 1 | """ Fixtures don't go here; see util/base.py and friends. """ 2 | # Monkeypatch first, to prevent "AttributeError: 'module' object has no 3 | # attribute 'poll'" errors when tests import socket, then monkeypatch. 4 | from gevent import monkey 5 | monkey.patch_all(aggressive=False) 6 | 7 | import gevent_openssl 8 | gevent_openssl.monkey_patch() 9 | 10 | from inbox.test.util.base import * # noqa 11 | from inbox.util.testutils import (mock_imapclient, # noqa 12 | mock_smtp_get_connection, # noqa 13 | mock_dns_resolver, # noqa 14 | dump_dns_queries, # noqa 15 | files, # noqa 16 | uploaded_file_ids) # noqa 17 | -------------------------------------------------------------------------------- /inbox/test/data/.agignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /inbox/test/data/LetMeSendYouEmail.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/data/LetMeSendYouEmail.wav -------------------------------------------------------------------------------- /inbox/test/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/data/__init__.py -------------------------------------------------------------------------------- /inbox/test/data/andra-moi-ennepe.txt: -------------------------------------------------------------------------------- 1 | ἄνδρα μοι ἔννεπε, μοῦσα, πολύτροπον, ὃς μάλα πολλὰ 2 | πλάγχθη, ἐπεὶ Τροίης ἱερὸν πτολίεθρον ἔπερσεν: 3 | πολλῶν δ' ἀνθρώπων ἴδεν ἄστεα καὶ νόον ἔγνω, 4 | πολλὰ δ' ὅ γ' ἐν πόντῳ πάθεν ἄλγεα ὃν κατὰ θυμόν, 5 | ἀρνύμενος ἥν τε ψυχὴν καὶ νόστον ἑταίρων. 5 6 | ἀλλ' οὐδ' ὣς ἑτάρους ἐρρύσατο, ἱέμενός περ: 7 | αὐτῶν γὰρ σφετέρῃσιν ἀτασθαλίῃσιν ὄλοντο, 8 | νήπιοι, οἳ κατὰ βοῦς Ὑπερίονος Ἠελίοιο 9 | ἤσθιον: αὐτὰρ ὁ τοῖσιν ἀφείλετο νόστιμον ἦμαρ. 10 | τῶν ἁμόθεν γε, θεά, θύγατερ Διός, εἰπὲ καὶ ἡμῖν. 11 | -------------------------------------------------------------------------------- /inbox/test/data/long-non-ascii-filename.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /inbox/test/data/muir.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/data/muir.jpg -------------------------------------------------------------------------------- /inbox/test/data/piece-jointe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/data/piece-jointe.jpg -------------------------------------------------------------------------------- /inbox/test/data/raw_message_with_long_content_id.txt: -------------------------------------------------------------------------------- 1 | From: from@example.com 2 | To: to@example.com 3 | Subject: test 4 | Content-Type: multipart/mixed; 5 | boundary="Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD" 6 | 7 | 8 | --Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD 9 | Content-Disposition: attachment; 10 | filename=attachment.txt 11 | Content-Type: text/plain; 12 | name="attachment.txt" 13 | Content-Transfer-Encoding: 7bit 14 | Content-ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 15 | 16 | hi 17 | 18 | --Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD-- 19 | 20 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/bogus_sequence_number.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REQUEST 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252cccc@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:1407317367304 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/event_with_no_participants.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | DTSTAMP:20150312T181936Z 15 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 16 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 17 | JCXDA@imip.me.com 18 | END:VEVENT 19 | END:VCALENDAR 20 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/event_without_sequence.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | LOCATION:1\, Infinite Loop 9 | DTSTART;VALUE=DATE:20150316 10 | DTEND;VALUE=DATE:20150317 11 | TRANSP:TRANSPARENT 12 | LAST-MODIFIED:20150312T181936Z 13 | DTSTAMP:20150312T181936Z 14 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 15 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 16 | JCXDA@imip.me.com 17 | END:VEVENT 18 | END:VCALENDAR 19 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/google_cancelled1.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REQUEST 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T150000Z 9 | DTSTAMP:20150330T133948Z 10 | ORGANIZER;CN=Inbox Apptest:mailto:inboxapptest.french@gmail.com 11 | UID:c74p2nmutcd0kt69ku7rs8vu2g@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE 13 | ;CN=Inbox Apptest;X-NUM-GUESTS=0:mailto:inboxapptest.french@gmail.com 14 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= 15 | TRUE;CN=karim@nilas.com;X-NUM-GUESTS=0:mailto:karim@nilas.com 16 | CREATED:20150330T133947Z 17 | DESCRIPTION:View your event at https://www.google.com/calendar/event?action 18 | =VIEW&eid=Yzc0cDJubXV0Y2Qwa3Q2OWt1N3JzOHZ1Mmcga2FyaW1AbmlsYXMuY29t&tok=Mjkj 19 | aW5ib3hhcHB0ZXN0LmZyZW5jaEBnbWFpbC5jb202YTJlN2NjMDc1NWFhMTdmYzU5YzNkYjJkNGY 20 | wMDk4ZWUwNzU1ZThi&ctz=Europe/Paris&hl=en. 21 | LAST-MODIFIED:20150330T133948Z 22 | LOCATION: 23 | SEQUENCE:0 24 | STATUS:CONFIRMED 25 | SUMMARY:Buffalo Buffalo 26 | TRANSP:OPAQUE 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/google_cancelled2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:CANCEL 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T150000Z 9 | DTSTAMP:20150330T134102Z 10 | ORGANIZER;CN=Inbox Apptest:mailto:inboxapptest.french@gmail.com 11 | UID:c74p2nmutcd0kt69ku7rs8vu2g@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Inbox 13 | Apptest;X-NUM-GUESTS=0:mailto:inboxapptest.french@gmail.com 14 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ka 15 | rim@nilas.com;X-NUM-GUESTS=0:mailto:karim@nilas.com 16 | CREATED:20150330T133947Z 17 | DESCRIPTION: 18 | LAST-MODIFIED:20150330T134102Z 19 | LOCATION: 20 | SEQUENCE:1 21 | STATUS:CANCELLED 22 | SUMMARY:Buffalo Buffalo 23 | TRANSP:OPAQUE 24 | END:VEVENT 25 | END:VCALENDAR 26 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/icloud_oneday_event.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto: 15 | karim@nilas.com 16 | ATTENDEE;ROLE=CHAIR;CN=Ben Bitdiddle;PARTSTAT=ACCEPTED; 17 | EMAIL=benbitdiddle@icloud.com:mailto:benbitdiddle@icloud.com 18 | DTSTAMP:20150312T181936Z 19 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 20 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 21 | JCXDA@imip.me.com 22 | END:VEVENT 23 | END:VCALENDAR 24 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/invalid_rsvp.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252$cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/invalid_rsvp2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252cccc@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/invite_w_rsvps2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T131419Z 10 | ORGANIZER;CN=Test2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Inbox 13 | Apptest;X-NUM-GUESTS=0:mailto:test1@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T131419Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/invite_w_rsvps3.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/invite_w_rsvps_4.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/meetup_infinite.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Meetup//Meetup Events v1.0//EN 3 | X-ORIGINAL-URL:http://www.meetup.com/sfgamedevelopers/events/16078654/ 4 | VERSION:2.0 5 | CALSCALE:GREGORIAN 6 | UID:nih2h78am1cb1uqetgfkslrfrc@meetup.com 7 | METHOD:PUBLISH 8 | BEGIN:VEVENT 9 | DTSTAMP:20110418T211031Z 10 | DTSTART;TZID=America/Los_Angeles:20110419T170000 11 | SUMMARY:Bay Area Video Game Development Meetup Group Monthly Meetup 12 | URL:http://www.meetup.com/sfgamedevelopers/events/16078654/ 13 | LOCATION:Jillian's (Metreon (101 4th St.)\, San Francisco\, CA 94101) 14 | SUMMARY:Bay Area Video Game Development Meetup Group Monthly Meetup 15 | DESCRIPTION:Bay Area Video Game Development Meetup Group\nTuesday\, April 19 at 8:00 PM\n\nHey Gamers\,\n\nWe will be meeting again this month at Jillian's. We will do our best to get the front lounge area near the entrance. If you do not see...\n\nDetails: http://www.meetup.com/sfgamedevelopers/events/16078654/ 16 | ORGANIZER;CN=Meetup Reminder:MAILTO:info@meetup.com 17 | END:VEVENT 18 | END:VCALENDAR 19 | -------------------------------------------------------------------------------- /inbox/test/events/fixtures/multiple_events.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | DTSTAMP:20150312T181936Z 15 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 16 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 17 | JCXDA@imip.me.com 18 | END:VEVENT 19 | 20 | BEGIN:VEVENT 21 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 22 | SUMMARY:More meetings 23 | SEQUENCE:0 24 | LOCATION:1\, Infinite Loop 25 | DTSTART;VALUE=DATE:20150317 26 | DTEND;VALUE=DATE:20150318 27 | TRANSP:TRANSPARENT 28 | LAST-MODIFIED:20150312T181936Z 29 | DTSTAMP:20150312T181936Z 30 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 31 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 32 | JCXDA@imip.me.com 33 | END:VEVENT 34 | 35 | END:VCALENDAR 36 | -------------------------------------------------------------------------------- /inbox/test/general/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/general/__init__.py -------------------------------------------------------------------------------- /inbox/test/general/test_address_canonicalization.py: -------------------------------------------------------------------------------- 1 | def test_canonicalization(db): 2 | from inbox.models import Namespace, Account 3 | ns = Namespace() 4 | account = Account(namespace=ns, 5 | email_address='lambda.the.ultimate@gmail.com') 6 | db.session.add(account) 7 | db.session.add(ns) 8 | db.session.commit() 9 | assert account.email_address == 'lambda.the.ultimate@gmail.com' 10 | 11 | assert db.session.query(Account). \ 12 | filter_by(email_address='lambdatheultimate@gmail.com').count() == 1 13 | 14 | assert db.session.query(Account). \ 15 | filter_by(email_address='lambda.theultimate@gmail.com').count() == 1 16 | 17 | # Check that nothing bad happens if you pass something that can't actually 18 | # be parsed as an email address. 19 | assert db.session.query(Account). \ 20 | filter_by(email_address='foo').count() == 0 21 | # Flanker will parse hostnames too, don't break on that. 22 | assert db.session.query(Account). \ 23 | filter_by(email_address='http://example.com').count() == 0 24 | -------------------------------------------------------------------------------- /inbox/test/general/test_draft_creation.py: -------------------------------------------------------------------------------- 1 | from inbox.sendmail.base import create_message_from_json, update_draft 2 | 3 | 4 | def test_headers_presence(default_namespace, db): 5 | data = {'subject': 'test draft', 'to': [{'email': 'karim@nylas.com'}]} 6 | draft = create_message_from_json(data, default_namespace, db.session, 7 | False) 8 | 9 | assert draft.nylas_uid is not None 10 | assert draft.message_id_header is not None 11 | 12 | old_uid = draft.nylas_uid 13 | 14 | update_draft(db.session, default_namespace.account, draft, 15 | body="updated body", blocks=[]) 16 | 17 | assert draft.nylas_uid is not None 18 | assert draft.message_id_header is not None 19 | assert draft.nylas_uid != old_uid 20 | -------------------------------------------------------------------------------- /inbox/test/general/test_html_parsing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Regression tests for HTML parsing.""" 3 | from inbox.util.html import strip_tags 4 | 5 | 6 | def test_strip_tags(): 7 | text = ('
' 8 | 'check out this link yo!
') 9 | assert strip_tags(text).strip() == 'check out this link yo!' 10 | 11 | 12 | def test_preserve_refs(): 13 | """Test that HTML character/entity references are preserved when we strip 14 | tags.""" 15 | text = u'la philologie mène au pire' 16 | assert strip_tags(text) == u'la philologie mène au pire' 17 | 18 | text = u'la philologie mène au pire' 19 | assert strip_tags(text) == u'la philologie mène au pire' 20 | 21 | text = u'veer & wander' 22 | assert strip_tags(text) == 'veer & wander' 23 | -------------------------------------------------------------------------------- /inbox/test/general/test_provider_export.py: -------------------------------------------------------------------------------- 1 | from inbox.providers import providers 2 | import json 3 | 4 | 5 | def test_provider_export_as_json(): 6 | """Provider dict should be exportable as json""" 7 | assert json.dumps(dict(providers)) 8 | -------------------------------------------------------------------------------- /inbox/test/imap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/imap/__init__.py -------------------------------------------------------------------------------- /inbox/test/imap/network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/imap/network/__init__.py -------------------------------------------------------------------------------- /inbox/test/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | from inbox.util.misc import register_backends 5 | module_registry = register_backends(__name__, __path__) 6 | -------------------------------------------------------------------------------- /inbox/test/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = imap/network eas/network data system 3 | -------------------------------------------------------------------------------- /inbox/test/scheduling/conftest.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa: F401 2 | from inbox.test.util.base import dbloader, db, default_account 3 | -------------------------------------------------------------------------------- /inbox/test/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/search/__init__.py -------------------------------------------------------------------------------- /inbox/test/search/conftest.py: -------------------------------------------------------------------------------- 1 | from inbox.test.util.base import (config, db, absolute_path, 2 | default_namespace) 3 | from inbox.test.api.base import api_client 4 | 5 | __all__ = ['config', 'db', 'absolute_path', 'default_namespace', 6 | 'api_client'] 7 | -------------------------------------------------------------------------------- /inbox/test/system/.gitignore: -------------------------------------------------------------------------------- 1 | accounts.py 2 | -------------------------------------------------------------------------------- /inbox/test/system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/system/__init__.py -------------------------------------------------------------------------------- /inbox/test/system/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from inbox import APIClient 3 | 4 | 5 | class NylasTestClient(APIClient): 6 | 7 | def __init__(self, email_address=None, api_base=os.getenv("INBOX_API_PORT_5555_TCP_ADDR", "http://localhost:5555")): 8 | self.email_address = email_address 9 | APIClient.__init__(self, None, None, None, api_base) 10 | 11 | @property 12 | def namespaces(self): 13 | all_ns = super(NylasTestClient, self).namespaces 14 | if self.email_address: 15 | return all_ns.where(email_address=self.email_address) 16 | else: 17 | return all_ns 18 | -------------------------------------------------------------------------------- /inbox/test/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/util/__init__.py -------------------------------------------------------------------------------- /inbox/test/util/crispin.py: -------------------------------------------------------------------------------- 1 | # NOT a fixture because it needs args 2 | def crispin_client(account_id, account_provider): 3 | from inbox.crispin import connection_pool 4 | return connection_pool(account_id, pool_size=1).get() 5 | -------------------------------------------------------------------------------- /inbox/test/webhooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/test/webhooks/__init__.py -------------------------------------------------------------------------------- /inbox/transactions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/transactions/__init__.py -------------------------------------------------------------------------------- /inbox/util/__init__.py: -------------------------------------------------------------------------------- 1 | """ Non-server-specific utility modules. These shouldn't depend on any code 2 | from the inbox module tree! 3 | 4 | Don't add new code here! Find the relevant submodule, or use misc.py if 5 | there's really no other place. 6 | """ 7 | # Allow out-of-tree submodules. 8 | from pkgutil import extend_path 9 | __path__ = extend_path(__path__, __name__) 10 | -------------------------------------------------------------------------------- /inbox/util/encoding.py: -------------------------------------------------------------------------------- 1 | def base36encode(number): 2 | if not isinstance(number, (int, long)): 3 | raise TypeError('number must be an integer') 4 | if number < 0: 5 | raise ValueError('number must be positive') 6 | 7 | alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 8 | 9 | base36 = '' 10 | while number: 11 | number, i = divmod(number, 36) 12 | base36 = alphabet[i] + base36 13 | 14 | return base36 or alphabet[0] 15 | 16 | 17 | def base36decode(number): 18 | return int(number, 36) 19 | 20 | 21 | def unicode_safe_truncate(s, max_length): 22 | """ 23 | Implements unicode-safe truncation and trims whitespace for a given input 24 | string, number or unicode string. 25 | """ 26 | if not isinstance(s, unicode): 27 | s = str(s).decode('utf-8', 'ignore') 28 | return s.rstrip()[:max_length] 29 | -------------------------------------------------------------------------------- /inbox/util/itert.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | 4 | def chunk(iterable, size): 5 | """ Yield chunks of an iterable. 6 | 7 | If len(iterable) is not evenly divisible by size, the last chunk will 8 | be shorter than size. 9 | """ 10 | it = iter(iterable) 11 | while True: 12 | group = tuple(itertools.islice(it, None, size)) 13 | if not group: 14 | break 15 | yield group 16 | 17 | 18 | def partition(pred, iterable): 19 | """ Use a predicate to partition entries into false entries and true 20 | entries. 21 | 22 | e.g.: 23 | 24 | partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 25 | """ 26 | t1, t2 = itertools.tee(iterable) 27 | return list(itertools.ifilterfalse(pred, t1)), filter(pred, t2) 28 | 29 | 30 | def flatten(iterable): 31 | # Flatten a list of lists. 32 | # http://stackoverflow.com/a/953097 33 | return list(itertools.chain(*iterable)) 34 | -------------------------------------------------------------------------------- /inbox/util/stats.py: -------------------------------------------------------------------------------- 1 | import statsd 2 | 3 | from inbox.config import config 4 | 5 | 6 | def get_statsd_client(): 7 | return statsd.StatsClient( 8 | str(config.get("STATSD_HOST", "localhost")), 9 | config.get("STATSD_PORT", 8125), 10 | prefix=config.get("STATSD_PREFIX", "stats")) 11 | 12 | statsd_client = get_statsd_client() 13 | -------------------------------------------------------------------------------- /inbox/webhooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/inbox/webhooks/__init__.py -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /migrations/versions/000_g_msgid_g_thrid_as_integers.py: -------------------------------------------------------------------------------- 1 | """ Store g_msgid and g_thrid as integers, not strings. For more efficiency. 2 | 3 | Revision ID: 2605b23e1fe6 4 | Revises: None 5 | Create Date: 2014-03-04 00:34:31.817332 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2605b23e1fe6' 11 | down_revision = None 12 | 13 | from alembic import op 14 | 15 | from sqlalchemy.dialects import mysql 16 | 17 | 18 | def upgrade(): 19 | op.alter_column('message', 'g_msgid', type_=mysql.BIGINT) 20 | op.alter_column('message', 'g_thrid', type_=mysql.BIGINT) 21 | 22 | op.create_index('ix_message_g_msgid', 'message', ['g_msgid'], unique=False) 23 | op.create_index('ix_message_g_thrid', 'message', ['g_thrid'], unique=False) 24 | 25 | 26 | def downgrade(): 27 | op.alter_column('message', 'g_msgid', type_=mysql.VARCHAR(40)) 28 | op.alter_column('message', 'g_thrid', type_=mysql.VARCHAR(40)) 29 | 30 | op.drop_index('ix_message_g_thrid', table_name='message') 31 | op.drop_index('ix_message_g_msgid', table_name='message') 32 | -------------------------------------------------------------------------------- /migrations/versions/001_rename_message_id_to_message_id_header.py: -------------------------------------------------------------------------------- 1 | """rename message_id to message_id_header 2 | 3 | Revision ID: 217431caacc7 4 | Revises: 2605b23e1fe6 5 | Create Date: 2014-03-04 06:51:13.008195 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '217431caacc7' 11 | down_revision = '2605b23e1fe6' 12 | 13 | from alembic import op 14 | 15 | 16 | def upgrade(): 17 | op.execute("ALTER TABLE message CHANGE message_id message_id_header VARCHAR(255) NULL") 18 | 19 | 20 | def downgrade(): 21 | # First make all current NULL values actually 0. This isn't a great solution, but it works. 22 | print "WARNING: This removes data about messages that do not contain a Message-Id header!" 23 | op.execute("UPDATE message SET message_id_header=0 WHERE message_id_header IS NULL") 24 | op.execute("ALTER TABLE message CHANGE message_id_header message_id VARCHAR(255) NOT NULL") 25 | -------------------------------------------------------------------------------- /migrations/versions/002_store_g_thrid_as_biginteger_instead_of_.py: -------------------------------------------------------------------------------- 1 | """ Change g_thrid as BigInteger instead of string 2 | 3 | Revision ID: 297aa1e1acc7 4 | Revises: 217431caacc7 5 | Create Date: 2014-03-05 19:44:58.323666 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '297aa1e1acc7' 11 | down_revision = '217431caacc7' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from sqlalchemy.dialects import mysql 16 | 17 | 18 | def upgrade(): 19 | op.alter_column('thread', 'g_thrid', type_=mysql.BIGINT) 20 | op.execute('OPTIMIZE TABLE thread') 21 | 22 | 23 | def downgrade(): 24 | op.alter_column('thread', 'g_thrid', type_=sa.String(255)) 25 | -------------------------------------------------------------------------------- /migrations/versions/003_expand_littlejson.py: -------------------------------------------------------------------------------- 1 | """expand LittleJSON 2 | 3 | Revision ID: 269247bc37d3 4 | Revises: 297aa1e1acc7 5 | Create Date: 2014-03-06 19:11:31.079427 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '269247bc37d3' 11 | down_revision = '297aa1e1acc7' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.alter_column('imapuid', 'extra_flags', type_=sa.String(255)) 19 | 20 | 21 | def downgrade(): 22 | op.alter_column('imapuid', 'extra_flags', type_=sa.String(40)) 23 | -------------------------------------------------------------------------------- /migrations/versions/004_drafts_as_required_folder.py: -------------------------------------------------------------------------------- 1 | """Drafts as required folder 2 | 3 | Revision ID: 41a7e825d108 4 | Revises: 269247bc37d3 5 | Create Date: 2014-03-13 21:14:25.652333 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '41a7e825d108' 11 | down_revision = '269247bc37d3' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.add_column('imapaccount', sa.Column('drafts_folder_name', sa.String(255), nullable=True)) 19 | 20 | 21 | def downgrade(): 22 | op.drop_column('imapaccount', 'drafts_folder_name') 23 | -------------------------------------------------------------------------------- /migrations/versions/006_add_search_tokens.py: -------------------------------------------------------------------------------- 1 | """Add search tokens. 2 | 3 | Revision ID: 482338e7a7d6 4 | Revises: 41a7e825d108 5 | Create Date: 2014-03-18 00:16:49.525732 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '482338e7a7d6' 11 | down_revision = 'adc646e1f11' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.create_table( 19 | 'searchtoken', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('token', sa.String(length=255), nullable=True), 22 | sa.Column('source', sa.Enum('name', 'email_address'), nullable=True), 23 | sa.Column('contact_id', sa.Integer(), nullable=True), 24 | sa.ForeignKeyConstraint(['contact_id'], ['contact.id'], 25 | ondelete='CASCADE'), 26 | sa.PrimaryKeyConstraint('id') 27 | ) 28 | 29 | 30 | def downgrade(): 31 | op.drop_table('searchtoken') 32 | -------------------------------------------------------------------------------- /migrations/versions/010_store_raw_contact_data.py: -------------------------------------------------------------------------------- 1 | """Store raw contact data. 2 | 3 | Revision ID: 3b511977a01f 4 | Revises: 169cac0cd87e 5 | Create Date: 2014-04-16 15:36:22.188971 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '3b511977a01f' 11 | down_revision = '169cac0cd87e' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.add_column('contact', sa.Column('raw_data', sa.Text(), nullable=True)) 19 | 20 | 21 | def downgrade(): 22 | op.drop_column('contact', 'raw_data') 23 | -------------------------------------------------------------------------------- /migrations/versions/014_contact_ranking_signals.py: -------------------------------------------------------------------------------- 1 | """contact ranking signals 2 | 3 | Revision ID: 563d405d1f99 4 | Revises: 169cac0cd87e 5 | Create Date: 2014-04-17 19:32:37.715207 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '563d405d1f99' 11 | down_revision = 'f7dbd9bf4a6' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.create_table( 19 | 'searchsignal', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('name', sa.String(length=40), nullable=True), 22 | sa.Column('value', sa.Integer(), nullable=True), 23 | sa.Column('contact_id', sa.Integer(), nullable=False), 24 | sa.ForeignKeyConstraint(['contact_id'], ['contact.id'], 25 | ondelete='CASCADE'), 26 | sa.PrimaryKeyConstraint('id') 27 | ) 28 | op.add_column('contact', sa.Column('score', sa.Integer(), nullable=True)) 29 | 30 | 31 | def downgrade(): 32 | op.drop_column('contact', 'score') 33 | op.drop_table('searchsignal') 34 | -------------------------------------------------------------------------------- /migrations/versions/016_extra_transaction_data.py: -------------------------------------------------------------------------------- 1 | """extra transaction data 2 | 3 | Revision ID: 5093433b073 4 | Revises: 3fee2f161614 5 | Create Date: 2014-04-25 23:23:36.442325 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '5093433b073' 11 | down_revision = '3fee2f161614' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.add_column('transaction', sa.Column('additional_data', sa.Text(4194304), 19 | nullable=True)) 20 | 21 | 22 | def downgrade(): 23 | op.drop_column('transaction', 'additional_data') 24 | -------------------------------------------------------------------------------- /migrations/versions/021_add_references_column_to_message_table.py: -------------------------------------------------------------------------------- 1 | """Add references column to message table 2 | 3 | Revision ID: 4fd291c6940c 4 | Revises: 10ef1d46f016 5 | Create Date: 2014-04-25 00:51:04.825531 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '4fd291c6940c' 11 | down_revision = '10ef1d46f016' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | from inbox.sqlalchemy_ext.util import JSON 19 | 20 | op.add_column('message', sa.Column('references', JSON, nullable=True)) 21 | 22 | 23 | def downgrade(): 24 | op.drop_column('message', 'references') 25 | -------------------------------------------------------------------------------- /migrations/versions/022_store_imapuid_msg_uid_as_biginteger_.py: -------------------------------------------------------------------------------- 1 | """Store ImapUid 2 | Date: Wed, 11 Mar 1992 16:27:37 -0500 (EST) 3 | From: Nathaniel Borenstein 4 | Mime-Version: 1.0 5 | To: ietf-822@ietf.org, info-metamail@thumper.bellcore.com 6 | Subject: Barbershop MIME 7 | 8 | Those of you not running MIME-compliant mail readers won't get a lot out 9 | of this, nor will those without ftp access to the Internet, but for the 10 | lucky few.... 11 | 12 | Here are the infamous Telephone Chords, the world's premier (=only) 13 | all-Bellcore barbershop quartet, singing about MIME. Note that because 14 | the "message/external-body" MIME construct is used, this whole message 15 | is only about 3000 bytes -- at least, until you start reading it. :-) 16 | -------------------------------------------------------------------------------- /tests/data/long-non-ascii-filename.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/data/muir.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/data/muir.jpg -------------------------------------------------------------------------------- /tests/data/piece-jointe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/data/piece-jointe.jpg -------------------------------------------------------------------------------- /tests/data/raw_message_with_long_content_id: -------------------------------------------------------------------------------- 1 | From: from@example.com 2 | To: to@example.com 3 | Subject: test 4 | Content-Type: multipart/mixed; 5 | boundary="Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD" 6 | 7 | 8 | --Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD 9 | Content-Disposition: attachment; 10 | filename=attachment.txt 11 | Content-Type: text/plain; 12 | name="attachment.txt" 13 | Content-Transfer-Encoding: 7bit 14 | Content-ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 15 | 16 | hi 17 | 18 | --Apple-Mail=_A5779F7D-F2DA-42C3-BE13-48D5C62A02BD-- 19 | 20 | -------------------------------------------------------------------------------- /tests/data/test_folders.shelf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/data/test_folders.shelf -------------------------------------------------------------------------------- /tests/events/fixtures/bogus_sequence_number.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REQUEST 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252cccc@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:1407317367304 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/event_with_no_participants.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | DTSTAMP:20150312T181936Z 15 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 16 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 17 | JCXDA@imip.me.com 18 | END:VEVENT 19 | END:VCALENDAR 20 | -------------------------------------------------------------------------------- /tests/events/fixtures/event_without_sequence.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | LOCATION:1\, Infinite Loop 9 | DTSTART;VALUE=DATE:20150316 10 | DTEND;VALUE=DATE:20150317 11 | TRANSP:TRANSPARENT 12 | LAST-MODIFIED:20150312T181936Z 13 | DTSTAMP:20150312T181936Z 14 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 15 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 16 | JCXDA@imip.me.com 17 | END:VEVENT 18 | END:VCALENDAR 19 | -------------------------------------------------------------------------------- /tests/events/fixtures/google_cancelled1.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REQUEST 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T150000Z 9 | DTSTAMP:20150330T133948Z 10 | ORGANIZER;CN=Inbox Apptest:mailto:inboxapptest.french@gmail.com 11 | UID:c74p2nmutcd0kt69ku7rs8vu2g@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE 13 | ;CN=Inbox Apptest;X-NUM-GUESTS=0:mailto:inboxapptest.french@gmail.com 14 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= 15 | TRUE;CN=karim@nilas.com;X-NUM-GUESTS=0:mailto:karim@nilas.com 16 | CREATED:20150330T133947Z 17 | DESCRIPTION:View your event at https://www.google.com/calendar/event?action 18 | =VIEW&eid=Yzc0cDJubXV0Y2Qwa3Q2OWt1N3JzOHZ1Mmcga2FyaW1AbmlsYXMuY29t&tok=Mjkj 19 | aW5ib3hhcHB0ZXN0LmZyZW5jaEBnbWFpbC5jb202YTJlN2NjMDc1NWFhMTdmYzU5YzNkYjJkNGY 20 | wMDk4ZWUwNzU1ZThi&ctz=Europe/Paris&hl=en. 21 | LAST-MODIFIED:20150330T133948Z 22 | LOCATION: 23 | SEQUENCE:0 24 | STATUS:CONFIRMED 25 | SUMMARY:Buffalo Buffalo 26 | TRANSP:OPAQUE 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /tests/events/fixtures/google_cancelled2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:CANCEL 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T150000Z 9 | DTSTAMP:20150330T134102Z 10 | ORGANIZER;CN=Inbox Apptest:mailto:inboxapptest.french@gmail.com 11 | UID:c74p2nmutcd0kt69ku7rs8vu2g@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Inbox 13 | Apptest;X-NUM-GUESTS=0:mailto:inboxapptest.french@gmail.com 14 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ka 15 | rim@nilas.com;X-NUM-GUESTS=0:mailto:karim@nilas.com 16 | CREATED:20150330T133947Z 17 | DESCRIPTION: 18 | LAST-MODIFIED:20150330T134102Z 19 | LOCATION: 20 | SEQUENCE:1 21 | STATUS:CANCELLED 22 | SUMMARY:Buffalo Buffalo 23 | TRANSP:OPAQUE 24 | END:VEVENT 25 | END:VCALENDAR 26 | -------------------------------------------------------------------------------- /tests/events/fixtures/icloud_oneday_event.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto: 15 | karim@nilas.com 16 | ATTENDEE;ROLE=CHAIR;CN=Ben Bitdiddle;PARTSTAT=ACCEPTED; 17 | EMAIL=benbitdiddle@icloud.com:mailto:benbitdiddle@icloud.com 18 | DTSTAMP:20150312T181936Z 19 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 20 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 21 | JCXDA@imip.me.com 22 | END:VEVENT 23 | END:VCALENDAR 24 | -------------------------------------------------------------------------------- /tests/events/fixtures/invalid_rsvp.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252$cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/invalid_rsvp2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:234252cccc@google.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/invite_w_rsvps2.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T131419Z 10 | ORGANIZER;CN=Test2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Inbox 13 | Apptest;X-NUM-GUESTS=0:mailto:test1@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T131419Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/invite_w_rsvps3.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/invite_w_rsvps_4.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:REPLY 6 | BEGIN:VEVENT 7 | DTSTART:20150401T140000Z 8 | DTEND:20150401T143000Z 9 | DTSTAMP:20150331T124344Z 10 | ORGANIZER;CN=Inbox Apptest2:mailto:test2@example.com 11 | UID:cccc@nylas.com 12 | ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=DECLINED;CN=Karim 13 | Hamidou;X-NUM-GUESTS=0:mailto:karim@example.com 14 | CREATED:20150331T124302Z 15 | DESCRIPTION: 16 | LAST-MODIFIED:20150331T124344Z 17 | LOCATION: 18 | SEQUENCE:0 19 | STATUS:CONFIRMED 20 | SUMMARY:Spot le chien 21 | TRANSP:OPAQUE 22 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 23 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 24 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 25 | X-MICROSOFT-CDO-IMPORTANCE:1 26 | X-MICROSOFT-CDO-INSTTYPE:0 27 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 28 | X-MICROSOFT-CDO-OWNERAPPTID:2113142777 29 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/events/fixtures/meetup_infinite.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Meetup//Meetup Events v1.0//EN 3 | X-ORIGINAL-URL:http://www.meetup.com/sfgamedevelopers/events/16078654/ 4 | VERSION:2.0 5 | CALSCALE:GREGORIAN 6 | UID:nih2h78am1cb1uqetgfkslrfrc@meetup.com 7 | METHOD:PUBLISH 8 | BEGIN:VEVENT 9 | DTSTAMP:20110418T211031Z 10 | DTSTART;TZID=America/Los_Angeles:20110419T170000 11 | SUMMARY:Bay Area Video Game Development Meetup Group Monthly Meetup 12 | URL:http://www.meetup.com/sfgamedevelopers/events/16078654/ 13 | LOCATION:Jillian's (Metreon (101 4th St.)\, San Francisco\, CA 94101) 14 | SUMMARY:Bay Area Video Game Development Meetup Group Monthly Meetup 15 | DESCRIPTION:Bay Area Video Game Development Meetup Group\nTuesday\, April 19 at 8:00 PM\n\nHey Gamers\,\n\nWe will be meeting again this month at Jillian's. We will do our best to get the front lounge area near the entrance. If you do not see...\n\nDetails: http://www.meetup.com/sfgamedevelopers/events/16078654/ 16 | ORGANIZER;CN=Meetup Reminder:MAILTO:info@meetup.com 17 | END:VEVENT 18 | END:VCALENDAR 19 | -------------------------------------------------------------------------------- /tests/events/fixtures/multiple_events.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN 4 | METHOD:REQUEST 5 | BEGIN:VEVENT 6 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 7 | SUMMARY:An all-day meeting about meetings 8 | SEQUENCE:0 9 | LOCATION:1\, Infinite Loop 10 | DTSTART;VALUE=DATE:20150316 11 | DTEND;VALUE=DATE:20150317 12 | TRANSP:TRANSPARENT 13 | LAST-MODIFIED:20150312T181936Z 14 | DTSTAMP:20150312T181936Z 15 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 16 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 17 | JCXDA@imip.me.com 18 | END:VEVENT 19 | 20 | BEGIN:VEVENT 21 | UID:8153F823-B9F1-4BE0-ADFB-5FEEB01C08A9 22 | SUMMARY:More meetings 23 | SEQUENCE:0 24 | LOCATION:1\, Infinite Loop 25 | DTSTART;VALUE=DATE:20150317 26 | DTEND;VALUE=DATE:20150318 27 | TRANSP:TRANSPARENT 28 | LAST-MODIFIED:20150312T181936Z 29 | DTSTAMP:20150312T181936Z 30 | ORGANIZER;CN=Ben Bitdiddle;EMAIL=benbitdiddle@icloud.com:mailto: 31 | 2_HEZDSOJZGEZTSMJZGI4TSOJRGNOIFYHPYTDQMCIAF5U2J7KGUYDTWMZSMEX4QJ23ABSXJO6R 32 | JCXDA@imip.me.com 33 | END:VEVENT 34 | 35 | END:VCALENDAR 36 | -------------------------------------------------------------------------------- /tests/general/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/general/__init__.py -------------------------------------------------------------------------------- /tests/general/test_address_canonicalization.py: -------------------------------------------------------------------------------- 1 | def test_canonicalization(db): 2 | from inbox.models import Namespace, Account 3 | ns = Namespace() 4 | account = Account(namespace=ns, 5 | email_address='lambda.the.ultimate@gmail.com') 6 | db.session.add(account) 7 | db.session.add(ns) 8 | db.session.commit() 9 | assert account.email_address == 'lambda.the.ultimate@gmail.com' 10 | 11 | assert db.session.query(Account). \ 12 | filter_by(email_address='lambdatheultimate@gmail.com').count() == 1 13 | 14 | assert db.session.query(Account). \ 15 | filter_by(email_address='lambda.theultimate@gmail.com').count() == 1 16 | 17 | # Check that nothing bad happens if you pass something that can't actually 18 | # be parsed as an email address. 19 | assert db.session.query(Account). \ 20 | filter_by(email_address='foo').count() == 0 21 | # Flanker will parse hostnames too, don't break on that. 22 | assert db.session.query(Account). \ 23 | filter_by(email_address='http://example.com').count() == 0 24 | -------------------------------------------------------------------------------- /tests/general/test_draft_creation.py: -------------------------------------------------------------------------------- 1 | from inbox.sendmail.base import create_message_from_json, update_draft 2 | 3 | 4 | def test_headers_presence(default_namespace, db): 5 | data = {'subject': 'test draft', 'to': [{'email': 'karim@nylas.com'}]} 6 | draft = create_message_from_json(data, default_namespace, db.session, 7 | False) 8 | 9 | assert draft.nylas_uid is not None 10 | assert draft.message_id_header is not None 11 | 12 | old_uid = draft.nylas_uid 13 | 14 | update_draft(db.session, default_namespace.account, draft, 15 | body="updated body", blocks=[]) 16 | 17 | assert draft.nylas_uid is not None 18 | assert draft.message_id_header is not None 19 | assert draft.nylas_uid != old_uid 20 | -------------------------------------------------------------------------------- /tests/general/test_html_parsing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Regression tests for HTML parsing.""" 3 | from inbox.util.html import strip_tags 4 | 5 | 6 | def test_strip_tags(): 7 | text = ('
' 8 | 'check out this link yo!
') 9 | assert strip_tags(text).strip() == 'check out this link yo!' 10 | 11 | 12 | def test_preserve_refs(): 13 | """Test that HTML character/entity references are preserved when we strip 14 | tags.""" 15 | text = u'la philologie mène au pire' 16 | assert strip_tags(text) == u'la philologie mène au pire' 17 | 18 | text = u'la philologie mène au pire' 19 | assert strip_tags(text) == u'la philologie mène au pire' 20 | 21 | text = u'veer & wander' 22 | assert strip_tags(text) == 'veer & wander' 23 | -------------------------------------------------------------------------------- /tests/general/test_provider_export.py: -------------------------------------------------------------------------------- 1 | from inbox.providers import providers 2 | import json 3 | 4 | 5 | def test_provider_export_as_json(): 6 | """Provider dict should be exportable as json""" 7 | assert json.dumps(dict(providers)) 8 | -------------------------------------------------------------------------------- /tests/imap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/imap/__init__.py -------------------------------------------------------------------------------- /tests/imap/network/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/imap/network/__init__.py -------------------------------------------------------------------------------- /tests/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Allow out-of-tree backend submodules. 2 | from pkgutil import extend_path 3 | __path__ = extend_path(__path__, __name__) 4 | from inbox.util.misc import register_backends 5 | module_registry = register_backends(__name__, __path__) 6 | -------------------------------------------------------------------------------- /tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = imap/network data system s3 3 | -------------------------------------------------------------------------------- /tests/scheduling/conftest.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa: F401 2 | from tests.util.base import dbloader, db, default_account 3 | -------------------------------------------------------------------------------- /tests/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/search/__init__.py -------------------------------------------------------------------------------- /tests/search/conftest.py: -------------------------------------------------------------------------------- 1 | from tests.util.base import (config, db, absolute_path, 2 | default_namespace) 3 | from tests.api.base import api_client 4 | 5 | __all__ = ['config', 'db', 'absolute_path', 'default_namespace', 6 | 'api_client'] 7 | -------------------------------------------------------------------------------- /tests/system/.gitignore: -------------------------------------------------------------------------------- 1 | accounts.py 2 | -------------------------------------------------------------------------------- /tests/system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/system/__init__.py -------------------------------------------------------------------------------- /tests/system/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from inbox import APIClient 3 | 4 | 5 | class NylasTestClient(APIClient): 6 | 7 | def __init__(self, email_address=None, api_base=os.getenv("INBOX_API_PORT_5555_TCP_ADDR", "http://localhost:5555")): 8 | self.email_address = email_address 9 | APIClient.__init__(self, None, None, None, api_base) 10 | 11 | @property 12 | def namespaces(self): 13 | all_ns = super(NylasTestClient, self).namespaces 14 | if self.email_address: 15 | return all_ns.where(email_address=self.email_address) 16 | else: 17 | return all_ns 18 | -------------------------------------------------------------------------------- /tests/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/util/__init__.py -------------------------------------------------------------------------------- /tests/util/crispin.py: -------------------------------------------------------------------------------- 1 | # NOT a fixture because it needs args 2 | def crispin_client(account_id, account_provider): 3 | from inbox.crispin import connection_pool 4 | return connection_pool(account_id, pool_size=1).get() 5 | -------------------------------------------------------------------------------- /tests/webhooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/sync-engine/b91b94b9a0033be4199006eb234d270779a04443/tests/webhooks/__init__.py -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | ; tox aims to automate and standardize testing in Python. 2 | ; Documentation: https://tox.readthedocs.org/ 3 | ; Configuration: https://tox.readthedocs.org/en/latest/config.html 4 | [tox] 5 | envlist = py27 6 | 7 | [testenv] 8 | deps = 9 | pytest 10 | pytest-flask 11 | pytest-instafail 12 | pytest-timeout 13 | pytest-xdist 14 | requests 15 | setenv = 16 | NYLAS_ENV=test 17 | commands = py.test inbox/test/ 18 | sitepackages = True 19 | usedevelop = True 20 | 21 | [flake8] 22 | ignore = E126,E127,E266,E402,E501 23 | exclude = .hypothesis 24 | 25 | [pep8] 26 | ignore = E126,E127,E266,E402,E501 27 | --------------------------------------------------------------------------------