async def _handle_job_request(self, web_request: WebRequest) -> Dict[str, Any]: action = web_request.get_action() if action == "POST": files: Union[List[str], str] = web_request.get('filenames') if isinstance(files, str): files = [f.strip() for f in files.split(',') if f.strip()] # Validate that all files exist before queueing await self.queue_job(files) elif action == "DELETE": if web_request.get_boolean("all", False): await self.delete_job([], all=True) else: job_ids: Union[List[str], str] = web_request.get('job_ids') if isinstance(job_ids, str): job_ids = [ f.strip() for f in job_ids.split(',') if f.strip() ] await self.delete_job(job_ids) else: raise self.server.error(f"Invalid action: {action}") return { 'queued_jobs': self._job_map_to_list(), 'queue_state': self.queue_state }
async def _handle_item_request(self, web_request: WebRequest) -> Dict[str, Any]: action = web_request.get_action() namespace = web_request.get_str("namespace") if namespace in self.forbidden_namespaces: raise self.server.error( f"Read/Write access to namespace '{namespace}'" " is forbidden", 403) key: Any valid_types: Tuple[type, ...] if action != "GET": if namespace in self.protected_namespaces: raise self.server.error( f"Write access to namespace '{namespace}'" " is forbidden", 403) key = web_request.get("key") valid_types = (list, str) else: key = web_request.get("key", None) valid_types = (list, str, type(None)) if not isinstance(key, valid_types): raise self.server.error( "Value for argument 'key' is an invalid type: " f"{type(key).__name__}") if action == "GET": val = await self.get_item(namespace, key) elif action == "POST": val = web_request.get("value") await self.insert_item(namespace, key, val) elif action == "DELETE": val = await self.delete_item(namespace, key, drop_empty_db=True) return {'namespace': namespace, 'key': key, 'value': val}
async def _handle_update_request(self, web_request: WebRequest ) -> str: await self.initialized_lock.wait() if await self._check_klippy_printing(): raise self.server.error("Update Refused: Klippy is printing") app: str = web_request.get_endpoint().split("/")[-1] if app == "client": app = web_request.get('name') if self.cmd_helper.is_app_updating(app): return f"Object {app} is currently being updated" updater = self.updaters.get(app, None) if updater is None: raise self.server.error(f"Updater {app} not available", 404) async with self.cmd_request_lock: self.cmd_helper.set_update_info(app, id(web_request)) try: if not await self._check_need_reinstall(app): await updater.update() except Exception as e: self.cmd_helper.notify_update_response( f"Error updating {app}") self.cmd_helper.notify_update_response( str(e), is_complete=True) raise finally: self.cmd_helper.clear_update_info() return "ok"
async def _handle_update_request( self, web_request: WebRequest ) -> Dict[str, Any]: subs: Optional[Union[str, List[str]]] subs = web_request.get("subscriptions", None) if isinstance(subs, str): subs = [sub.strip() for sub in subs.split(",") if sub.strip()] elif subs is None: subs = list(self.subscriptions.keys()) for sub in subs: if sub not in self.subscriptions: raise self.server.error(f"No subscription for {sub}") async with self.request_lock: changed = False for sub in subs: ret = await self.subscriptions[sub].update_entries() changed |= ret entries = await self.entry_mgr.list_entries() if changed: self.eventloop.delay_callback( .05, self.server.send_event, "announcements:entries_updated", {"entries": entries}) return { "entries": entries, "modified": changed }
async def _select_file(self, web_request: WebRequest ) -> None: command: str = web_request.get('command') rel_path: str = web_request.get('relative_path') root, filename = rel_path.strip("/").split("/", 1) fmgr: FileManager = self.server.lookup_component('file_manager') if command == "select": start_print: bool = web_request.get('print', False) if not start_print: # No-op, selecting a file has no meaning in Moonraker return if root != "gcodes": raise self.server.error( "File must be located in the 'gcodes' root", 400) if not fmgr.check_file_exists(root, filename): raise self.server.error("File does not exist") try: ret = await self.klippy_apis.query_objects( {'print_stats': None}) pstate: str = ret['print_stats']['state'] except self.server.error: pstate = "not_avail" started: bool = False if pstate not in ["printing", "paused", "not_avail"]: try: await self.klippy_apis.start_print(filename) except self.server.error: started = False else: logging.debug(f"Job '{filename}' started via Octoprint API") started = True if not started: if fmgr.upload_queue_enabled(): job_queue: JobQueue = self.server.lookup_component( 'job_queue') await job_queue.queue_job(filename, check_exists=False) # Fire the file_manager's upload_queued event for # compatibility. We assume that this endpoint is # requests by Cura after a file has been uploaded. self.server.send_event("file_manager:upload_queued", filename) logging.debug(f"Job '{filename}' queued via Octoprint API") else: raise self.server.error("Conflict", 409) else: raise self.server.error(f"Unsupported Command: {command}")
async def _handle_call_agent(self, web_request: WebRequest) -> Any: agent = web_request.get_str("agent") method: str = web_request.get_str("method") args: Optional[Union[List, Dict[str, Any]]] args = web_request.get("arguments", None) if args is not None and not isinstance(args, (list, dict)): raise self.server.error( "The 'arguments' field must contain an object or a list") if agent not in self.agents: raise self.server.error(f"Agent {agent} not connected") conn = self.agents[agent] return await conn.call_method(method, args)
async def _handle_publish_request( self, web_request: WebRequest) -> Dict[str, Any]: topic: str = web_request.get_str("topic") payload: Any = web_request.get("payload", None) qos: int = web_request.get_int("qos", self.qos) retain: bool = web_request.get_boolean("retain", False) timeout: Optional[float] = web_request.get_float('timeout', None) try: await asyncio.wait_for( self.publish_topic(topic, payload, qos, retain), timeout) except asyncio.TimeoutError: raise self.server.error("MQTT Publish Timed Out", 504) return {"topic": topic}
async def _post_command(self, web_request: WebRequest) -> Dict: """ Request to run some gcode command """ commands: List[str] = web_request.get('commands', []) for command in commands: logging.info(f'Executing GCode: {command}') try: await self.klippy_apis.run_gcode(command) except self.server.error: msg = f"Error executing GCode {command}" logging.exception(msg) return {}
async def _handle_feed_request( self, web_request: WebRequest ) -> Dict[str, Any]: action = web_request.get_action() name: str = web_request.get("name") name = name.lower() changed: bool = False db: MoonrakerDatabase = self.server.lookup_component("database") result = "skipped" if action == "POST": if name not in self.subscriptions: feed = RssFeed(name, self.entry_mgr, self.dev_mode) self.subscriptions[name] = feed await feed.initialize() changed = await feed.update_entries() self.stored_feeds.append(name) db.insert_item( "moonraker", "announcements.stored_feeds", self.stored_feeds ) result = "added" elif action == "DELETE": if name not in self.stored_feeds: raise self.server.error(f"Feed '{name}' not stored") if name in self.configured_feeds: raise self.server.error( f"Feed '{name}' exists in the configuration, cannot remove" ) self.stored_feeds.remove(name) db.insert_item( "moonraker", "announcements.stored_feeds", self.stored_feeds ) if name in self.subscriptions: del self.subscriptions[name] changed = await self.entry_mgr.prune_by_feed(name) logging.info(f"Removed Announcement Feed: {name}") result = "removed" else: raise self.server.error(f"Feed does not exist: {name}") if changed: entries = await self.entry_mgr.list_entries() self.eventloop.delay_callback( .05, self.server.send_event, "announcements:entries_updated", {"entries": entries} ) return { "feed": name, "action": result }
async def _handle_service_request(self, web_request: WebRequest) -> str: name: str = web_request.get('service') action = web_request.get_endpoint().split('/')[-1] if name == "moonraker": if action != "restart": raise self.server.error( f"Service action '{action}' not available for moonraker") event_loop = self.server.get_event_loop() event_loop.register_callback(self.do_service_action, action, name) elif self.sys_provider.is_service_available(name): await self.do_service_action(action, name) else: if name in ALLOWED_SERVICES: raise self.server.error(f"Service '{name}' not installed") raise self.server.error(f"Service '{name}' not allowed") return "ok"
async def _handle_service_request(self, web_request: WebRequest) -> str: name: str = web_request.get('service') action = web_request.get_endpoint().split('/')[-1] if name == "moonraker": if action != "restart": raise self.server.error( f"Service action '{action}' not available for moonraker") IOLoop.current().spawn_callback(self.do_service_action, action, name) elif name in self.available_services: await self.do_service_action(action, name) else: if name in ALLOWED_SERVICES and \ name not in self.available_services: raise self.server.error(f"Service '{name}' not installed") raise self.server.error(f"Service '{name}' not allowed") return "ok"
async def _handle_agent_event(self, web_request: WebRequest) -> str: conn = web_request.get_connection() if not isinstance(conn, WebSocket): raise self.server.error("No connection detected") if conn.client_data["type"] != "agent": raise self.server.error( "Only connections of the 'agent' type can send events") name = conn.client_data["name"] evt_name = web_request.get_str("event") if evt_name in ["connected", "disconnected"]: raise self.server.error(f"Event '{evt_name}' is reserved") data: Optional[Union[List, Dict[str, Any]]] data = web_request.get("data", None) evt: Dict[str, Any] = {"agent": name, "event": evt_name} if data is not None: evt["data"] = data conn.send_notification("agent_event", [evt]) return "ok"