def send_mail(self, message: MIMEMultipart): if not self._is_active: logger.info(self, 'Not sending email to {} (service desactivated)'.format(message['To'])) else: self.connect() # Acutally, this connects to SMTP only if required logger.info(self, 'Sending email to {}'.format(message['To'])) self._smtp_connection.send_message(message)
def disconnect(self): if self._smtp_connection: logger.info( self, 'Disconnecting from SMTP server {}'.format( self._smtp_config.server)) self._smtp_connection.quit() logger.info(self, 'Connection closed.')
def connect(self): if not self._smtp_connection: log = 'Connecting from SMTP server {}' logger.info(self, log.format(self._smtp_config.server)) self._smtp_connection = smtplib.SMTP(self._smtp_config.server, self._smtp_config.port) self._smtp_connection.ehlo() if self._smtp_config.login: try: starttls_result = self._smtp_connection.starttls() log = 'SMTP start TLS result: {}' logger.debug(self, log.format(starttls_result)) except Exception as e: log = 'SMTP start TLS error: {}' logger.debug(self, log.format(e.__str__())) if self._smtp_config.login: try: login_res = self._smtp_connection.login( self._smtp_config.login, self._smtp_config.password) log = 'SMTP login result: {}' logger.debug(self, log.format(login_res)) except Exception as e: log = 'SMTP login error: {}' logger.debug(self, log.format(e.__str__())) logger.info(self, 'Connection OK')
def connect(self): if not self._smtp_connection: log = 'Connecting from SMTP server {}' logger.info(self, log.format(self._smtp_config.server)) self._smtp_connection = smtplib.SMTP( self._smtp_config.server, self._smtp_config.port ) self._smtp_connection.ehlo() if self._smtp_config.login: try: starttls_result = self._smtp_connection.starttls() log = 'SMTP start TLS result: {}' logger.debug(self, log.format(starttls_result)) except Exception as e: log = 'SMTP start TLS error: {}' logger.debug(self, log.format(e.__str__())) if self._smtp_config.login: try: login_res = self._smtp_connection.login( self._smtp_config.login, self._smtp_config.password ) log = 'SMTP login result: {}' logger.debug(self, log.format(login_res)) except Exception as e: log = 'SMTP login error: {}' logger.debug(self, log.format(e.__str__())) logger.info(self, 'Connection OK')
def serialize_it(): nonlocal content if content.type == ContentType.Comment: logger.info( 'serialize_content_for_search_result', 'Serializing parent class {} instead of {} [content #{}]'. format(content.parent.type, content.type, content.content_id)) content = content.parent data_container = content if content.revision_to_serialize > 0: for revision in content.revisions: if revision.revision_id == content.revision_to_serialize: data_container = revision break # FIXME - D.A. - 2015-02-23 - This import should not be there... from tracim.lib.content import ContentApi breadcrumbs = ContentApi(None).build_breadcrumb( data_container.workspace, data_container.content_id, skip_root=True) last_comment_datetime = data_container.updated comments = data_container.get_comments() if comments: last_comment_datetime = max( last_comment_datetime, max(comment.updated for comment in comments)) content_type = ContentType(content.type) result = DictLikeClass( id=content.content_id, type=DictLikeClass(content_type.toDict()), parent=context.toDict(content.parent), workspace=context.toDict(content.workspace), content=data_container.description, content_raw=data_container.description_as_raw_text(), created=data_container.created, created_as_delta=data_container.created_as_delta(), label=data_container.label, icon=ContentType.get_icon(content.type), owner=context.toDict(data_container.owner), status=context.toDict(data_container.get_status()), breadcrumb=context.toDict(breadcrumbs), last_activity=last_comment_datetime, last_activity_as_delta=content.datetime_as_delta( last_comment_datetime)) if content.type == ContentType.File: result.label = content.label.__str__() if not result.label or '' == result.label: result.label = 'No title' return result
def get_tracker_js_content(self, js_tracker_file_path = None): js_tracker_file_path = tg.config.get('js_tracker_path', None) if js_tracker_file_path: logger.info(self, 'Reading JS tracking code from file {}'.format(js_tracker_file_path)) with open (js_tracker_file_path, 'r') as js_file: data = js_file.read() return data else: return ''
def get_tracker_js_content(self, js_tracker_file_path=None): """Get frontend analytics file.""" result = '' js_tracker_file_path = tg.config.get('js_tracker_path', None) if js_tracker_file_path: info_log = 'Reading JS tracking code from file {}' logger.info(self, info_log.format(js_tracker_file_path)) with open(js_tracker_file_path, 'r') as js_file: data = js_file.read() result = data return result
def login(self, *args, **kwargs): """ This method is there for backward compatibility only This is related to the default TG2 authentication behavior... Now the login form is included in home page :param args: :param kwargs: :return: """ came_from = kwargs['came_from'] if 'came_from' in kwargs.keys() else '' logger.info(self, 'came_from: {}'.format(kwargs)) return self.index(came_from, args, *kwargs)
def serialize_it(): nonlocal content if content.type == ContentType.Comment: logger.info('serialize_content_for_search_result', 'Serializing parent class {} instead of {} [content #{}]'.format(content.parent.type, content.type, content.content_id)) content = content.parent data_container = content if content.revision_to_serialize>0: for revision in content.revisions: if revision.revision_id==content.revision_to_serialize: data_container = revision break # FIXME - D.A. - 2015-02-23 - This import should not be there... from tracim.lib.content import ContentApi breadcrumbs = ContentApi(None).build_breadcrumb(data_container.workspace, data_container.content_id, skip_root=True) last_comment_datetime = data_container.updated comments = data_container.get_comments() if comments: last_comment_datetime = max(last_comment_datetime, max(comment.updated for comment in comments)) content_type = ContentType(content.type) result = DictLikeClass( id = content.content_id, type = DictLikeClass(content_type.toDict()), parent = context.toDict(content.parent), workspace = context.toDict(content.workspace), content = data_container.description, content_raw = data_container.description_as_raw_text(), created = data_container.created, created_as_delta = data_container.created_as_delta(), label = data_container.label, icon = ContentType.get_icon(content.type), owner = context.toDict(data_container.owner), status = context.toDict(data_container.get_status()), breadcrumb = context.toDict(breadcrumbs), last_activity=last_comment_datetime, last_activity_as_delta=content.datetime_as_delta(last_comment_datetime) ) if content.type==ContentType.File: result.label = content.label.__str__() if content.label else content.file_name.__str__() if not result.label or ''==result.label: result.label = 'No title' return result
def __init__(self, current_user: User=None): """ :param current_user: the user that has triggered the notification :return: """ logger.info(self, 'Instantiating Real Notifier') cfg = CFG.get_instance() self._user = current_user self._smtp_config = SmtpConfiguration(cfg.EMAIL_NOTIFICATION_SMTP_SERVER, cfg.EMAIL_NOTIFICATION_SMTP_PORT, cfg.EMAIL_NOTIFICATION_SMTP_USER, cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
def index(self, came_from='', *args, **kwargs): if request.identity: if came_from: logger.info(self, 'Will redirect to {}'.format(came_from)) redirect(url(came_from)) else: redirect(self.url(None, self.home.__name__)) login_counter = request.environ.get('repoze.who.logins', 0) if login_counter > 0: flash(_('Wrong credentials'), CST.STATUS_ERROR) return dict(page='login', login_counter=str(login_counter), came_from=came_from)
def send_mail(self, message: MIMEMultipart): if not self._is_active: log = 'Not sending email to {} (service disabled)' logger.info(self, log.format(message['To'])) else: self.connect() # Actually, this connects to SMTP only if required logger.info(self, 'Sending email to {}'.format(message['To'])) self._smtp_connection.send_message(message) from tracim.lib.notifications import EmailNotifier EmailNotifier.log_notification( action=' SENT', recipient=message['To'], subject=message['Subject'], )
def __init__(self, current_user: User=None): """ :param current_user: the user that has triggered the notification :return: """ logger.info(self, 'Instantiating Real Notifier') # TODO: Find a way to import properly without cyclic import from tracim.config.app_cfg import CFG cfg = CFG.get_instance() self._user = current_user self._smtp_config = SmtpConfiguration(cfg.EMAIL_NOTIFICATION_SMTP_SERVER, cfg.EMAIL_NOTIFICATION_SMTP_PORT, cfg.EMAIL_NOTIFICATION_SMTP_USER, cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
def stop(self, name: str) -> None: """ Stop daemon with his name and wait for him. Where name is given name when daemon started with run method. :param name: """ if name in self._running_daemons: logger.info(self, 'Stopping daemon with name "{0}" ...' .format(name)) self._running_daemons[name].stop() self._running_daemons[name].join() del self._running_daemons[name] logger.info(self, 'Stopping daemon with name "{0}": OK' .format(name))
def stop(self, name: str) -> None: """ Stop daemon with his name and wait for him. Where name is given name when daemon started with run method. :param name: """ if name in self._running_daemons: logger.info(self, 'Stopping daemon with name "{0}" ...'.format(name)) self._running_daemons[name].stop() self._running_daemons[name].join() del self._running_daemons[name] logger.info(self, 'Stopping daemon with name "{0}": OK'.format(name))
def __init__(self, current_user: User = None): """ :param current_user: the user that has triggered the notification :return: """ logger.info(self, 'Instantiating Real Notifier') # TODO: Find a way to import properly without cyclic import from tracim.config.app_cfg import CFG cfg = CFG.get_instance() self._user = current_user self._smtp_config = SmtpConfiguration( cfg.EMAIL_NOTIFICATION_SMTP_SERVER, cfg.EMAIL_NOTIFICATION_SMTP_PORT, cfg.EMAIL_NOTIFICATION_SMTP_USER, cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
def __setattr__(self, key, value): """ Log-ready setter. this is used for logging configuration (every parameter except password) :param key: :param value: :return: """ if 'PASSWORD' not in key and 'URL' not in key and 'CONTENT' not in key: # We do not show PASSWORD for security reason # we do not show URL because the associated config uses tg.lurl() which is evaluated when at display time. # At the time of configuration setup, it can't be evaluated # We do not show CONTENT in order not to pollute log files logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value)) else: logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key)) self.__dict__[key] = value
def register_converter(cls, context_string, model_class, convert_function): """ :param context_string: :param model_class: :param convert_function: :return: """ if context_string not in Context._converters: Context._converters[context_string] = dict() if model_class not in Context._converters[context_string]: logger.info(Context, 'Registering Serialization feature: [ {2} | {1} | {0} ]'.format( convert_function.__name__, model_class.__name__, context_string)) Context._converters[context_string][model_class] = convert_function
def run(self, name: str, daemon_class: object, **kwargs) -> None: """ Start a daemon with given daemon class. :param name: Name of runned daemon. It's not possible to start two daemon with same name. In the opposite case, raise tracim.lib.exceptions.AlreadyRunningDaemon :param daemon_class: Daemon class to use for daemon instance. :param kwargs: Other kwargs will be given to daemon class instantiation. """ if name in self._running_daemons: raise AlreadyRunningDaemon( 'Daemon with name "{0}" already running'.format(name) ) logger.info(self, 'Starting daemon with name "{0}" and class "{1}" ...' .format(name, daemon_class)) daemon = daemon_class(name=name, kwargs=kwargs, daemon=True) daemon.start() self._running_daemons[name] = daemon
def run(self, name: str, daemon_class: object, **kwargs) -> None: """ Start a daemon with given daemon class. :param name: Name of runned daemon. It's not possible to start two daemon with same name. In the opposite case, raise tracim.lib.exceptions.AlreadyRunningDaemon :param daemon_class: Daemon class to use for daemon instance. :param kwargs: Other kwargs will be given to daemon class instantiation. """ if name in self._running_daemons: raise AlreadyRunningDaemon( 'Daemon with name "{0}" already running'.format(name)) logger.info( self, 'Starting daemon with name "{0}" and class "{1}" ...'.format( name, daemon_class)) daemon = daemon_class(name=name, kwargs=kwargs, daemon=True) daemon.start() self._running_daemons[name] = daemon
def run(self) -> None: logger.info(self, 'Starting MailFetcher') while self._is_active: logger.debug(self, 'sleep for {}'.format(self.delay)) time.sleep(self.delay) try: self._connect() with self.lock.acquire(timeout=MAIL_FETCHER_FILELOCK_TIMEOUT): messages = self._fetch() cleaned_mails = [ DecodedMail(m.message, m.uid) for m in messages ] self._notify_tracim(cleaned_mails) self._disconnect() except filelock.Timeout as e: log = 'Mail Fetcher Lock Timeout {}' logger.warning(self, log.format(e.__str__())) except Exception as e: # TODO - G.M - 2017-11-23 - Identify possible exceptions log = 'IMAP error: {}' logger.warning(self, log.format(e.__str__()))
def stop_all(self) -> None: """ Stop all started daemons and wait for them. """ logger.info(self, 'Stopping all daemons') for name, daemon in self._running_daemons.items(): logger.info(self, 'Stopping daemon "{0}" ...'.format(name)) daemon.stop() for name, daemon in self._running_daemons.items(): logger.info( self, 'Stopping daemon "{0}" waiting confirmation'.format(name), ) daemon.join() logger.info(self, 'Stopping daemon "{0}" OK'.format(name)) self._running_daemons = {}
def stop_all(self, *args, **kwargs) -> None: """ Stop all started daemons and wait for them. """ logger.info(self, 'Stopping all daemons') for name, daemon in self._running_daemons.items(): logger.info(self, 'Stopping daemon "{0}" ...'.format(name)) daemon.stop() for name, daemon in self._running_daemons.items(): daemon.join() logger.info(self, 'Stopping daemon "{0}" OK'.format(name)) self._running_daemons = {}
def notify_content_update(self, content: Content): logger.info(self, 'Fake notifier, do not send email-notification for update of content {}'.format(content.content_id))
def notify_created_account( self, user: User, password: str, ) -> None: """ Send created account email to given user :param password: choosed password :param user: user to notify """ # TODO BS 20160712: Cyclic import from tracim.lib.notifications import EST logger.debug(self, 'user: {}'.format(user.user_id)) logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format( user.email, )) async_email_sender = EmailSender( self._smtp_config, self._global_config.EMAIL_NOTIFICATION_ACTIVATED ) subject = \ self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \ .replace( EST.WEBSITE_TITLE, self._global_config.WEBSITE_TITLE.__str__() ) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._global_config.EMAIL_NOTIFICATION_FROM message['To'] = user.email text_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT # nopep8 html_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML # nopep8 body_text = self._render( mako_template_filepath=text_template_file_path, context={ 'user': user, 'password': password, 'login_url': self._global_config.WEBSITE_BASE_URL, } ) body_html = self._render( mako_template_filepath=html_template_file_path, context={ 'user': user, 'password': password, 'login_url': self._global_config.WEBSITE_BASE_URL, } ) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part1) message.attach(part2) asyncjob_perform(async_email_sender.send_mail, message) # Note: The following action allow to close the SMTP connection. # This will work only if the async jobs are done in the right order asyncjob_perform(async_email_sender.disconnect)
def notify_created_account( self, user: User, password: str, ) -> None: """ Send created account email to given user :param password: choosed password :param user: user to notify """ # TODO BS 20160712: Cyclic import from tracim.lib.notifications import EST logger.debug(self, 'user: {}'.format(user.user_id)) logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format( user.email, )) async_email_sender = EmailSender( self._smtp_config, self._global_config.EMAIL_NOTIFICATION_ACTIVATED ) subject = \ self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \ .replace( EST.WEBSITE_TITLE, self._global_config.WEBSITE_TITLE.__str__() ) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = '{0} <{1}>'.format( self._global_config.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL, self._global_config.EMAIL_NOTIFICATION_FROM_EMAIL, ) message['To'] = user.email text_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT # nopep8 html_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML # nopep8 body_text = self._render( mako_template_filepath=text_template_file_path, context={ 'user': user, 'password': password, 'login_url': self._global_config.WEBSITE_BASE_URL, } ) body_html = self._render( mako_template_filepath=html_template_file_path, context={ 'user': user, 'password': password, 'login_url': self._global_config.WEBSITE_BASE_URL, } ) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part1) message.attach(part2) send_email_through(async_email_sender.send_mail, message)
def notify_content_update(self, content: Content): # TODO: Find a way to import properly without cyclic import from tracim.config.app_cfg import CFG global_config = CFG.get_instance() if content.get_last_action().id not \ in global_config.EMAIL_NOTIFICATION_NOTIFIED_EVENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the action is {})'.format( content.content_id, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id ) ) return logger.info(self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0 ) ) if content.type not \ in global_config.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the content type is {})'.format( content.type, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id ) ) return logger.info(self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0 ) ) #### # # INFO - D.A. - 2014-11-05 - Emails are sent through asynchronous jobs. # For that reason, we do not give SQLAlchemy objects but ids only # (SQLA objects are related to a given thread/session) # try: if global_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower()==global_config.CST.ASYNC.lower(): logger.info(self, 'Sending email in ASYNC mode') # TODO - D.A - 2014-11-06 # This feature must be implemented in order to be able to scale to large communities raise NotImplementedError('Sending emails through ASYNC mode is not working yet') asyncjob_perform(EmailNotifier(self._smtp_config, global_config).notify_content_update, self._user.user_id, content.content_id) else: logger.info(self, 'Sending email in SYNC mode') EmailNotifier(self._smtp_config, global_config).notify_content_update(self._user.user_id, content.content_id) except Exception as e: logger.error(self, 'Exception catched during email notification: {}'.format(e.__str__()))
def disconnect(self): if self._smtp_connection: logger.info(self, 'Disconnecting from SMTP server {}'.format(self._smtp_config.server)) self._smtp_connection.quit() logger.info(self, 'Connection closed.')
def notify_content_update(self, content: Content): # TODO: Find a way to import properly without cyclic import from tracim.config.app_cfg import CFG global_config = CFG.get_instance() if content.get_last_action().id not \ in global_config.EMAIL_NOTIFICATION_NOTIFIED_EVENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the action is {})'.format( content.content_id, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id)) return logger.info( self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0)) if content.type not \ in global_config.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the content type is {})'.format( content.type, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id)) return logger.info( self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0)) #### # # INFO - D.A. - 2014-11-05 - Emails are sent through asynchronous jobs. # For that reason, we do not give SQLAlchemy objects but ids only # (SQLA objects are related to a given thread/session) # try: if global_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower( ) == global_config.CST.ASYNC.lower(): logger.info(self, 'Sending email in ASYNC mode') # TODO - D.A - 2014-11-06 # This feature must be implemented in order to be able to scale to large communities raise NotImplementedError( 'Sending emails through ASYNC mode is not working yet') else: logger.info(self, 'Sending email in SYNC mode') EmailNotifier(self._smtp_config, global_config).notify_content_update( self._user.user_id, content.content_id) except Exception as e: logger.error( self, 'Exception catched during email notification: {}'.format( e.__str__()))
def run(self) -> None: logger.info(self, 'Starting MailFetcher') while self._is_active: imapc = None sleep_after_connection = True try: imapc = imapclient.IMAPClient( self.host, self.port, ssl=self.use_ssl, timeout=MAIL_FETCHER_CONNECTION_TIMEOUT) imapc.login(self.user, self.password) logger.debug(self, 'Select folder {}'.format(self.folder, )) imapc.select_folder(self.folder) # force renew connection when deadline is reached deadline = time.time() + self.connection_max_lifetime while True: if not self._is_active: logger.warning(self, 'Mail Fetcher process aborted') sleep_after_connection = False break if time.time() > deadline: logger.debug( self, "MailFetcher Connection Lifetime limit excess" ", Try Re-new connection") sleep_after_connection = False break # check for new mails self._check_mail(imapc) if self.use_idle and imapc.has_capability('IDLE'): # IDLE_mode wait until event from server logger.debug(self, 'wail for event(IDLE)') imapc.idle() imapc.idle_check( timeout=MAIL_FETCHER_IDLE_RESPONSE_TIMEOUT) imapc.idle_done() else: if self.use_idle and not imapc.has_capability('IDLE'): log = 'IDLE mode activated but server do not' \ 'support it, use polling instead.' logger.warning(self, log) # normal polling mode : sleep a define duration logger.debug(self, 'sleep for {}'.format(self.heartbeat)) time.sleep(self.heartbeat) # Socket except (socket.error, socket.gaierror, socket.herror) as e: log = 'Socket fail with IMAP connection {}' logger.error(self, log.format(e.__str__())) except socket.timeout as e: log = 'Socket timeout on IMAP connection {}' logger.error(self, log.format(e.__str__())) # SSL except ssl.SSLError as e: log = 'SSL error on IMAP connection' logger.error(self, log.format(e.__str__())) except ssl.CertificateError as e: log = 'SSL Certificate verification failed on IMAP connection' logger.error(self, log.format(e.__str__())) # Filelock except filelock.Timeout as e: log = 'Mail Fetcher Lock Timeout {}' logger.warning(self, log.format(e.__str__())) # IMAP # TODO - G.M - 10-01-2017 - Support imapclient exceptions # when Imapclient stable will be 2.0+ except BadIMAPFetchResponse as e: log = 'Imap Fetch command return bad response.' \ 'Is someone else connected to the mailbox ?: ' \ '{}' logger.error(self, log.format(e.__str__())) # Others except Exception as e: log = 'Mail Fetcher error {}' logger.error(self, log.format(e.__str__())) finally: # INFO - G.M - 2018-01-09 - Connection closing # Properly close connection according to # https://github.com/mjs/imapclient/pull/279/commits/043e4bd0c5c775c5a08cb5f1baa93876a46732ee # TODO : Use __exit__ method instead when imapclient stable will # be 2.0+ . if imapc: logger.debug(self, 'Try logout') try: imapc.logout() except Exception: try: imapc.shutdown() except Exception as e: log = "Can't logout, connection broken ? {}" logger.error(self, log.format(e.__str__())) if sleep_after_connection: logger.debug(self, 'sleep for {}'.format(self.heartbeat)) time.sleep(self.heartbeat) log = 'Mail Fetcher stopped' logger.debug(self, log)
def __init__(self, current_user: User=None): logger.info(self, 'Instantiating Dummy Notifier')
def notify_content_update(self, event_actor_id: int, event_content_id: int): """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim.lib.content import ContentApi user = UserApi(None).get_one(event_actor_id) logger.debug(self, 'Content: {}'.format(event_content_id)) content = ContentApi(user, show_archived=True, show_deleted=True).get_one(event_content_id, ContentType.Any) # TODO - use a system user instead of the user that has triggered the event main_content = content.parent if content.type==ContentType.Comment else content notifiable_roles = WorkspaceApi(user).get_notifiable_roles(content.workspace) if len(notifiable_roles)<=0: logger.info(self, 'Skipping notification as nobody subscribed to in workspace {}'.format(content.workspace.label)) return logger.info(self, 'Sending asynchronous emails to {} user(s)'.format(len(notifiable_roles))) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent async_email_sender = EmailSender(self._smtp_config, self._global_config.EMAIL_NOTIFICATION_ACTIVATED) for role in notifiable_roles: logger.info(self, 'Sending email to {}'.format(role.user.email)) to_addr = '{name} <{email}>'.format(name=role.user.display_name, email=role.user.email) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # subject = self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT subject = subject.replace(EST.WEBSITE_TITLE, self._global_config.WEBSITE_TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace(EST.CONTENT_STATUS_LABEL, main_content.get_status().label.__str__()) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._global_config.EMAIL_NOTIFICATION_FROM message['To'] = to_addr body_text = self._build_email_body(self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user) body_html = self._build_email_body(self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part1) message.attach(part2) message_str = message.as_string() asyncjob_perform(async_email_sender.send_mail, message) # s.send_message(message) # Note: The following action allow to close the SMTP connection. # This will work only if the async jobs are done in the right order asyncjob_perform(async_email_sender.disconnect)
def notify_content_update(self, event_actor_id: int, event_content_id: int): """ Look for all users to be notified about the new content and send them an individual email :param event_actor_id: id of the user that has triggered the event :param event_content_id: related content_id :return: """ # FIXME - D.A. - 2014-11-05 # Dirty import. It's here in order to avoid circular import from tracim.lib.content import ContentApi user = UserApi(None).get_one(event_actor_id) logger.debug(self, 'Content: {}'.format(event_content_id)) content = ContentApi( user, show_archived=True, show_deleted=True ).get_one( event_content_id, ContentType.Any ) # TODO - use a system user instead of the user that has triggered the event main_content = content.parent if content.type == ContentType.Comment else content notifiable_roles = WorkspaceApi(user).get_notifiable_roles( content.workspace) if len(notifiable_roles) <= 0: logger.info( self, 'Skipping notification as nobody subscribed to in workspace {}' .format(content.workspace.label)) return logger.info( self, 'Sending asynchronous emails to {} user(s)'.format( len(notifiable_roles))) # INFO - D.A. - 2014-11-06 # The following email sender will send emails in the async task queue # This allow to build all mails through current thread but really send them (including SMTP connection) # In the other thread. # # This way, the webserver will return sooner (actually before notification emails are sent async_email_sender = EmailSender( self._smtp_config, self._global_config.EMAIL_NOTIFICATION_ACTIVATED) for role in notifiable_roles: logger.info(self, 'Sending email to {}'.format(role.user.email)) to_addr = '{name} <{email}>'.format(name=role.user.display_name, email=role.user.email) # # INFO - D.A. - 2014-11-06 # We do not use .format() here because the subject defined in the .ini file # may not include all required labels. In order to avoid partial format() (which result in an exception) # we do use replace and force the use of .__str__() in order to process LazyString objects # subject = self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT subject = subject.replace( EST.WEBSITE_TITLE, self._global_config.WEBSITE_TITLE.__str__()) subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__()) subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__()) subject = subject.replace( EST.CONTENT_STATUS_LABEL, main_content.get_status().label.__str__()) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender(user) message['To'] = to_addr body_text = self._build_email_body( self._global_config. EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user) body_html = self._build_email_body( self._global_config. EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part1) message.attach(part2) send_email_through(async_email_sender.send_mail, message)
def __init__(self, current_user: User = None): logger.info(self, 'Instantiating Dummy Notifier')
def notify_content_update(self, content: Content): type(self).send_count += 1 logger.info( self, 'Fake notifier, do not send email-notification for update of content {}' .format(content.content_id))