#include "http_server.h" #include "main.h" #include "spi_master.h" #include HTTPServer::HTTPServer(Player* p, Controller* c) { _player = p; _controller = c; _server = new AsyncWebServer(80); ws = new AsyncWebSocket("/ws"); _server->addHandler(ws); ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);}); _server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "text/html", (const char*)file_index_html_start); }); _server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) { req->send(200); }, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { this->_handle_upload(request, filename, index, data, len, final); }); _server->on("/_mapping.txt", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "text/plain", _controller->pm->create_mapping_txt()); }); _server->on("/player.json", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "application/json", _controller->player->json()); }); _server->on("/playlist_manager.json", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "application/json", _controller->pm->json()); }); _server->on("/controller.json", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "application/json", _controller->json()); }); _server->on("/position.json", HTTP_GET, [&](AsyncWebServerRequest* req) { req->send(200, "application/json", _controller->player->position_json()); }); _server->on("/cmd", HTTP_POST, [&](AsyncWebServerRequest *req) { if (req->hasParam("cmd", true)) { _controller->queue_command(req->getParam("cmd", true)->value()); req->send(200); } else { req->send(400); } }); _server->begin(); MDNS.addService("http", "tcp", 80); } void HTTPServer::_handle_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { // https://www.gnu.org/software/tar/manual/html_node/Standard.html // https://www.mkssoftware.com/docs/man4/tar.4.asp if (index == 0) { // Starting upload _chunk = new uint8_t[512]; _chunk_length = 0; _upload_position = 0; _file_size = 0; _file_size_done = 0; _need_header = true; } uint32_t upload_offset = 0; while (upload_offset < len) { // Load a chunk if (_chunk_length < 512 && len > upload_offset) { uint16_t needed = 512 - _chunk_length; if (needed > len - upload_offset) needed = len - upload_offset; memcpy(_chunk + _chunk_length, data + upload_offset, needed); _chunk_length += needed; upload_offset += needed; _upload_position += needed; if (_chunk_length == 512) { // Process chunk DEBUG("."); if (_need_header) { if (_chunk[257]=='u'&&_chunk[258]=='s'&&_chunk[259]=='t'&&_chunk[260]=='a'&&_chunk[261]=='r') { DEBUG("It is a valid header, starting at 0x%X!\n", _upload_position-512); char filename[200]; strncpy(filename, (char*)_chunk, 100); DEBUG("filename: %s\n", filename); _file_size = 0; _file_size_done = 0; for (int i=0; i<11; i++) { //Serial.print(_header_buffer[124 + i]); _file_size = (_file_size<<3) + (_chunk[124 + i] - '0'); } DEBUG("filesize: %d\n", _file_size); uint8_t type = _chunk[156] - '0'; if (type==0) { String path = "/"; path += filename; DEBUG("Opening file %s\n", path.c_str()); uint8_t state = SPIMaster::state; SPIMaster::disable(); SPIMaster::select_sd(); // Better safe than sorry. ;-) _upload_file.close(); _upload_file = SD.open(path, "w"); SPIMaster::set_state(state); } else if (type==5) { String dirname = "/"; dirname += filename; dirname.remove(dirname.length()-1); uint8_t state = SPIMaster::state; SPIMaster::disable(); SPIMaster::select_sd(); bool res = SD.mkdir(dirname); SPIMaster::set_state(state); DEBUG("Creating folder '%s' returned %d.\n", dirname.c_str(), res); } else { ERROR("Unknown file type %d\n", type); } _need_header = (type==5 || _file_size==0); // No chunks needed for directories. } else { bool byte_found = false; for (int i=0; i<512; i++) byte_found = byte_found || _chunk[i]>0; if (!byte_found) { DEBUG("Empty chunk while looking for header -> ignoring.\n"); } else { ERROR("Invalid tar header: %c %c %c %c %c. Looking at header start offset 0x%X.\n", _chunk[257], _chunk[258], _chunk[259], _chunk[260], _chunk[261], _upload_position-512); } } } else { uint32_t bytes_to_write = _file_size - _file_size_done; if (bytes_to_write > 512) bytes_to_write=512; uint8_t state = SPIMaster::state; SPIMaster::disable(); SPIMaster::select_sd(); _upload_file.write(_chunk, bytes_to_write); _file_size_done += bytes_to_write; if (_file_size_done >= _file_size) { _upload_file.close(); _need_header = true; } SPIMaster::set_state(state); } _chunk_length = 0; } } } if (final == true) { uint8_t state = SPIMaster::state; SPIMaster::disable(); SPIMaster::select_sd(); _upload_file.close(); SPIMaster::set_state(state); delete _chunk; _controller->update_playlist_manager(); return; } } void HTTPServer::_onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { if (type==WS_EVT_CONNECT) { _controller->inform_new_client(client); } else if (type==WS_EVT_DATA) { AwsFrameInfo* info = (AwsFrameInfo*) arg; if (info->final && info->index==0 && info->len==len && info->opcode==WS_TEXT) { data[len]='\0'; DEBUG("Received ws message: %s\n", (char*)data); _controller->queue_command((char*)data); } } }