better file uploading support

dev
NtsFranz 2022-09-11 01:04:03 -04:00
parent fc5bb08a0d
commit 815707b8ad
3 changed files with 82 additions and 28 deletions

View File

@ -2,8 +2,10 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking; using UnityEngine.Networking;
@ -14,7 +16,8 @@ namespace VELConnect
public class VELConnectManager : MonoBehaviour public class VELConnectManager : MonoBehaviour
{ {
public string velConnectUrl = "http://localhost"; public string velConnectUrl = "http://localhost";
private static VELConnectManager instance; public static string VelConnectUrl => _instance.velConnectUrl;
private static VELConnectManager _instance;
public class State public class State
{ {
@ -27,6 +30,7 @@ namespace VELConnect
public string last_modified; public string last_modified;
public Dictionary<string, string> data; public Dictionary<string, string> data;
} }
public class Device public class Device
{ {
public string hw_id; public string hw_id;
@ -136,8 +140,8 @@ namespace VELConnect
private void Awake() private void Awake()
{ {
if (instance != null) Debug.LogError("VELConnectManager instance already exists", this); if (_instance != null) Debug.LogError("VELConnectManager instance already exists", this);
instance = this; _instance = this;
} }
// Start is called before the first frame update // Start is called before the first frame update
@ -382,10 +386,10 @@ namespace VELConnect
if (sendInitialState) if (sendInitialState)
{ {
if (instance != null && instance.lastState?.device != null) if (_instance != null && _instance.lastState?.device != null)
{ {
if (instance.lastState.device.GetType().GetField(key) if (_instance.lastState.device.GetType().GetField(key)
?.GetValue(instance.lastState.device) is string val) ?.GetValue(_instance.lastState.device) is string val)
{ {
try try
{ {
@ -472,12 +476,12 @@ namespace VELConnect
public static string GetDeviceData(string key) public static string GetDeviceData(string key)
{ {
return instance != null ? instance.lastState?.device?.TryGetData(key) : null; return _instance != null ? _instance.lastState?.device?.TryGetData(key) : null;
} }
public static string GetRoomData(string key) public static string GetRoomData(string key)
{ {
return instance != null ? instance.lastState?.room?.TryGetData(key) : null; return _instance != null ? _instance.lastState?.room?.TryGetData(key) : null;
} }
@ -486,8 +490,8 @@ namespace VELConnect
/// </summary> /// </summary>
public static void SetDeviceField(Dictionary<string, object> device) public static void SetDeviceField(Dictionary<string, object> device)
{ {
instance.PostRequestCallback( PostRequestCallback(
instance.velConnectUrl + "/api/device/set_data/" + DeviceId, _instance.velConnectUrl + "/api/device/set_data/" + DeviceId,
JsonConvert.SerializeObject(device), JsonConvert.SerializeObject(device),
new Dictionary<string, string> { { "modified_by", DeviceId } } new Dictionary<string, string> { { "modified_by", DeviceId } }
); );
@ -498,8 +502,8 @@ namespace VELConnect
/// </summary> /// </summary>
public static void SetDeviceData(Dictionary<string, string> data) public static void SetDeviceData(Dictionary<string, string> data)
{ {
instance.PostRequestCallback( PostRequestCallback(
instance.velConnectUrl + "/api/device/set_data/" + DeviceId, _instance.velConnectUrl + "/api/device/set_data/" + DeviceId,
JsonConvert.SerializeObject(new Dictionary<string, object> { { "data", data } }), JsonConvert.SerializeObject(new Dictionary<string, object> { { "data", data } }),
new Dictionary<string, string> { { "modified_by", DeviceId } } new Dictionary<string, string> { { "modified_by", DeviceId } }
); );
@ -513,18 +517,34 @@ namespace VELConnect
return; return;
} }
instance.PostRequestCallback( PostRequestCallback(
instance.velConnectUrl + "/api/set_data/" + Application.productName + "_" + VelNetManager.Room, _instance.velConnectUrl + "/api/set_data/" + Application.productName + "_" + VelNetManager.Room,
JsonConvert.SerializeObject(data), JsonConvert.SerializeObject(data),
new Dictionary<string, string> { { "modified_by", DeviceId } } new Dictionary<string, string> { { "modified_by", DeviceId } }
); );
} }
public static void UploadFile(string fileName, byte[] fileData, Action<string> successCallback = null)
{
MultipartFormDataContent requestContent = new MultipartFormDataContent();
ByteArrayContent fileContent = new ByteArrayContent(fileData);
public void GetRequestCallback(string url, Action<string> successCallback = null, requestContent.Add(fileContent, "file", fileName);
Task.Run(async () =>
{
HttpResponseMessage r = await new HttpClient().PostAsync(_instance.velConnectUrl + "/api/upload_file", requestContent);
string resp = await r.Content.ReadAsStringAsync();
Dictionary<string, string> dict = JsonConvert.DeserializeObject<Dictionary<string,string>>(resp);
successCallback?.Invoke(dict["key"]);
});
}
public static void GetRequestCallback(string url, Action<string> successCallback = null,
Action<string> failureCallback = null) Action<string> failureCallback = null)
{ {
StartCoroutine(GetRequestCallbackCo(url, successCallback, failureCallback)); _instance.StartCoroutine(_instance.GetRequestCallbackCo(url, successCallback, failureCallback));
} }
private IEnumerator GetRequestCallbackCo(string url, Action<string> successCallback = null, private IEnumerator GetRequestCallbackCo(string url, Action<string> successCallback = null,
@ -548,13 +568,14 @@ namespace VELConnect
} }
} }
public void PostRequestCallback(string url, string postData, Dictionary<string, string> headers = null, public static void PostRequestCallback(string url, string postData, Dictionary<string, string> headers = null,
Action<string> successCallback = null, Action<string> successCallback = null,
Action<string> failureCallback = null) Action<string> failureCallback = null)
{ {
StartCoroutine(PostRequestCallbackCo(url, postData, headers, successCallback, failureCallback)); _instance.StartCoroutine(PostRequestCallbackCo(url, postData, headers, successCallback, failureCallback));
} }
private static IEnumerator PostRequestCallbackCo(string url, string postData, private static IEnumerator PostRequestCallbackCo(string url, string postData,
Dictionary<string, string> headers = null, Action<string> successCallback = null, Dictionary<string, string> headers = null, Action<string> successCallback = null,
Action<string> failureCallback = null) Action<string> failureCallback = null)

View File

@ -72,7 +72,7 @@ CREATE TABLE `DataBlock` (
`id` TEXT NOT NULL, `id` TEXT NOT NULL,
-- id of the owner of this file. Ownership is not transferable because ids may collide, -- id of the owner of this file. Ownership is not transferable because ids may collide,
-- but the owner could be null for global scope -- but the owner could be null for global scope
`owner_id` TEXT, `owner_id` TEXT NOT NULL DEFAULT 'none',
`visibility` TEXT CHECK( `visibility` IN ('public','private','unlisted') ) NOT NULL DEFAULT 'public', `visibility` TEXT CHECK( `visibility` IN ('public','private','unlisted') ) NOT NULL DEFAULT 'public',
-- This is an indexable field to filter out different types of datablocks -- This is an indexable field to filter out different types of datablocks
`category` TEXT, `category` TEXT,

View File

@ -1,3 +1,4 @@
import os
import secrets import secrets
import json import json
import string import string
@ -129,7 +130,7 @@ def create_device(hw_id: str):
@router.get('/device/get_data/{hw_id}') @router.get('/device/get_data/{hw_id}')
def get_state(request: Request, response: Response, hw_id: str): def get_device_data(request: Request, response: Response, hw_id: str):
"""Gets the device state""" """Gets the device state"""
devices = db.query(""" devices = db.query("""
@ -155,7 +156,7 @@ def get_state(request: Request, response: Response, hw_id: str):
@router.post('/device/set_data/{hw_id}') @router.post('/device/set_data/{hw_id}')
def set_state(request: fastapi.Request, hw_id: str, data: dict, modified_by: str = None): def set_device_data(request: fastapi.Request, hw_id: str, data: dict, modified_by: str = None):
"""Sets the device state""" """Sets the device state"""
create_device(hw_id) create_device(hw_id)
@ -343,21 +344,53 @@ def get_user_dict(user_id: str) -> dict | None:
return None return None
@router.post("/upload_file")
async def upload_file_with_random_key(request: fastapi.Request, file: UploadFile, modified_by: str = None):
return await upload_file(request, file, None, modified_by)
@router.post("/upload_file/{key}") @router.post("/upload_file/{key}")
async def upload_file(request: fastapi.Request, file: UploadFile, key: str, modified_by: str = None): async def upload_file(request: fastapi.Request, file: UploadFile, key: str | None, modified_by: str = None):
if not os.path.exists('data'):
os.makedirs('data')
# generates a key if none was supplied
if key is None:
key = generate_id()
# regenerate if necessary
while len(db.query("SELECT id FROM `DataBlock` WHERE id=:id;", {"id": key})) > 0:
key = generate_id()
async with aiofiles.open('data/' + key, 'wb') as out_file: async with aiofiles.open('data/' + key, 'wb') as out_file:
content = await file.read() # async read content = await file.read() # async read
await out_file.write(content) # async write await out_file.write(content) # async write
# add a datablock to link to the file # add a datablock to link to the file
set_data(request, {'filename': file.filename}, key, 'file', modified_by) set_data(request, data={'filename': file.filename}, key=key, category='file', modified_by=modified_by)
return {"filename": file.filename} return {"filename": file.filename, 'key': key}
@router.get("/download_file/{key}") @router.get("/download_file/{key}")
async def download_file(key: str): async def download_file(response: Response, key: str):
# get the relevant datablock # get the relevant datablock
data = get_data(key) data = get_data(response, key)
print(data) print(data)
if response.status_code == status.HTTP_404_NOT_FOUND:
return 'Not found'
if data['category'] != 'file': if data['category'] != 'file':
return 'Not a file', 500 response.status_code = status.HTTP_400_BAD_REQUEST
return fastapi.FileResponse(data['data']['filename']) return 'Not a file'
return FileResponse(path='data/' + key, filename=data['data']['filename'])
@router.get("/get_all_files")
async def get_all_files():
data = db.query("""
SELECT *
FROM `DataBlock`
WHERE visibility='public' AND category='file';
""")
data = [dict(f) for f in data]
for f in data:
parse_data(f)
return data