prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Enable Scheduler from desk
  Feature to enable scheduler from desk.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `frappe/utils/scheduler.py`
  Content:
  ```
  1 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2 # License: MIT. See LICENSE
  3 """
  4 Events:
  5 	always
  6 	daily
  7 	monthly
  8 	weekly
  9 """
  10 
  11 # imports - standard imports
  12 import os
  13 import time
  14 from typing import NoReturn
  15 
  16 # imports - module imports
  17 import frappe
  18 from frappe.installer import update_site_config
  19 from frappe.utils import cint, get_datetime, get_sites, now_datetime
  20 from frappe.utils.background_jobs import get_jobs
  21 
  22 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
  23 
  24 
  25 def cprint(*args, **kwargs):
  26 	"""Prints only if called from STDOUT"""
  27 	try:
  28 		os.get_terminal_size()
  29 		print(*args, **kwargs)
  30 	except Exception:
  31 		pass
  32 
  33 
  34 def start_scheduler() -> NoReturn:
  35 	"""Run enqueue_events_for_all_sites based on scheduler tick.
  36 	Specify scheduler_interval in seconds in common_site_config.json"""
  37 
  38 	tick = cint(frappe.get_conf().scheduler_tick_interval) or 60
  39 
  40 	while True:
  41 		time.sleep(tick)
  42 		enqueue_events_for_all_sites()
  43 
  44 
  45 def enqueue_events_for_all_sites() -> None:
  46 	"""Loop through sites and enqueue events that are not already queued"""
  47 
  48 	if os.path.exists(os.path.join(".", ".restarting")):
  49 		# Don't add task to queue if webserver is in restart mode
  50 		return
  51 
  52 	with frappe.init_site():
  53 		sites = get_sites()
  54 
  55 	for site in sites:
  56 		try:
  57 			enqueue_events_for_site(site=site)
  58 		except Exception:
  59 			frappe.logger("scheduler").debug(f"Failed to enqueue events for site: {site}", exc_info=True)
  60 
  61 
  62 def enqueue_events_for_site(site: str) -> None:
  63 	def log_exc():
  64 		frappe.logger("scheduler").error(f"Exception in Enqueue Events for Site {site}", exc_info=True)
  65 
  66 	try:
  67 		frappe.init(site=site)
  68 		frappe.connect()
  69 		if is_scheduler_inactive():
  70 			return
  71 
  72 		enqueue_events(site=site)
  73 
  74 		frappe.logger("scheduler").debug(f"Queued events for site {site}")
  75 	except Exception as e:
  76 		if frappe.db.is_access_denied(e):
  77 			frappe.logger("scheduler").debug(f"Access denied for site {site}")
  78 		log_exc()
  79 
  80 	finally:
  81 		frappe.destroy()
  82 
  83 
  84 def enqueue_events(site: str) -> list[str] | None:
  85 	if schedule_jobs_based_on_activity():
  86 		enqueued_jobs = []
  87 		for job_type in frappe.get_all("Scheduled Job Type", ("name", "method"), {"stopped": 0}):
  88 			job_type = frappe.get_cached_doc("Scheduled Job Type", job_type.name)
  89 			if _enqueued := job_type.enqueue():
  90 				enqueued_jobs.append(job_type.method)
  91 
  92 		return enqueued_jobs
  93 
  94 
  95 def is_scheduler_inactive(verbose=True) -> bool:
  96 	if frappe.local.conf.maintenance_mode:
  97 		if verbose:
  98 			cprint(f"{frappe.local.site}: Maintenance mode is ON")
  99 		return True
  100 
  101 	if frappe.local.conf.pause_scheduler:
  102 		if verbose:
  103 			cprint(f"{frappe.local.site}: frappe.conf.pause_scheduler is SET")
  104 		return True
  105 
  106 	if is_scheduler_disabled(verbose=verbose):
  107 		return True
  108 
  109 	return False
  110 
  111 
  112 def is_scheduler_disabled(verbose=True) -> bool:
  113 	if frappe.conf.disable_scheduler:
  114 		if verbose:
  115 			cprint(f"{frappe.local.site}: frappe.conf.disable_scheduler is SET")
  116 		return True
  117 
  118 	scheduler_disabled = not frappe.utils.cint(
  119 		frappe.db.get_single_value("System Settings", "enable_scheduler")
  120 	)
  121 	if scheduler_disabled:
  122 		if verbose:
  123 			cprint(f"{frappe.local.site}: SystemSettings.enable_scheduler is UNSET")
  124 	return scheduler_disabled
  125 
  126 
  127 def toggle_scheduler(enable):
  128 	frappe.db.set_single_value("System Settings", "enable_scheduler", int(enable))
  129 
  130 
  131 def enable_scheduler():
  132 	toggle_scheduler(True)
  133 
  134 
  135 def disable_scheduler():
  136 	toggle_scheduler(False)
  137 
  138 
  139 def schedule_jobs_based_on_activity(check_time=None):
  140 	"""Returns True for active sites defined by Activity Log
  141 	Returns True for inactive sites once in 24 hours"""
  142 	if is_dormant(check_time=check_time):
  143 		# ensure last job is one day old
  144 		last_job_timestamp = _get_last_modified_timestamp("Scheduled Job Log")
  145 		if not last_job_timestamp:
  146 			return True
  147 		else:
  148 			if ((check_time or now_datetime()) - last_job_timestamp).total_seconds() >= 86400:
  149 				# one day is passed since jobs are run, so lets do this
  150 				return True
  151 			else:
  152 				# schedulers run in the last 24 hours, do nothing
  153 				return False
  154 	else:
  155 		# site active, lets run the jobs
  156 		return True
  157 
  158 
  159 def is_dormant(check_time=None):
  160 	last_activity_log_timestamp = _get_last_modified_timestamp("Activity Log")
  161 	since = (frappe.get_system_settings("dormant_days") or 4) * 86400
  162 	if not last_activity_log_timestamp:
  163 		return True
  164 	if ((check_time or now_datetime()) - last_activity_log_timestamp).total_seconds() >= since:
  165 		return True
  166 	return False
  167 
  168 
  169 def _get_last_modified_timestamp(doctype):
  170 	timestamp = frappe.db.get_value(
  171 		doctype, filters={}, fieldname="modified", order_by="modified desc"
  172 	)
  173 	if timestamp:
  174 		return get_datetime(timestamp)
  175 
  176 
  177 @frappe.whitelist()
  178 def activate_scheduler():
  179 	if is_scheduler_disabled():
  180 		enable_scheduler()
  181 	if frappe.conf.pause_scheduler:
  182 		update_site_config("pause_scheduler", 0)
  183 
  184 
  185 @frappe.whitelist()
  186 def get_scheduler_status():
  187 	if is_scheduler_inactive():
  188 		return {"status": "inactive"}
  189 	return {"status": "active"}
  190
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py
  --- a/frappe/utils/scheduler.py
  +++ b/frappe/utils/scheduler.py
  @@ -176,6 +176,11 @@
   
   @frappe.whitelist()
   def activate_scheduler():
  +	frappe.only_for("Administrator")
  +
  +	if frappe.local.conf.maintenance_mode:
  +		frappe.throw(frappe._("Scheduler can not be re-enabled when maintenance mode is active."))
  +
   	if is_scheduler_disabled():
   		enable_scheduler()
   	if frappe.conf.pause_scheduler:
  
verification_info: {"golden_diff": "diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py\n--- a/frappe/utils/scheduler.py\n+++ b/frappe/utils/scheduler.py\n@@ -176,6 +176,11 @@\n \n @frappe.whitelist()\n def activate_scheduler():\n+\tfrappe.only_for(\"Administrator\")\n+\n+\tif frappe.local.conf.maintenance_mode:\n+\t\tfrappe.throw(frappe._(\"Scheduler can not be re-enabled when maintenance mode is active.\"))\n+\n \tif is_scheduler_disabled():\n \t\tenable_scheduler()\n \tif frappe.conf.pause_scheduler:\n", "issue": "Enable Scheduler from desk\nFeature to enable scheduler from desk.\n", "before_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# License: MIT. See LICENSE\n\"\"\"\nEvents:\n\talways\n\tdaily\n\tmonthly\n\tweekly\n\"\"\"\n\n# imports - standard imports\nimport os\nimport time\nfrom typing import NoReturn\n\n# imports - module imports\nimport frappe\nfrom frappe.installer import update_site_config\nfrom frappe.utils import cint, get_datetime, get_sites, now_datetime\nfrom frappe.utils.background_jobs import get_jobs\n\nDATETIME_FORMAT = \"%Y-%m-%d %H:%M:%S\"\n\n\ndef cprint(*args, **kwargs):\n\t\"\"\"Prints only if called from STDOUT\"\"\"\n\ttry:\n\t\tos.get_terminal_size()\n\t\tprint(*args, **kwargs)\n\texcept Exception:\n\t\tpass\n\n\ndef start_scheduler() -> NoReturn:\n\t\"\"\"Run enqueue_events_for_all_sites based on scheduler tick.\n\tSpecify scheduler_interval in seconds in common_site_config.json\"\"\"\n\n\ttick = cint(frappe.get_conf().scheduler_tick_interval) or 60\n\n\twhile True:\n\t\ttime.sleep(tick)\n\t\tenqueue_events_for_all_sites()\n\n\ndef enqueue_events_for_all_sites() -> None:\n\t\"\"\"Loop through sites and enqueue events that are not already queued\"\"\"\n\n\tif os.path.exists(os.path.join(\".\", \".restarting\")):\n\t\t# Don't add task to queue if webserver is in restart mode\n\t\treturn\n\n\twith frappe.init_site():\n\t\tsites = get_sites()\n\n\tfor site in sites:\n\t\ttry:\n\t\t\tenqueue_events_for_site(site=site)\n\t\texcept Exception:\n\t\t\tfrappe.logger(\"scheduler\").debug(f\"Failed to enqueue events for site: {site}\", exc_info=True)\n\n\ndef enqueue_events_for_site(site: str) -> None:\n\tdef log_exc():\n\t\tfrappe.logger(\"scheduler\").error(f\"Exception in Enqueue Events for Site {site}\", exc_info=True)\n\n\ttry:\n\t\tfrappe.init(site=site)\n\t\tfrappe.connect()\n\t\tif is_scheduler_inactive():\n\t\t\treturn\n\n\t\tenqueue_events(site=site)\n\n\t\tfrappe.logger(\"scheduler\").debug(f\"Queued events for site {site}\")\n\texcept Exception as e:\n\t\tif frappe.db.is_access_denied(e):\n\t\t\tfrappe.logger(\"scheduler\").debug(f\"Access denied for site {site}\")\n\t\tlog_exc()\n\n\tfinally:\n\t\tfrappe.destroy()\n\n\ndef enqueue_events(site: str) -> list[str] | None:\n\tif schedule_jobs_based_on_activity():\n\t\tenqueued_jobs = []\n\t\tfor job_type in frappe.get_all(\"Scheduled Job Type\", (\"name\", \"method\"), {\"stopped\": 0}):\n\t\t\tjob_type = frappe.get_cached_doc(\"Scheduled Job Type\", job_type.name)\n\t\t\tif _enqueued := job_type.enqueue():\n\t\t\t\tenqueued_jobs.append(job_type.method)\n\n\t\treturn enqueued_jobs\n\n\ndef is_scheduler_inactive(verbose=True) -> bool:\n\tif frappe.local.conf.maintenance_mode:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: Maintenance mode is ON\")\n\t\treturn True\n\n\tif frappe.local.conf.pause_scheduler:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: frappe.conf.pause_scheduler is SET\")\n\t\treturn True\n\n\tif is_scheduler_disabled(verbose=verbose):\n\t\treturn True\n\n\treturn False\n\n\ndef is_scheduler_disabled(verbose=True) -> bool:\n\tif frappe.conf.disable_scheduler:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: frappe.conf.disable_scheduler is SET\")\n\t\treturn True\n\n\tscheduler_disabled = not frappe.utils.cint(\n\t\tfrappe.db.get_single_value(\"System Settings\", \"enable_scheduler\")\n\t)\n\tif scheduler_disabled:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: SystemSettings.enable_scheduler is UNSET\")\n\treturn scheduler_disabled\n\n\ndef toggle_scheduler(enable):\n\tfrappe.db.set_single_value(\"System Settings\", \"enable_scheduler\", int(enable))\n\n\ndef enable_scheduler():\n\ttoggle_scheduler(True)\n\n\ndef disable_scheduler():\n\ttoggle_scheduler(False)\n\n\ndef schedule_jobs_based_on_activity(check_time=None):\n\t\"\"\"Returns True for active sites defined by Activity Log\n\tReturns True for inactive sites once in 24 hours\"\"\"\n\tif is_dormant(check_time=check_time):\n\t\t# ensure last job is one day old\n\t\tlast_job_timestamp = _get_last_modified_timestamp(\"Scheduled Job Log\")\n\t\tif not last_job_timestamp:\n\t\t\treturn True\n\t\telse:\n\t\t\tif ((check_time or now_datetime()) - last_job_timestamp).total_seconds() >= 86400:\n\t\t\t\t# one day is passed since jobs are run, so lets do this\n\t\t\t\treturn True\n\t\t\telse:\n\t\t\t\t# schedulers run in the last 24 hours, do nothing\n\t\t\t\treturn False\n\telse:\n\t\t# site active, lets run the jobs\n\t\treturn True\n\n\ndef is_dormant(check_time=None):\n\tlast_activity_log_timestamp = _get_last_modified_timestamp(\"Activity Log\")\n\tsince = (frappe.get_system_settings(\"dormant_days\") or 4) * 86400\n\tif not last_activity_log_timestamp:\n\t\treturn True\n\tif ((check_time or now_datetime()) - last_activity_log_timestamp).total_seconds() >= since:\n\t\treturn True\n\treturn False\n\n\ndef _get_last_modified_timestamp(doctype):\n\ttimestamp = frappe.db.get_value(\n\t\tdoctype, filters={}, fieldname=\"modified\", order_by=\"modified desc\"\n\t)\n\tif timestamp:\n\t\treturn get_datetime(timestamp)\n\n\n@frappe.whitelist()\ndef activate_scheduler():\n\tif is_scheduler_disabled():\n\t\tenable_scheduler()\n\tif frappe.conf.pause_scheduler:\n\t\tupdate_site_config(\"pause_scheduler\", 0)\n\n\n@frappe.whitelist()\ndef get_scheduler_status():\n\tif is_scheduler_inactive():\n\t\treturn {\"status\": \"inactive\"}\n\treturn {\"status\": \"active\"}\n", "path": "frappe/utils/scheduler.py"}], "after_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# License: MIT. See LICENSE\n\"\"\"\nEvents:\n\talways\n\tdaily\n\tmonthly\n\tweekly\n\"\"\"\n\n# imports - standard imports\nimport os\nimport time\nfrom typing import NoReturn\n\n# imports - module imports\nimport frappe\nfrom frappe.installer import update_site_config\nfrom frappe.utils import cint, get_datetime, get_sites, now_datetime\nfrom frappe.utils.background_jobs import get_jobs\n\nDATETIME_FORMAT = \"%Y-%m-%d %H:%M:%S\"\n\n\ndef cprint(*args, **kwargs):\n\t\"\"\"Prints only if called from STDOUT\"\"\"\n\ttry:\n\t\tos.get_terminal_size()\n\t\tprint(*args, **kwargs)\n\texcept Exception:\n\t\tpass\n\n\ndef start_scheduler() -> NoReturn:\n\t\"\"\"Run enqueue_events_for_all_sites based on scheduler tick.\n\tSpecify scheduler_interval in seconds in common_site_config.json\"\"\"\n\n\ttick = cint(frappe.get_conf().scheduler_tick_interval) or 60\n\n\twhile True:\n\t\ttime.sleep(tick)\n\t\tenqueue_events_for_all_sites()\n\n\ndef enqueue_events_for_all_sites() -> None:\n\t\"\"\"Loop through sites and enqueue events that are not already queued\"\"\"\n\n\tif os.path.exists(os.path.join(\".\", \".restarting\")):\n\t\t# Don't add task to queue if webserver is in restart mode\n\t\treturn\n\n\twith frappe.init_site():\n\t\tsites = get_sites()\n\n\tfor site in sites:\n\t\ttry:\n\t\t\tenqueue_events_for_site(site=site)\n\t\texcept Exception:\n\t\t\tfrappe.logger(\"scheduler\").debug(f\"Failed to enqueue events for site: {site}\", exc_info=True)\n\n\ndef enqueue_events_for_site(site: str) -> None:\n\tdef log_exc():\n\t\tfrappe.logger(\"scheduler\").error(f\"Exception in Enqueue Events for Site {site}\", exc_info=True)\n\n\ttry:\n\t\tfrappe.init(site=site)\n\t\tfrappe.connect()\n\t\tif is_scheduler_inactive():\n\t\t\treturn\n\n\t\tenqueue_events(site=site)\n\n\t\tfrappe.logger(\"scheduler\").debug(f\"Queued events for site {site}\")\n\texcept Exception as e:\n\t\tif frappe.db.is_access_denied(e):\n\t\t\tfrappe.logger(\"scheduler\").debug(f\"Access denied for site {site}\")\n\t\tlog_exc()\n\n\tfinally:\n\t\tfrappe.destroy()\n\n\ndef enqueue_events(site: str) -> list[str] | None:\n\tif schedule_jobs_based_on_activity():\n\t\tenqueued_jobs = []\n\t\tfor job_type in frappe.get_all(\"Scheduled Job Type\", (\"name\", \"method\"), {\"stopped\": 0}):\n\t\t\tjob_type = frappe.get_cached_doc(\"Scheduled Job Type\", job_type.name)\n\t\t\tif _enqueued := job_type.enqueue():\n\t\t\t\tenqueued_jobs.append(job_type.method)\n\n\t\treturn enqueued_jobs\n\n\ndef is_scheduler_inactive(verbose=True) -> bool:\n\tif frappe.local.conf.maintenance_mode:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: Maintenance mode is ON\")\n\t\treturn True\n\n\tif frappe.local.conf.pause_scheduler:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: frappe.conf.pause_scheduler is SET\")\n\t\treturn True\n\n\tif is_scheduler_disabled(verbose=verbose):\n\t\treturn True\n\n\treturn False\n\n\ndef is_scheduler_disabled(verbose=True) -> bool:\n\tif frappe.conf.disable_scheduler:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: frappe.conf.disable_scheduler is SET\")\n\t\treturn True\n\n\tscheduler_disabled = not frappe.utils.cint(\n\t\tfrappe.db.get_single_value(\"System Settings\", \"enable_scheduler\")\n\t)\n\tif scheduler_disabled:\n\t\tif verbose:\n\t\t\tcprint(f\"{frappe.local.site}: SystemSettings.enable_scheduler is UNSET\")\n\treturn scheduler_disabled\n\n\ndef toggle_scheduler(enable):\n\tfrappe.db.set_single_value(\"System Settings\", \"enable_scheduler\", int(enable))\n\n\ndef enable_scheduler():\n\ttoggle_scheduler(True)\n\n\ndef disable_scheduler():\n\ttoggle_scheduler(False)\n\n\ndef schedule_jobs_based_on_activity(check_time=None):\n\t\"\"\"Returns True for active sites defined by Activity Log\n\tReturns True for inactive sites once in 24 hours\"\"\"\n\tif is_dormant(check_time=check_time):\n\t\t# ensure last job is one day old\n\t\tlast_job_timestamp = _get_last_modified_timestamp(\"Scheduled Job Log\")\n\t\tif not last_job_timestamp:\n\t\t\treturn True\n\t\telse:\n\t\t\tif ((check_time or now_datetime()) - last_job_timestamp).total_seconds() >= 86400:\n\t\t\t\t# one day is passed since jobs are run, so lets do this\n\t\t\t\treturn True\n\t\t\telse:\n\t\t\t\t# schedulers run in the last 24 hours, do nothing\n\t\t\t\treturn False\n\telse:\n\t\t# site active, lets run the jobs\n\t\treturn True\n\n\ndef is_dormant(check_time=None):\n\tlast_activity_log_timestamp = _get_last_modified_timestamp(\"Activity Log\")\n\tsince = (frappe.get_system_settings(\"dormant_days\") or 4) * 86400\n\tif not last_activity_log_timestamp:\n\t\treturn True\n\tif ((check_time or now_datetime()) - last_activity_log_timestamp).total_seconds() >= since:\n\t\treturn True\n\treturn False\n\n\ndef _get_last_modified_timestamp(doctype):\n\ttimestamp = frappe.db.get_value(\n\t\tdoctype, filters={}, fieldname=\"modified\", order_by=\"modified desc\"\n\t)\n\tif timestamp:\n\t\treturn get_datetime(timestamp)\n\n\n@frappe.whitelist()\ndef activate_scheduler():\n\tfrappe.only_for(\"Administrator\")\n\n\tif frappe.local.conf.maintenance_mode:\n\t\tfrappe.throw(frappe._(\"Scheduler can not be re-enabled when maintenance mode is active.\"))\n\n\tif is_scheduler_disabled():\n\t\tenable_scheduler()\n\tif frappe.conf.pause_scheduler:\n\t\tupdate_site_config(\"pause_scheduler\", 0)\n\n\n@frappe.whitelist()\ndef get_scheduler_status():\n\tif is_scheduler_inactive():\n\t\treturn {\"status\": \"inactive\"}\n\treturn {\"status\": \"active\"}\n", "path": "frappe/utils/scheduler.py"}]}