prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Move app installation to background
  Long installs timeout the installation of the app and leads to broken installs.
  
  
  </issue>
  <code>
  [start of frappe/desk/page/applications/applications.py]
  1 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2 # MIT License. See license.txt
  3 
  4 from __future__ import unicode_literals
  5 import frappe
  6 import frappe.utils
  7 import frappe.installer
  8 import frappe.sessions
  9 import subprocess
  10 import os
  11 import json
  12 from frappe import _
  13 from distutils.spawn import find_executable
  14 
  15 @frappe.whitelist()
  16 def get_app_list():
  17 	"""Get list of all apps with properties, installed, category from hooks and
  18 	`frappe/data/app_listing/` if an entry exists"""
  19 	out = {}
  20 	installed = frappe.get_installed_apps()
  21 	for app in frappe.get_all_apps(True):
  22 		app_hooks = frappe.get_hooks(app_name=app)
  23 
  24 		if app not in installed and app_hooks.get('hide_in_installer'):
  25 			continue
  26 
  27 		out[app] = {}
  28 		for key in ("app_name", "app_title", "app_description", "app_icon",
  29 			"app_publisher", "app_version", "app_url", "app_color"):
  30 			 val = app_hooks.get(key) or []
  31 			 out[app][key] = val[0] if len(val) else ""
  32 
  33 		if app in installed:
  34 			out[app]["installed"] = 1
  35 
  36 	for app_from_list in get_app_listing().values():
  37 		if app_from_list.app_name in out:
  38 			out[app_from_list.app_name].update(app_from_list)
  39 		else:
  40 			if not frappe.conf.disallow_app_listing:
  41 				out[app_from_list.app_name] = app_from_list
  42 
  43 	return out
  44 
  45 def get_app_listing():
  46 	"""Get apps listed in `frappe/data/app_listing/`"""
  47 	apps_listing_dir = os.path.join(os.path.dirname(frappe.__file__), 'data', 'app_listing')
  48 	out = {}
  49 	for app in os.listdir(apps_listing_dir):
  50 		if app.endswith(".json"):
  51 			with open(os.path.join(apps_listing_dir, app)) as f:
  52 				out[app[:-5]] = frappe._dict(json.load(f))
  53 	return out
  54 
  55 @frappe.whitelist()
  56 def install_app(name):
  57 	"""Install app, if app is not installed in local environment, install it via git url in
  58 	`frappe/data/app_listing/`"""
  59 	frappe.only_for("System Manager")
  60 
  61 	if name not in frappe.get_all_apps(True):
  62 		if not frappe.conf.disallow_app_listing:
  63 			get_app(name)
  64 			frappe.cache().delete_value(["app_hooks"])
  65 			# reload sys.path
  66 			import site
  67 			reload(site)
  68 		else:
  69 			# will only come via direct API
  70 			frappe.throw("Listing app not allowed")
  71 
  72 	app_hooks = frappe.get_hooks(app_name=name)
  73 	if app_hooks.get('hide_in_installer'):
  74 		frappe.throw(_("You cannot install this app"))
  75 
  76 	frappe.publish_realtime("install_app_progress", {"status": _("Installing App {0}").format(name)},
  77 		user=frappe.session.user)
  78 
  79 	frappe.installer.install_app(name)
  80 
  81 	frappe.publish_realtime("install_app_progress", {"status": _("{0} Installed").format(name)},
  82 		user=frappe.session.user)
  83 
  84 def get_app(name):
  85 	"""Get app using git clone and install it in bench environment"""
  86 	app_listing = get_app_listing()
  87 	if name not in app_listing:
  88 		frappe.throw(_("Unknown app {0}").format(name))
  89 		raise frappe.ValidationError
  90 
  91 	frappe.publish_realtime("install_app_progress", {"status": _("Downloading App {0}").format(name)},
  92 		user=frappe.session.user)
  93 
  94 	args = [find_executable('bench'), 'get-app', name, app_listing[name]['repo_url']]
  95 
  96 	try:
  97 		subprocess.check_call(args, cwd=frappe.utils.get_bench_path(),
  98 			stderr=subprocess.STDOUT)
  99 		return "okay"
  100 	except subprocess.CalledProcessError as e:
  101 		frappe.msgprint("<b>" + " ".join(args) + "</b>")
  102 		frappe.msgprint(e.output)
  103 		return e.output
  104
  [end of frappe/desk/page/applications/applications.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/frappe/desk/page/applications/applications.py b/frappe/desk/page/applications/applications.py
  --- a/frappe/desk/page/applications/applications.py
  +++ b/frappe/desk/page/applications/applications.py
  @@ -11,6 +11,7 @@
   import json
   from frappe import _
   from distutils.spawn import find_executable
  +from frappe.utils.background_jobs import enqueue
   
   @frappe.whitelist()
   def get_app_list():
  @@ -73,6 +74,12 @@
   	if app_hooks.get('hide_in_installer'):
   		frappe.throw(_("You cannot install this app"))
   
  +	enqueue('frappe.desk.page.applications.applications.start_install', name=name)
  +
  +	frappe.msgprint(_('Queued for install'))
  +
  +
  +def start_install(name):
   	frappe.publish_realtime("install_app_progress", {"status": _("Installing App {0}").format(name)},
   		user=frappe.session.user)
   
  @@ -81,6 +88,20 @@
   	frappe.publish_realtime("install_app_progress", {"status": _("{0} Installed").format(name)},
   		user=frappe.session.user)
   
  +@frappe.whitelist()
  +def remove_app(name):
  +	"""Remove installed app"""
  +	frappe.only_for("System Manager")
  +
  +	if name in frappe.get_installed_apps():
  +		enqueue('frappe.desk.page.applications.applications.start_remove', name=name)
  +
  +	frappe.msgprint(_('Queued for backup and removing {0}').format(frappe.bold(name)))
  +
  +def start_remove(name):
  +	frappe.installer.remove_app(app_name=name, yes=True)
  +	frappe.publish_realtime('msgprint', _('App {0} removed').format(frappe.bold(name)))
  +
   def get_app(name):
   	"""Get app using git clone and install it in bench environment"""
   	app_listing = get_app_listing()
  
verification_info: {"golden_diff": "diff --git a/frappe/desk/page/applications/applications.py b/frappe/desk/page/applications/applications.py\n--- a/frappe/desk/page/applications/applications.py\n+++ b/frappe/desk/page/applications/applications.py\n@@ -11,6 +11,7 @@\n import json\n from frappe import _\n from distutils.spawn import find_executable\n+from frappe.utils.background_jobs import enqueue\n \n @frappe.whitelist()\n def get_app_list():\n@@ -73,6 +74,12 @@\n \tif app_hooks.get('hide_in_installer'):\n \t\tfrappe.throw(_(\"You cannot install this app\"))\n \n+\tenqueue('frappe.desk.page.applications.applications.start_install', name=name)\n+\n+\tfrappe.msgprint(_('Queued for install'))\n+\n+\n+def start_install(name):\n \tfrappe.publish_realtime(\"install_app_progress\", {\"status\": _(\"Installing App {0}\").format(name)},\n \t\tuser=frappe.session.user)\n \n@@ -81,6 +88,20 @@\n \tfrappe.publish_realtime(\"install_app_progress\", {\"status\": _(\"{0} Installed\").format(name)},\n \t\tuser=frappe.session.user)\n \n+@frappe.whitelist()\n+def remove_app(name):\n+\t\"\"\"Remove installed app\"\"\"\n+\tfrappe.only_for(\"System Manager\")\n+\n+\tif name in frappe.get_installed_apps():\n+\t\tenqueue('frappe.desk.page.applications.applications.start_remove', name=name)\n+\n+\tfrappe.msgprint(_('Queued for backup and removing {0}').format(frappe.bold(name)))\n+\n+def start_remove(name):\n+\tfrappe.installer.remove_app(app_name=name, yes=True)\n+\tfrappe.publish_realtime('msgprint', _('App {0} removed').format(frappe.bold(name)))\n+\n def get_app(name):\n \t\"\"\"Get app using git clone and install it in bench environment\"\"\"\n \tapp_listing = get_app_listing()\n", "issue": "Move app installation to background\nLong installs timeout the installation of the app and leads to broken installs.\n\n", "before_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# MIT License. See license.txt\n\nfrom __future__ import unicode_literals\nimport frappe\nimport frappe.utils\nimport frappe.installer\nimport frappe.sessions\nimport subprocess\nimport os\nimport json\nfrom frappe import _\nfrom distutils.spawn import find_executable\n\n@frappe.whitelist()\ndef get_app_list():\n\t\"\"\"Get list of all apps with properties, installed, category from hooks and\n\t`frappe/data/app_listing/` if an entry exists\"\"\"\n\tout = {}\n\tinstalled = frappe.get_installed_apps()\n\tfor app in frappe.get_all_apps(True):\n\t\tapp_hooks = frappe.get_hooks(app_name=app)\n\n\t\tif app not in installed and app_hooks.get('hide_in_installer'):\n\t\t\tcontinue\n\n\t\tout[app] = {}\n\t\tfor key in (\"app_name\", \"app_title\", \"app_description\", \"app_icon\",\n\t\t\t\"app_publisher\", \"app_version\", \"app_url\", \"app_color\"):\n\t\t\t val = app_hooks.get(key) or []\n\t\t\t out[app][key] = val[0] if len(val) else \"\"\n\n\t\tif app in installed:\n\t\t\tout[app][\"installed\"] = 1\n\n\tfor app_from_list in get_app_listing().values():\n\t\tif app_from_list.app_name in out:\n\t\t\tout[app_from_list.app_name].update(app_from_list)\n\t\telse:\n\t\t\tif not frappe.conf.disallow_app_listing:\n\t\t\t\tout[app_from_list.app_name] = app_from_list\n\n\treturn out\n\ndef get_app_listing():\n\t\"\"\"Get apps listed in `frappe/data/app_listing/`\"\"\"\n\tapps_listing_dir = os.path.join(os.path.dirname(frappe.__file__), 'data', 'app_listing')\n\tout = {}\n\tfor app in os.listdir(apps_listing_dir):\n\t\tif app.endswith(\".json\"):\n\t\t\twith open(os.path.join(apps_listing_dir, app)) as f:\n\t\t\t\tout[app[:-5]] = frappe._dict(json.load(f))\n\treturn out\n\n@frappe.whitelist()\ndef install_app(name):\n\t\"\"\"Install app, if app is not installed in local environment, install it via git url in\n\t`frappe/data/app_listing/`\"\"\"\n\tfrappe.only_for(\"System Manager\")\n\n\tif name not in frappe.get_all_apps(True):\n\t\tif not frappe.conf.disallow_app_listing:\n\t\t\tget_app(name)\n\t\t\tfrappe.cache().delete_value([\"app_hooks\"])\n\t\t\t# reload sys.path\n\t\t\timport site\n\t\t\treload(site)\n\t\telse:\n\t\t\t# will only come via direct API\n\t\t\tfrappe.throw(\"Listing app not allowed\")\n\n\tapp_hooks = frappe.get_hooks(app_name=name)\n\tif app_hooks.get('hide_in_installer'):\n\t\tfrappe.throw(_(\"You cannot install this app\"))\n\n\tfrappe.publish_realtime(\"install_app_progress\", {\"status\": _(\"Installing App {0}\").format(name)},\n\t\tuser=frappe.session.user)\n\n\tfrappe.installer.install_app(name)\n\n\tfrappe.publish_realtime(\"install_app_progress\", {\"status\": _(\"{0} Installed\").format(name)},\n\t\tuser=frappe.session.user)\n\ndef get_app(name):\n\t\"\"\"Get app using git clone and install it in bench environment\"\"\"\n\tapp_listing = get_app_listing()\n\tif name not in app_listing:\n\t\tfrappe.throw(_(\"Unknown app {0}\").format(name))\n\t\traise frappe.ValidationError\n\n\tfrappe.publish_realtime(\"install_app_progress\", {\"status\": _(\"Downloading App {0}\").format(name)},\n\t\tuser=frappe.session.user)\n\n\targs = [find_executable('bench'), 'get-app', name, app_listing[name]['repo_url']]\n\n\ttry:\n\t\tsubprocess.check_call(args, cwd=frappe.utils.get_bench_path(),\n\t\t\tstderr=subprocess.STDOUT)\n\t\treturn \"okay\"\n\texcept subprocess.CalledProcessError as e:\n\t\tfrappe.msgprint(\"<b>\" + \" \".join(args) + \"</b>\")\n\t\tfrappe.msgprint(e.output)\n\t\treturn e.output\n", "path": "frappe/desk/page/applications/applications.py"}]}