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

View file

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

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