<commit_before>"""
Create and register a new NotificationCallbackTimerHandler
"""
import datetime
import os
import urllib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from itertools import groupby
import logging
from django.contrib.staticfiles import finders
import uuid
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
import pynliner
from edx_notifications import const
from django.dispatch import receiver
import pytz
from edx_notifications.data import NotificationCallbackTimer, NotificationPreference
from edx_notifications.signals import perform_timer_registrations
from edx_notifications.stores.sql.store_provider import SQLNotificationStoreProvider
from edx_notifications.stores.store import notification_store
from edx_notifications.exceptions import ItemNotFoundError
from edx_notifications.namespaces import resolve_namespace
from edx_notifications.renderers.renderer import get_renderer_for_type
from django.utils.translation import ugettext as _
from edx_notifications.lib.consumer import (
    get_user_preference_by_name,
    get_notification_preference,
    get_notifications_for_user
)
from edx_notifications.callbacks import NotificationCallbackTimerHandler

log = logging.getLogger(__name__)


class NotificationDigestMessageCallback(NotificationCallbackTimerHandler):
    """
        This is called by the NotificationTimer for triggering sending out
        daily and weekly digest of notification emails.
        The timer.periodicity_min can be used to differentiate between the
        daily (where the periodicity_min will be equal to MINUTES_IN_A_DAY) and
        weekly (where the periodicity_min will be equal to MINUTES_IN_A_WEEK)
        digest timers.

        The return dictionary must contain the key 'reschedule_in_mins' with
        the value timer.periodicity_min in order to re-arm the callback to
        trigger again after the specified interval.
    """

    def notification_timer_callback(self, timer):
        is_daily_digest = True
        if timer.context:
            is_daily_digest = timer.context.get('is_daily_digest')

        # figure out what the preference name should be that controlls
        # the distribution of the digest
        preference_name = const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME
        if timer.context:
            preference_name = timer.context.get(
                'preference_name',
                const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME
            )

        # record a 'to_timestamp'
        to_timestamp = datetime.datetime.now(pytz.UTC)

        # do we just want unread notifications since we last ran the
        # digest?
        if const.NOTIFICATION_DIGEST_SEND_TIMEFILTERED:
            if timer.context and 'last_ran' in timer.context:
                from_timestamp = timer.context['last_ran']
            else:
                tdelta = datetime.timedelta(days=1) if is_daily_digest else datetime.timedelta(days=7)
                from_timestamp = to_timestamp - tdelta
        else:
            # send all unread notifications regardless
            # of time filter
            from_timestamp = None

        subject = timer.context['subject']
        from_email = timer.context['from_email']
        unread_only = timer.context['unread_only']

        # call into the main entry point
        # for generating digests
        errors = []
        try:
            send_notifications_digest(
                from_timestamp,
                to_timestamp,
                preference_name,
                subject,
                from_email,
                unread_only=unread_only
            )
        except Exception, ex:  # pylint: disable=broad-except
            errors.append(str(ex))

        result = {
            'errors': errors,
            'reschedule_in_mins': timer.periodicity_min,
            # be sure to update the timer context, to record when we
            # last ran this digest
            'context_update': {
                'last_ran': to_timestamp,
            }
        }
        return result


@receiver(perform_timer_registrations)
def register_digest_timers(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Register NotificationCallbackTimerHandler.
    This will be called automatically on the Notification subsystem startup (because we are
    receiving the 'perform_timer_registrations' signal)
    """
    store = notification_store()

    # Set first execution time as upcoming midnight after the server is run for the first time.
    first_execution_at = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1)
    first_execution_at = first_execution_at.replace(hour=0, minute=0, second=0, microsecond=0)

    # see if we have an existing timer set, and preserve that execution time
    # that is, don't reset it across startups
    try:
        existing_daily_digest_timer = store.get_notification_timer(const.DAILY_DIGEST_TIMER_NAME)
        if existing_daily_digest_timer:
            first_execution_at = existing_daily_digest_timer.callback_at
    except ItemNotFoundError:
        pass

    daily_digest_timer = NotificationCallbackTimer(
        name=const.DAILY_DIGEST_TIMER_NAME,
        callback_at=first_execution_at,
        class_name='edx_notifications.digests.NotificationDigestMessageCallback',
        is_active=True,
        periodicity_min=const.MINUTES_IN_A_DAY,
        context={
            'is_daily_digest': True,
            'preference_name': const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME,
            'subject': const.NOTIFICATION_DAILY_DIGEST_SUBJECT,
            'from_email': const.NOTIFICATION_EMAIL_FROM_ADDRESS,
            'unread_only': const.NOTIFICATION_DIGEST_UNREAD_ONLY,
        }
    )
    store.save_notification_timer(daily_digest_timer)

    weekly_digest_timer = NotificationCallbackTimer(
        name=const.WEEKLY_DIGEST_TIMER_NAME,
        callback_at=first_execution_at,
        class_name='edx_notifications.digests.NotificationDigestMessageCallback',
        is_active=True,
        periodicity_min=const.MINUTES_IN_A_WEEK,
        context={
            'is_daily_digest': False,
            'preference_name': const.NOTIFICATION_WEEKLY_DIGEST_PREFERENCE_NAME,
            'subject': const.NOTIFICATION_DAILY_DIGEST_SUBJECT,
            'from_email': const.NOTIFICATION_EMAIL_FROM_ADDRESS,
            'unread_only': const.NOTIFICATION_DIGEST_UNREAD_ONLY,
        }
    )
    store.save_notification_timer(weekly_digest_timer)


def create_default_notification_preferences():
    """
    This function installs two default system-defined Notification Preferences
    for daily and weekly Digests.
    """
    store_provider = SQLNotificationStoreProvider()

    daily_digest_preference = NotificationPreference(<commit_msg>don't overwrite the periodicty_min on restarts<commit_after>"""
Create and register a new NotificationCallbackTimerHandler
"""
import datetime
import os
import urllib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from itertools import groupby
import logging
from django.contrib.staticfiles import finders
import uuid
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
import pynliner
from edx_notifications import const
from django.dispatch import receiver
import pytz
from edx_notifications.data import NotificationCallbackTimer, NotificationPreference
from edx_notifications.signals import perform_timer_registrations
from edx_notifications.stores.sql.store_provider import SQLNotificationStoreProvider
from edx_notifications.stores.store import notification_store
from edx_notifications.exceptions import ItemNotFoundError
from edx_notifications.namespaces import resolve_namespace
from edx_notifications.renderers.renderer import get_renderer_for_type
from django.utils.translation import ugettext as _
from edx_notifications.lib.consumer import (
    get_user_preference_by_name,
    get_notification_preference,
    get_notifications_for_user
)
from edx_notifications.callbacks import NotificationCallbackTimerHandler

log = logging.getLogger(__name__)


class NotificationDigestMessageCallback(NotificationCallbackTimerHandler):
    """
        This is called by the NotificationTimer for triggering sending out
        daily and weekly digest of notification emails.
        The timer.periodicity_min can be used to differentiate between the
        daily (where the periodicity_min will be equal to MINUTES_IN_A_DAY) and
        weekly (where the periodicity_min will be equal to MINUTES_IN_A_WEEK)
        digest timers.

        The return dictionary must contain the key 'reschedule_in_mins' with
        the value timer.periodicity_min in order to re-arm the callback to
        trigger again after the specified interval.
    """

    def notification_timer_callback(self, timer):
        is_daily_digest = True
        if timer.context:
            is_daily_digest = timer.context.get('is_daily_digest')

        # figure out what the preference name should be that controlls
        # the distribution of the digest
        preference_name = const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME
        if timer.context:
            preference_name = timer.context.get(
                'preference_name',
                const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME
            )

        # record a 'to_timestamp'
        to_timestamp = datetime.datetime.now(pytz.UTC)

        # do we just want unread notifications since we last ran the
        # digest?
        if const.NOTIFICATION_DIGEST_SEND_TIMEFILTERED:
            if timer.context and 'last_ran' in timer.context:
                from_timestamp = timer.context['last_ran']
            else:
                tdelta = datetime.timedelta(days=1) if is_daily_digest else datetime.timedelta(days=7)
                from_timestamp = to_timestamp - tdelta
        else:
            # send all unread notifications regardless
            # of time filter
            from_timestamp = None

        subject = timer.context['subject']
        from_email = timer.context['from_email']
        unread_only = timer.context['unread_only']

        # call into the main entry point
        # for generating digests
        errors = []
        try:
            send_notifications_digest(
                from_timestamp,
                to_timestamp,
                preference_name,
                subject,
                from_email,
                unread_only=unread_only
            )
        except Exception, ex:  # pylint: disable=broad-except
            errors.append(str(ex))

        result = {
            'errors': errors,
            'reschedule_in_mins': timer.periodicity_min,
            # be sure to update the timer context, to record when we
            # last ran this digest
            'context_update': {
                'last_ran': to_timestamp,
            }
        }
        return result


@receiver(perform_timer_registrations)
def register_digest_timers(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Register NotificationCallbackTimerHandler.
    This will be called automatically on the Notification subsystem startup (because we are
    receiving the 'perform_timer_registrations' signal)
    """
    store = notification_store()

    # Set first execution time as upcoming midnight after the server is run for the first time.
    first_execution_at = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1)
    first_execution_at = first_execution_at.replace(hour=0, minute=0, second=0, microsecond=0)
    periodicity_min = const.MINUTES_IN_A_DAY

    # see if we have an existing timer set, and preserve that execution time
    # that is, don't reset it across startups
    try:
        existing_daily_digest_timer = store.get_notification_timer(const.DAILY_DIGEST_TIMER_NAME)
        if existing_daily_digest_timer:
            first_execution_at = existing_daily_digest_timer.callback_at
            periodicity_min = existing_daily_digest_timer.periodicity_min
    except ItemNotFoundError:
        pass

    daily_digest_timer = NotificationCallbackTimer(
        name=const.DAILY_DIGEST_TIMER_NAME,
        callback_at=first_execution_at,
        class_name='edx_notifications.digests.NotificationDigestMessageCallback',
        is_active=True,
        periodicity_min=periodicity_min,
        context={
            'is_daily_digest': True,
            'preference_name': const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME,
            'subject': const.NOTIFICATION_DAILY_DIGEST_SUBJECT,
            'from_email': const.NOTIFICATION_EMAIL_FROM_ADDRESS,
            'unread_only': const.NOTIFICATION_DIGEST_UNREAD_ONLY,
        }
    )
    store.save_notification_timer(daily_digest_timer)

    periodicity_min = const.MINUTES_IN_A_WEEK

    # see if we have an existing timer set, and preserve that execution time
    # that is, don't reset it across startups
    try:
        existing_daily_digest_timer = store.get_notification_timer(const.WEEKLY_DIGEST_TIMER_NAME)
        if existing_daily_digest_timer:
            first_execution_at = existing_daily_digest_timer.callback_at
            periodicity_min = existing_daily_digest_timer.periodicity_min
    except ItemNotFoundError:
        pass

    weekly_digest_timer = NotificationCallbackTimer(
        name=const.WEEKLY_DIGEST_TIMER_NAME,
        callback_at=first_execution_at,
        class_name='edx_notifications.digests.NotificationDigestMessageCallback',
        is_active=True,
        periodicity_min=periodicity_min,
        context={
            'is_daily_digest': False,
            'preference_name': const.NOTIFICATION_WEEKLY_DIGEST_PREFERENCE_NAME,
            'subject': const.NOTIFICATION_DAILY_DIGEST_SUBJECT,
            'from_email': const.NOTIFICATION_EMAIL_FROM_ADDRESS,
            'unread_only': const.NOTIFICATION_DIGEST_UNREAD_ONLY,
        }
    )
    store.save_notification_timer(weekly_digest_timer)


def create_default_notification_preferences():
    """
    This function installs two default system-defined Notification Preferences
    for daily and weekly Digests.
    """
    store_provider = SQLNotificationStoreProvider()

    daily_digest_preference = NotificationPreference(
{'fraction_non_alphanumeric': 0.05506883604505632, 'fraction_numerical': 0.0009734390209984703, 'mean_word_length': 3.1318586613042227, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2239894', 'n_tokens_mistral': 4062, 'n_tokens_neox': 3850, 'n_words': 942}