# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt """ Events: always daily monthly weekly """ # imports - compatibility imports from __future__ import print_function, unicode_literals # imports - standard imports import os import time # imports - third party imports import schedule # imports - module imports import frappe from frappe.core.doctype.user.user import STANDARD_USERS from frappe.installer import update_site_config from frappe.utils import get_sites, now_datetime from frappe.utils.background_jobs import get_jobs DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' def start_scheduler(): '''Run enqueue_events_for_all_sites every 2 minutes (default). Specify scheduler_interval in seconds in common_site_config.json''' schedule.every(frappe.get_conf().scheduler_tick_interval or 60).seconds.do(enqueue_events_for_all_sites) while True: schedule.run_pending() time.sleep(1) def enqueue_events_for_all_sites(): '''Loop through sites and enqueue events that are not already queued''' if os.path.exists(os.path.join('.', '.restarting')): # Don't add task to queue if webserver is in restart mode return with frappe.init_site(): sites = get_sites() for site in sites: try: enqueue_events_for_site(site=site) except Exception as e: print(e.__class__, 'Failed to enqueue events for site: {}'.format(site)) def enqueue_events_for_site(site): def log_and_raise(): error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback()) frappe.logger("scheduler").error(error_message) try: frappe.init(site=site) frappe.connect() if is_scheduler_inactive(): return enqueue_events(site=site) frappe.logger("scheduler").debug('Queued events for site {0}'.format(site)) except frappe.db.OperationalError as e: if frappe.db.is_access_denied(e): frappe.logger("scheduler").debug('Access denied for site {0}'.format(site)) else: log_and_raise() except: log_and_raise() finally: frappe.destroy() def enqueue_events(site): if schedule_jobs_based_on_activity(): frappe.flags.enqueued_jobs = [] queued_jobs = get_jobs(site=site, key='job_type').get(site) or [] for job_type in frappe.get_all('Scheduled Job Type', ('name', 'method'), dict(stopped=0)): if not job_type.method in queued_jobs: # don't add it to queue if still pending frappe.get_doc('Scheduled Job Type', job_type.name).enqueue() def is_scheduler_inactive(): if frappe.local.conf.maintenance_mode: return True if frappe.local.conf.pause_scheduler: return True if is_scheduler_disabled(): return True return False def is_scheduler_disabled(): if frappe.conf.disable_scheduler: return True return not frappe.utils.cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) def toggle_scheduler(enable): frappe.db.set_value("System Settings", None, "enable_scheduler", 1 if enable else 0) def enable_scheduler(): toggle_scheduler(True) def disable_scheduler(): toggle_scheduler(False) def schedule_jobs_based_on_activity(check_time=None): '''Returns True for active sites defined by Activity Log Returns True for inactive sites once in 24 hours''' if is_dormant(check_time=check_time): # ensure last job is one day old last_job_timestamp = frappe.db.get_last_created('Scheduled Job Log') if not last_job_timestamp: return True else: if ((check_time or now_datetime()) - last_job_timestamp).total_seconds() >= 86400: # one day is passed since jobs are run, so lets do this return True else: # schedulers run in the last 24 hours, do nothing return False else: # site active, lets run the jobs return True def is_dormant(check_time=None): last_activity_log_timestamp = frappe.db.get_last_created('Activity Log') since = (frappe.get_system_settings('dormant_days') or 4) * 86400 if not last_activity_log_timestamp: return True if ((check_time or now_datetime()) - last_activity_log_timestamp).total_seconds() >= since: return True return False @frappe.whitelist() def activate_scheduler(): if is_scheduler_disabled(): enable_scheduler() if frappe.conf.pause_scheduler: update_site_config('pause_scheduler', 0)