From 2aa5feceb4fb824dd8b5fa40adce5e23b606488e Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 6 Jun 2021 13:00:55 +0200 Subject: [PATCH] Added upload controller Integrated recipe file upload into cbpi - Controller and http endpoint added --> No extension required --- .../upload_controller.py} | 165 ++++++------------ cbpi/craftbeerpi.py | 3 + cbpi/extension/ReciepeUpload/__init__.py | 83 --------- cbpi/extension/ReciepeUpload/config.yaml | 3 - cbpi/extension/RecipeUpload/config.yaml | 3 - cbpi/http_endpoints/http_upload.py | 73 ++++---- 6 files changed, 92 insertions(+), 238 deletions(-) rename cbpi/{extension/RecipeUpload/__init__.py => controller/upload_controller.py} (85%) delete mode 100644 cbpi/extension/ReciepeUpload/__init__.py delete mode 100644 cbpi/extension/ReciepeUpload/config.yaml delete mode 100644 cbpi/extension/RecipeUpload/config.yaml diff --git a/cbpi/extension/RecipeUpload/__init__.py b/cbpi/controller/upload_controller.py similarity index 85% rename from cbpi/extension/RecipeUpload/__init__.py rename to cbpi/controller/upload_controller.py index beca115..1c82448 100644 --- a/cbpi/extension/RecipeUpload/__init__.py +++ b/cbpi/controller/upload_controller.py @@ -1,11 +1,7 @@ - -# -*- coding: utf-8 -*- import os import pathlib import aiohttp from aiohttp import web -import logging -from unittest.mock import MagicMock, patch import asyncio from cbpi.api import * import xml.etree.ElementTree @@ -15,135 +11,101 @@ from cbpi.api.dataclasses import NotificationAction, NotificationType from cbpi.controller.kettle_controller import KettleController from cbpi.api.base import CBPiBase from cbpi.api.config import ConfigType -import json import webbrowser -logger = logging.getLogger(__name__) +import logging +import os.path +from os import listdir +from os.path import isfile, join +import json +import shortuuid +import yaml +from ..api.step import StepMove, StepResult, StepState -async def get_kbh_recipes(): - try: - path = os.path.join(".", 'config', "upload", "kbh.db") - conn = sqlite3.connect(path) - c = conn.cursor() - c.execute('SELECT ID, Sudname, Status FROM Sud') - data = c.fetchall() - result = [] - for row in data: - element = {'value': str(row[0]), 'label': str(row[1])} - result.append(element) - return result - except: - return [] +import re -async def get_xml_recipes(): - try: - path = os.path.join(".", 'config', "upload", "beer.xml") - e = xml.etree.ElementTree.parse(path).getroot() - result =[] - counter = 1 - for idx, val in enumerate(e.findall('RECIPE')): - element = {'value': str(counter), 'label': val.find("NAME").text} - result.append(element) - counter +=1 - return result - except: - return [] +class UploadController: -class RecipeUpload(CBPiExtension): def __init__(self, cbpi): self.cbpi = cbpi - self.cbpi.register(self, "/upload") + self.logger = logging.getLogger(__name__) + + async def get_kbh_recipes(self): + try: + path = os.path.join(".", 'config', "upload", "kbh.db") + conn = sqlite3.connect(path) + c = conn.cursor() + c.execute('SELECT ID, Sudname, Status FROM Sud') + data = c.fetchall() + result = [] + for row in data: + element = {'value': str(row[0]), 'label': str(row[1])} + result.append(element) + return result + except: + return [] - def allowed_file(self, filename, extension): - return '.' in filename and filename.rsplit('.', 1)[1] in set([extension]) + async def get_xml_recipes(self): + try: + path = os.path.join(".", 'config', "upload", "beer.xml") + e = xml.etree.ElementTree.parse(path).getroot() + result =[] + counter = 1 + for idx, val in enumerate(e.findall('RECIPE')): + element = {'value': str(counter), 'label': val.find("NAME").text} + result.append(element) + counter +=1 + return result + except: + return [] def get_creation_path(self): creation_path = self.cbpi.config.get("RECIPE_CREATION_PATH", "upload") path = {'path': 'upload'} if creation_path == '' else {'path': creation_path} return path + def allowed_file(self, filename, extension): + return '.' in filename and filename.rsplit('.', 1)[1] in set([extension]) - @request_mapping(path='/', method="POST", auth_required=False) - async def RecipeUpload(self, request): - data = await request.post() + + async def FileUpload(self, data): fileData = data['File'] logging.info(fileData) + filename = fileData.filename + recipe_file = fileData.file + content_type = fileData.content_type - if fileData.content_type == 'text/xml': - logging.info(fileData.content_type) + if content_type == 'text/xml': try: - filename = fileData.filename - beerxml_file = fileData.file - content = beerxml_file.read().decode() - if beerxml_file and self.allowed_file(filename, 'xml'): + beer_xml = recipe_file.read().decode() + if recipe_file and self.allowed_file(filename, 'xml'): self.path = os.path.join(".", 'config', "upload", "beer.xml") f = open(self.path, "w") - f.write(content) + f.write(beer_xml) f.close() - self.cbpi.notify("Success", "XML Recipe {} has been uploaded".format(filename), NotificationType.SUCCESS) + self.cbpi.notify("Success", "XML Recipe {} has been uploaded".format(filename), NotificationType.SUCCESS) except: self.cbpi.notify("Error" "XML Recipe upload failed", NotificationType.ERROR) pass - elif fileData.content_type == 'application/octet-stream': + elif content_type == 'application/octet-stream': try: - filename = fileData.filename - logger.info(filename) - kbh_file = fileData.file - content = kbh_file.read() - if kbh_file and self.allowed_file(filename, 'sqlite'): + content = recipe_file.read() + if recipe_file and self.allowed_file(filename, 'sqlite'): self.path = os.path.join(".", 'config', "upload", "kbh.db") f=open(self.path, "wb") f.write(content) f.close() - self.cbpi.notify("Success", "Kleiner Brauhelfer database has been uploaded", NotificationType.SUCCESS) + self.cbpi.notify("Success", "Kleiner Brauhelfer database has been uploaded", NotificationType.SUCCESS) + except: self.cbpi.notify("Error", "Kleiner Brauhelfer database upload failed", NotificationType.ERROR) pass else: self.cbpi.notify("Error", "Wrong content type. Upload failed", NotificationType.ERROR) - return web.Response(status=200) - - @request_mapping(path='/kbh', method="GET", auth_required=False) - async def get_kbh_list(self, request): - kbh_list = await get_kbh_recipes() - return web.json_response(kbh_list) - - @request_mapping(path='/kbh', method="POST", auth_required=False) - async def create_kbh_recipe(self, request): - kbh_id = await request.json() - await self.kbh_recipe_creation(kbh_id['id']) - return web.Response(status=200) - - @request_mapping(path='/xml', method="GET", auth_required=False) - async def get_xml_list(self, request): - xml_list = await get_xml_recipes() - return web.json_response(xml_list) - - @request_mapping(path='/xml', method="POST", auth_required=False) - async def create_xml_recipe(self, request): - xml_id = await request.json() - await self.xml_recipe_creation(xml_id['id']) - return web.Response(status=200) - - @request_mapping(path="/getpath", auth_required=False) - async def http_getpath(self, request): - - """ - - --- - description: get path for recipe creation - tags: - - Upload - responses: - "200": - description: successful operation - """ - return web.json_response(data=self.get_creation_path()) - async def kbh_recipe_creation(self, Recipe_ID): self.kettle = None @@ -339,8 +301,6 @@ class RecipeUpload(CBPiExtension): finally: if conn: conn.close() - - return alerts async def xml_recipe_creation(self, Recipe_ID): self.kettle = None @@ -575,16 +535,3 @@ class RecipeUpload(CBPiExtension): return await response.text() await self.push_update() - - -def setup(cbpi): - - ''' - This method is called by the server during startup - Here you need to register your plugins at the server - - :param cbpi: the cbpi core - :return: - ''' - - cbpi.plugin.register("RecipeUpload", RecipeUpload) diff --git a/cbpi/craftbeerpi.py b/cbpi/craftbeerpi.py index c23ffa8..a5a44aa 100644 --- a/cbpi/craftbeerpi.py +++ b/cbpi/craftbeerpi.py @@ -25,6 +25,8 @@ from cbpi.controller.plugin_controller import PluginController from cbpi.controller.sensor_controller import SensorController from cbpi.controller.step_controller import StepController from cbpi.controller.recipe_controller import RecipeController +from cbpi.controller.upload_controller import UploadController + from cbpi.controller.system_controller import SystemController from cbpi.controller.satellite_controller import SatelliteController @@ -107,6 +109,7 @@ class CraftBeerPi: self.kettle = KettleController(self) self.step : StepController = StepController(self) self.recipe : RecipeController = RecipeController(self) + self.upload : UploadController = UploadController(self) self.notification : NotificationController = NotificationController(self) self.satellite = None if self.static_config.get("mqtt", False) is True: diff --git a/cbpi/extension/ReciepeUpload/__init__.py b/cbpi/extension/ReciepeUpload/__init__.py deleted file mode 100644 index bc5487b..0000000 --- a/cbpi/extension/ReciepeUpload/__init__.py +++ /dev/null @@ -1,83 +0,0 @@ - -# -*- coding: utf-8 -*- -import os -import pathlib -import aiohttp -from aiohttp import web -import logging -from unittest.mock import MagicMock, patch -import asyncio -from cbpi.api import * -from voluptuous.schema_builder import message -from cbpi.api.dataclasses import NotificationAction, NotificationType -from cbpi.controller.kettle_controller import KettleController -from cbpi.api.base import CBPiBase -from cbpi.api.config import ConfigType -import json -import webbrowser - -logger = logging.getLogger(__name__) - -class RecipeUpload(CBPiExtension): - def __init__(self, cbpi): - self.cbpi = cbpi - self.cbpi.register(self, "/upload") - - def allowed_file(self, filename, extension): - return '.' in filename and filename.rsplit('.', 1)[1] in set([extension]) - - @request_mapping(path='/', method="POST", auth_required=False) - async def RecipeUpload(self, request): - data = await request.post() - fileData = data['File'] - logging.info(fileData) - - if fileData.content_type == 'text/xml': - logging.info(fileData.content_type) - try: - filename = fileData.filename - beerxml_file = fileData.file - content = beerxml_file.read().decode() - if beerxml_file and self.allowed_file(filename, 'xml'): - self.path = os.path.join(".", 'config', "upload", "beer.xml") - - f = open(self.path, "w") - f.write(content) - f.close() - self.cbpi.notify("Success", "XML Recipe {} has been uploaded".format(filename), NotificationType.SUCCESS) - except: - self.cbpi.notify("Error" "XML Recipe upload failed", NotificationType.ERROR) - pass - - elif fileData.content_type == 'application/octet-stream': - try: - filename = fileData.filename - logger.info(filename) - kbh_file = fileData.file - content = kbh_file.read() - if kbh_file and self.allowed_file(filename, 'sqlite'): - self.path = os.path.join(".", 'config', "upload", "kbh.db") - - f=open(self.path, "wb") - f.write(content) - f.close() - self.cbpi.notify("Success", "Kleiner Brauhelfer database has been uploaded", NotificationType.SUCCESS) - except: - self.cbpi.notify("Error", "Kleiner Brauhelfer database upload failed", NotificationType.ERROR) - pass - else: - self.cbpi.notify("Error", "Wrong content type. Upload failed", NotificationType.ERROR) - - return web.Response(status=200) - -def setup(cbpi): - - ''' - This method is called by the server during startup - Here you need to register your plugins at the server - - :param cbpi: the cbpi core - :return: - ''' - - cbpi.plugin.register("RecipeUpload", RecipeUpload) diff --git a/cbpi/extension/ReciepeUpload/config.yaml b/cbpi/extension/ReciepeUpload/config.yaml deleted file mode 100644 index 805817f..0000000 --- a/cbpi/extension/ReciepeUpload/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -name: RecipeUpload -version: 4 -active: true diff --git a/cbpi/extension/RecipeUpload/config.yaml b/cbpi/extension/RecipeUpload/config.yaml deleted file mode 100644 index 805817f..0000000 --- a/cbpi/extension/RecipeUpload/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -name: RecipeUpload -version: 4 -active: true diff --git a/cbpi/http_endpoints/http_upload.py b/cbpi/http_endpoints/http_upload.py index 07a94ae..baa3713 100644 --- a/cbpi/http_endpoints/http_upload.py +++ b/cbpi/http_endpoints/http_upload.py @@ -1,45 +1,24 @@ -#from cbpi.controller.recipe_controller import RecipeController +from cbpi.controller.upload_controller import UploadController from cbpi.api.dataclasses import Props, Step from aiohttp import web from cbpi.api import * +import logging class UploadHttpEndpoints(): def __init__(self, cbpi): self.cbpi = cbpi -# self.controller : RecipeController = cbpi.recipe - self.cbpi.register(self, "/fileupload") + self.controller : UploadController = cbpi.upload + self.cbpi.register(self, "/upload") @request_mapping(path='/', method="POST", auth_required=False) - async def RecipeUpload(self, request): - """ - - --- - description: Upload XML file or database from KBH V2 - tags: - - FileUpload - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - orderId: - type: integer - userId: - type: integer - fileName: - type: string - format: binary - responses: - "200": - description: successful operation - """ - + async def FileUpload(self, request): data = await request.post() - fileData = data['File'] - logging.info(fileData) + await self.controller.FileUpload(data) + return web.Response(status=200) + + @request_mapping(path='/kbh', method="GET", auth_required=False) @@ -49,13 +28,13 @@ class UploadHttpEndpoints(): --- description: Get Recipe list from Kleiner Brauhelfer tags: - - FileUpload + - Upload responses: "200": description: successful operation """ - kbh_list = await get_kbh_recipes() + kbh_list = await self.controller.get_kbh_recipes() return web.json_response(kbh_list) @request_mapping(path='/kbh', method="POST", auth_required=False) @@ -65,14 +44,20 @@ class UploadHttpEndpoints(): --- description: Create Recipe from KBH database with selected ID tags: - - FileUpload + - Upload + parameters: + - name: "id" + in: "body" + description: "Recipe ID: {'id': ID}" + required: true + type: "string" responses: "200": description: successful operation """ kbh_id = await request.json() - await self.kbh_recipe_creation(kbh_id['id']) + await self.controller.kbh_recipe_creation(kbh_id['id']) return web.Response(status=200) @request_mapping(path='/xml', method="GET", auth_required=False) @@ -82,13 +67,14 @@ class UploadHttpEndpoints(): --- description: Get recipe list from xml file tags: - - FileUpload + - Upload responses: "200": description: successful operation """ - xml_list = await get_xml_recipes() + xml_list = await self.controller.get_xml_recipes() + return web.json_response(xml_list) @request_mapping(path='/xml', method="POST", auth_required=False) @@ -98,14 +84,21 @@ class UploadHttpEndpoints(): --- description: Create recipe from xml file with selected id tags: - - FileUpload + - Upload + parameters: + - name: "id" + in: "body" + description: "Recipe ID: {'id': ID}" + required: true + type: "string" + responses: "200": description: successful operation """ xml_id = await request.json() - await self.xml_recipe_creation(xml_id['id']) + await self.controller.xml_recipe_creation(xml_id['id']) return web.Response(status=200) @request_mapping(path="/getpath", auth_required=False) @@ -116,10 +109,10 @@ class UploadHttpEndpoints(): --- description: get path for recipe creation tags: - - FileUpload + - Upload responses: "200": description: successful operation """ - return web.json_response(data=self.get_creation_path()) + return web.json_response(data=self.controller.get_creation_path())