#include "GwUpdate.h" #include static String jsonError(String e){ e.replace('"',' '); return "{\"status\":\""+e+"\"}"; } class UpdateParam{ public: String error; size_t uplodaded=0; bool otherUpdate=false; bool hasError(){ return ! error.isEmpty(); } }; bool GwUpdate::delayedRestart(){ return xTaskCreate([](void *p){ GwLog *logRef=(GwLog *)p; logRef->logDebug(GwLog::LOG,"delayed reset started"); delay(800); ESP.restart(); vTaskDelete(NULL); },"reset",2000,logger,0,NULL) == pdPASS; } GwUpdate::GwUpdate(GwLog *log, GwWebServer *webserver, PasswordChecker ckr) { this->checker=ckr; this->logger = log; this->server = webserver->getServer(); server->on("/api/update", HTTP_POST, [&](AsyncWebServerRequest *request) { // the request handler is triggered after the upload has finished... // create the response, add header, and send response String result="{\"status\":\"OK\"}"; bool updateOk=true; if ( request->_tempObject == NULL){ //no data LOG_DEBUG(GwLog::ERROR,"no data in update request"); result=jsonError("no data uploaded"); updateOk=false; } else{ UpdateParam *param=(UpdateParam *)(request->_tempObject); updateOk=!param->hasError(); LOG_DEBUG(GwLog::LOG,"update finished status=%s %s (%d)", (updateOk?"ok":"error"), param->error.c_str(), param->uplodaded ); if (!updateOk){ result=jsonError(String("Update failed: ")+param->error); } if (! param->otherUpdate) { updateRunning=0; } } AsyncWebServerResponse *response = request->beginResponse(200, "application/json",result); response->addHeader("Connection", "close"); request->send(response); if (updateOk) delayedRestart(); }, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { //Upload handler chunks in data LOG_DEBUG(GwLog::DEBUG,"update chunk f=%s,idx=%d,len=%d,final=%d", filename.c_str(),(int)index,(int)len,(int)final ); UpdateParam *param=(UpdateParam *)request->_tempObject; if (!index) { if (param == NULL) { param=new UpdateParam(); request->_tempObject=param; } if (updateRunning > 0 && (updateRunning + 60000) >= millis()){ param->error="another update is running"; param->otherUpdate=true; } else{ updateRunning=millis(); } if (!param->hasError()) { AsyncWebParameter *hash=request->getParam("_hash"); if (! hash){ hash=request->getParam("_hash",true); } if (!hash) { LOG_DEBUG(GwLog::ERROR, "missing _hash in update"); param->error = "missing _hash"; } else { String shash=hash->value(); LOG_DEBUG(GwLog::DEBUG,"checking hash %s",shash.c_str()); if (!checker(shash)) { LOG_DEBUG(GwLog::ERROR, "invalid _hash in update %s",shash.c_str()); param->error = "invalid password"; } } } if (! param->hasError()){ Update.abort(); //abort any dangling update int cmd=U_FLASH; if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { // Start with max available size LOG_DEBUG(GwLog::ERROR,"unable to start update %s",Update.errorString()); param->error=Update.errorString(); } } } if (param && !param->hasError()) { // Write chunked data to the free sketch space if (len) { size_t wr = Update.write(data, len); if (wr != len) { LOG_DEBUG(GwLog::ERROR, "invalid write, expected %d got %d", (int)len, (int)wr); param->error="unable to write"; Update.abort(); } else{ param->uplodaded+=wr; } } if (final && ! param->hasError()) { // if the final flag is set then this is the last frame of data LOG_DEBUG(GwLog::DEBUG,"finalizing update"); if (!Update.end(true)) { //true to set the size to the current progress LOG_DEBUG(GwLog::ERROR, "unable to end update %s", Update.errorString()); param->error=String("unable to end update:") + String(Update.errorString()); Update.abort(); } } else { return; } } }); }