mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-25 08:28:23 +01:00
Added upload controller
Integrated recipe file upload into cbpi - Controller and http endpoint added --> No extension required
This commit is contained in:
parent
b3d87bc027
commit
2aa5feceb4
6 changed files with 92 additions and 238 deletions
|
@ -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,12 +11,26 @@ 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
|
||||||
|
|
||||||
|
class UploadController:
|
||||||
|
|
||||||
|
def __init__(self, cbpi):
|
||||||
|
self.cbpi = cbpi
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
async def get_kbh_recipes(self):
|
||||||
try:
|
try:
|
||||||
path = os.path.join(".", 'config', "upload", "kbh.db")
|
path = os.path.join(".", 'config', "upload", "kbh.db")
|
||||||
conn = sqlite3.connect(path)
|
conn = sqlite3.connect(path)
|
||||||
|
@ -35,7 +45,7 @@ async def get_kbh_recipes():
|
||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_xml_recipes():
|
async def get_xml_recipes(self):
|
||||||
try:
|
try:
|
||||||
path = os.path.join(".", 'config', "upload", "beer.xml")
|
path = os.path.join(".", 'config', "upload", "beer.xml")
|
||||||
e = xml.etree.ElementTree.parse(path).getroot()
|
e = xml.etree.ElementTree.parse(path).getroot()
|
||||||
|
@ -49,101 +59,53 @@ async def get_xml_recipes():
|
||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
if fileData.content_type == 'text/xml':
|
|
||||||
logging.info(fileData.content_type)
|
|
||||||
try:
|
|
||||||
filename = fileData.filename
|
filename = fileData.filename
|
||||||
beerxml_file = fileData.file
|
recipe_file = fileData.file
|
||||||
content = beerxml_file.read().decode()
|
content_type = fileData.content_type
|
||||||
if beerxml_file and self.allowed_file(filename, 'xml'):
|
|
||||||
|
if content_type == 'text/xml':
|
||||||
|
try:
|
||||||
|
beer_xml = recipe_file.read().decode()
|
||||||
|
if recipe_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
|
||||||
|
|
||||||
|
@ -340,8 +302,6 @@ class RecipeUpload(CBPiExtension):
|
||||||
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)
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
|
|
@ -1,3 +0,0 @@
|
||||||
name: RecipeUpload
|
|
||||||
version: 4
|
|
||||||
active: true
|
|
|
@ -1,3 +0,0 @@
|
||||||
name: RecipeUpload
|
|
||||||
version: 4
|
|
||||||
active: true
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue