Added upload controller

Integrated recipe file upload into cbpi
- Controller and http endpoint added
--> No extension required
This commit is contained in:
avollkopf 2021-06-06 13:00:55 +02:00
parent b3d87bc027
commit 2aa5feceb4
6 changed files with 92 additions and 238 deletions

View file

@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
import os import os
import pathlib import pathlib
import aiohttp import aiohttp
from aiohttp import web from aiohttp import web
import logging
from unittest.mock import MagicMock, patch
import asyncio import asyncio
from cbpi.api import * from cbpi.api import *
import xml.etree.ElementTree import xml.etree.ElementTree
@ -15,135 +11,101 @@ from cbpi.api.dataclasses import NotificationAction, NotificationType
from cbpi.controller.kettle_controller import KettleController from cbpi.controller.kettle_controller import KettleController
from cbpi.api.base import CBPiBase from cbpi.api.base import CBPiBase
from cbpi.api.config import ConfigType from cbpi.api.config import ConfigType
import json
import webbrowser 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(): import re
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 []
async def get_xml_recipes(): class UploadController:
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 RecipeUpload(CBPiExtension):
def __init__(self, cbpi): def __init__(self, cbpi):
self.cbpi = 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): async def get_xml_recipes(self):
return '.' in filename and filename.rsplit('.', 1)[1] in set([extension]) 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): def get_creation_path(self):
creation_path = self.cbpi.config.get("RECIPE_CREATION_PATH", "upload") creation_path = self.cbpi.config.get("RECIPE_CREATION_PATH", "upload")
path = {'path': 'upload'} if creation_path == '' else {'path': creation_path} path = {'path': 'upload'} if creation_path == '' else {'path': creation_path}
return 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): async def FileUpload(self, data):
data = await request.post()
fileData = data['File'] fileData = data['File']
logging.info(fileData) logging.info(fileData)
filename = fileData.filename
recipe_file = fileData.file
content_type = fileData.content_type
if fileData.content_type == 'text/xml': if content_type == 'text/xml':
logging.info(fileData.content_type)
try: try:
filename = fileData.filename beer_xml = recipe_file.read().decode()
beerxml_file = fileData.file if recipe_file and self.allowed_file(filename, 'xml'):
content = beerxml_file.read().decode()
if beerxml_file and self.allowed_file(filename, 'xml'):
self.path = os.path.join(".", 'config', "upload", "beer.xml") self.path = os.path.join(".", 'config', "upload", "beer.xml")
f = open(self.path, "w") f = open(self.path, "w")
f.write(content) f.write(beer_xml)
f.close() 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: except:
self.cbpi.notify("Error" "XML Recipe upload failed", NotificationType.ERROR) self.cbpi.notify("Error" "XML Recipe upload failed", NotificationType.ERROR)
pass pass
elif fileData.content_type == 'application/octet-stream': elif content_type == 'application/octet-stream':
try: try:
filename = fileData.filename content = recipe_file.read()
logger.info(filename) if recipe_file and self.allowed_file(filename, 'sqlite'):
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") self.path = os.path.join(".", 'config', "upload", "kbh.db")
f=open(self.path, "wb") f=open(self.path, "wb")
f.write(content) f.write(content)
f.close() 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: except:
self.cbpi.notify("Error", "Kleiner Brauhelfer database upload failed", NotificationType.ERROR) self.cbpi.notify("Error", "Kleiner Brauhelfer database upload failed", NotificationType.ERROR)
pass pass
else: else:
self.cbpi.notify("Error", "Wrong content type. Upload failed", NotificationType.ERROR) 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): async def kbh_recipe_creation(self, Recipe_ID):
self.kettle = None self.kettle = None
@ -339,8 +301,6 @@ class RecipeUpload(CBPiExtension):
finally: finally:
if conn: if conn:
conn.close() conn.close()
return alerts
async def xml_recipe_creation(self, Recipe_ID): async def xml_recipe_creation(self, Recipe_ID):
self.kettle = None self.kettle = None
@ -575,16 +535,3 @@ class RecipeUpload(CBPiExtension):
return await response.text() return await response.text()
await self.push_update() 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)

View file

@ -25,6 +25,8 @@ from cbpi.controller.plugin_controller import PluginController
from cbpi.controller.sensor_controller import SensorController from cbpi.controller.sensor_controller import SensorController
from cbpi.controller.step_controller import StepController from cbpi.controller.step_controller import StepController
from cbpi.controller.recipe_controller import RecipeController from cbpi.controller.recipe_controller import RecipeController
from cbpi.controller.upload_controller import UploadController
from cbpi.controller.system_controller import SystemController from cbpi.controller.system_controller import SystemController
from cbpi.controller.satellite_controller import SatelliteController from cbpi.controller.satellite_controller import SatelliteController
@ -107,6 +109,7 @@ class CraftBeerPi:
self.kettle = KettleController(self) self.kettle = KettleController(self)
self.step : StepController = StepController(self) self.step : StepController = StepController(self)
self.recipe : RecipeController = RecipeController(self) self.recipe : RecipeController = RecipeController(self)
self.upload : UploadController = UploadController(self)
self.notification : NotificationController = NotificationController(self) self.notification : NotificationController = NotificationController(self)
self.satellite = None self.satellite = None
if self.static_config.get("mqtt", False) is True: if self.static_config.get("mqtt", False) is True:

View file

@ -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)

View file

@ -1,3 +0,0 @@
name: RecipeUpload
version: 4
active: true

View file

@ -1,3 +0,0 @@
name: RecipeUpload
version: 4
active: true

View file

@ -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 cbpi.api.dataclasses import Props, Step
from aiohttp import web from aiohttp import web
from cbpi.api import * from cbpi.api import *
import logging
class UploadHttpEndpoints(): class UploadHttpEndpoints():
def __init__(self, cbpi): def __init__(self, cbpi):
self.cbpi = cbpi self.cbpi = cbpi
# self.controller : RecipeController = cbpi.recipe self.controller : UploadController = cbpi.upload
self.cbpi.register(self, "/fileupload") self.cbpi.register(self, "/upload")
@request_mapping(path='/', method="POST", auth_required=False) @request_mapping(path='/', method="POST", auth_required=False)
async def RecipeUpload(self, request): async def FileUpload(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
"""
data = await request.post() data = await request.post()
fileData = data['File'] await self.controller.FileUpload(data)
logging.info(fileData) return web.Response(status=200)
@request_mapping(path='/kbh', method="GET", auth_required=False) @request_mapping(path='/kbh', method="GET", auth_required=False)
@ -49,13 +28,13 @@ class UploadHttpEndpoints():
--- ---
description: Get Recipe list from Kleiner Brauhelfer description: Get Recipe list from Kleiner Brauhelfer
tags: tags:
- FileUpload - Upload
responses: responses:
"200": "200":
description: successful operation description: successful operation
""" """
kbh_list = await get_kbh_recipes() kbh_list = await self.controller.get_kbh_recipes()
return web.json_response(kbh_list) return web.json_response(kbh_list)
@request_mapping(path='/kbh', method="POST", auth_required=False) @request_mapping(path='/kbh', method="POST", auth_required=False)
@ -65,14 +44,20 @@ class UploadHttpEndpoints():
--- ---
description: Create Recipe from KBH database with selected ID description: Create Recipe from KBH database with selected ID
tags: tags:
- FileUpload - Upload
parameters:
- name: "id"
in: "body"
description: "Recipe ID: {'id': ID}"
required: true
type: "string"
responses: responses:
"200": "200":
description: successful operation description: successful operation
""" """
kbh_id = await request.json() 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) return web.Response(status=200)
@request_mapping(path='/xml', method="GET", auth_required=False) @request_mapping(path='/xml', method="GET", auth_required=False)
@ -82,13 +67,14 @@ class UploadHttpEndpoints():
--- ---
description: Get recipe list from xml file description: Get recipe list from xml file
tags: tags:
- FileUpload - Upload
responses: responses:
"200": "200":
description: successful operation description: successful operation
""" """
xml_list = await get_xml_recipes() xml_list = await self.controller.get_xml_recipes()
return web.json_response(xml_list) return web.json_response(xml_list)
@request_mapping(path='/xml', method="POST", auth_required=False) @request_mapping(path='/xml', method="POST", auth_required=False)
@ -98,14 +84,21 @@ class UploadHttpEndpoints():
--- ---
description: Create recipe from xml file with selected id description: Create recipe from xml file with selected id
tags: tags:
- FileUpload - Upload
parameters:
- name: "id"
in: "body"
description: "Recipe ID: {'id': ID}"
required: true
type: "string"
responses: responses:
"200": "200":
description: successful operation description: successful operation
""" """
xml_id = await request.json() 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) return web.Response(status=200)
@request_mapping(path="/getpath", auth_required=False) @request_mapping(path="/getpath", auth_required=False)
@ -116,10 +109,10 @@ class UploadHttpEndpoints():
--- ---
description: get path for recipe creation description: get path for recipe creation
tags: tags:
- FileUpload - Upload
responses: responses:
"200": "200":
description: successful operation description: successful operation
""" """
return web.json_response(data=self.get_creation_path()) return web.json_response(data=self.controller.get_creation_path())