From acc7253ca129ee8310f4381ca4e428a4adc70624 Mon Sep 17 00:00:00 2001 From: Jurij Retzlaff Date: Sun, 8 Nov 2020 11:23:22 +0100 Subject: [PATCH 1/2] logfile rotating implemented --- .../lib/jomjol_fileserver_ota/server_file.cpp | 85 ++++++++----- .../jomjol_flowcontroll/ClassFlowControll.cpp | 4 + .../jomjol_flowcontroll/ClassFlowMakeImage.h | 2 - code/lib/jomjol_helper/Helper.cpp | 67 ++++++++++ code/lib/jomjol_helper/Helper.h | 4 + code/lib/jomjol_logfile/ClassLogFile.cpp | 117 +++++++++++++++--- code/lib/jomjol_logfile/ClassLogFile.h | 6 +- code/src/server_main.h | 2 - code/src/server_tflite.cpp | 2 + code/src/version.cpp | 4 +- code/version.cpp | 4 +- sd-card/config/config.ini | 2 + sd-card/html/index.html | 2 +- 13 files changed, 241 insertions(+), 60 deletions(-) diff --git a/code/lib/jomjol_fileserver_ota/server_file.cpp b/code/lib/jomjol_fileserver_ota/server_file.cpp index 633542bc..78f45a74 100644 --- a/code/lib/jomjol_fileserver_ota/server_file.cpp +++ b/code/lib/jomjol_fileserver_ota/server_file.cpp @@ -84,7 +84,7 @@ static esp_err_t favicon_get_handler(httpd_req_t *req) * a list of all files and folders under the requested path. * In case of SPIFFS this returns empty list when path is any * string other than '/', since SPIFFS doesn't support directories */ -static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) +static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const char* uripath, bool readonly) { char entrypath[FILE_PATH_MAX]; char entrysize[16]; @@ -120,24 +120,24 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) httpd_resp_sendstr_chunk(req, ""); ///////////////////////////////////////////////// - - FILE *fd = fopen("/sdcard/html/upload_script.html", "r"); - char *chunk = ((struct file_server_data *)req->user_ctx)->scratch; - size_t chunksize; - do { - chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); -// printf("Chunksize %d\n", chunksize); - if (chunksize > 0){ - if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { - fclose(fd); - ESP_LOGE(TAG, "File sending failed!"); - return ESP_FAIL; + if (!readonly) { + FILE *fd = fopen("/sdcard/html/upload_script.html", "r"); + char *chunk = ((struct file_server_data *)req->user_ctx)->scratch; + size_t chunksize; + do { + chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); + // printf("Chunksize %d\n", chunksize); + if (chunksize > 0){ + if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { + fclose(fd); + ESP_LOGE(TAG, "File sending failed!"); + return ESP_FAIL; + } } - } - } while (chunksize != 0); - fclose(fd); -// ESP_LOGI(TAG, "File sending complete"); - + } while (chunksize != 0); + fclose(fd); + // ESP_LOGI(TAG, "File sending complete"); + } /////////////////////////////// std::string _zw = std::string(dirpath); @@ -149,12 +149,16 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) httpd_resp_sendstr_chunk(req, "" "" - "\n"); + ""); + if (!readonly) { + httpd_resp_sendstr_chunk(req, ""); + } + httpd_resp_sendstr_chunk(req, "\n"); /* Iterate over all files / folders and fetch their names and sizes */ while ((entry = readdir(dir)) != NULL) { @@ -173,7 +177,8 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) /* Send chunk of HTML file containing table entries with file name and size */ httpd_resp_sendstr_chunk(req, "\n"); } } @@ -226,6 +233,7 @@ static esp_err_t download_get_handler(httpd_req_t *req) // filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path, // req->uri, sizeof(filepath)); + if (!filename) { ESP_LOGE(TAG, "Filename is too long"); /* Respond with 500 Internal Server Error */ @@ -235,7 +243,22 @@ static esp_err_t download_get_handler(httpd_req_t *req) /* If name has trailing '/', respond with directory contents */ if (filename[strlen(filename) - 1] == '/') { - return http_resp_dir_html(req, filepath); + bool readonly = false; + size_t buf_len = httpd_req_get_url_query_len(req) + 1; + if (buf_len > 1) { + char buf[buf_len]; + if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { + ESP_LOGI(TAG, "Found URL query => %s", buf); + char param[32]; + /* Get value of expected key from query string */ + if (httpd_query_key_value(buf, "readonly", param, sizeof(param)) == ESP_OK) { + ESP_LOGI(TAG, "Found URL query parameter => readonly=%s", param); + readonly = param && strcmp(param,"true")==0; + } + } + } + + return http_resp_dir_html(req, filepath, filename, readonly); } std::string testwlan = toUpper(std::string(filename)); diff --git a/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp b/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp index 50ea08c7..68f3cdec 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp @@ -280,6 +280,10 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph) { LogFile.SwitchOnOff(false); } + } + if ((toUpper(zerlegt[0]) == "LOGFILERETENTIONINDAYS") && (zerlegt.size() > 1)) + { + LogFile.SetRetention(std::stoi(zerlegt[1])); } } return true; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h index f19eef94..0bae9f0f 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h +++ b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h @@ -4,8 +4,6 @@ #include -static const char* TAG2 = "example"; - #define BLINK_GPIO GPIO_NUM_4 #define CAMERA_MODEL_AI_THINKER diff --git a/code/lib/jomjol_helper/Helper.cpp b/code/lib/jomjol_helper/Helper.cpp index 4e7797ec..4263dd63 100644 --- a/code/lib/jomjol_helper/Helper.cpp +++ b/code/lib/jomjol_helper/Helper.cpp @@ -1,8 +1,12 @@ //#pragma warning(disable : 4996) #include "Helper.h" +#include +#include +#include //#define ISWINDOWS_TRUE +#define PATH_MAX_STRING_SIZE 256 using namespace std; @@ -159,6 +163,63 @@ string getFileType(string filename) return zw; } +/* recursive mkdir */ +int mkdir_r(const char *dir, const mode_t mode) { + char tmp[PATH_MAX_STRING_SIZE]; + char *p = NULL; + struct stat sb; + size_t len; + + /* copy path */ + len = strnlen (dir, PATH_MAX_STRING_SIZE); + if (len == 0 || len == PATH_MAX_STRING_SIZE) { + return -1; + } + memcpy (tmp, dir, len); + tmp[len] = '\0'; + + /* remove trailing slash */ + if(tmp[len - 1] == '/') { + tmp[len - 1] = '\0'; + } + + /* check if path exists and is a directory */ + if (stat (tmp, &sb) == 0) { + if (S_ISDIR (sb.st_mode)) { + return 0; + } + } + + /* recursive mkdir */ + for(p = tmp + 1; *p; p++) { + if(*p == '/') { + *p = 0; + /* test path */ + if (stat(tmp, &sb) != 0) { + /* path does not exist - create directory */ + if (mkdir(tmp, mode) < 0) { + return -1; + } + } else if (!S_ISDIR(sb.st_mode)) { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + /* test path */ + if (stat(tmp, &sb) != 0) { + /* path does not exist - create directory */ + if (mkdir(tmp, mode) < 0) { + return -1; + } + } else if (!S_ISDIR(sb.st_mode)) { + /* not a directory */ + return -1; + } + return 0; +} + string toUpper(string in) { for (int i = 0; i < in.length(); ++i) @@ -173,3 +234,9 @@ float temperatureRead() { return (temprature_sens_read() - 32) / 1.8; } + +time_t addDays(time_t startTime, int days) { + struct tm* tm = localtime(&startTime); + tm->tm_mday += days; + return mktime(tm); +} \ No newline at end of file diff --git a/code/lib/jomjol_helper/Helper.h b/code/lib/jomjol_helper/Helper.h index ce2f4e32..55453a3c 100644 --- a/code/lib/jomjol_helper/Helper.h +++ b/code/lib/jomjol_helper/Helper.h @@ -17,6 +17,10 @@ bool ctype_space(const char c, string adddelimiter); string getFileType(string filename); +int mkdir_r(const char *dir, const mode_t mode); + string toUpper(string in); float temperatureRead(); + +time_t addDays(time_t startTime, int days); diff --git a/code/lib/jomjol_logfile/ClassLogFile.cpp b/code/lib/jomjol_logfile/ClassLogFile.cpp index 11a168a6..1f6b1df0 100644 --- a/code/lib/jomjol_logfile/ClassLogFile.cpp +++ b/code/lib/jomjol_logfile/ClassLogFile.cpp @@ -1,7 +1,14 @@ #include "ClassLogFile.h" #include "time_sntp.h" +#include +#include +#include +#include +#include "Helper.h" -ClassLogFile LogFile("/sdcard/log.txt"); +static const char *TAG = "log"; + +ClassLogFile LogFile("/sdcard/log/message", "log_%Y-%m-%d.txt"); void ClassLogFile::WriteToDedicatedFile(std::string _fn, std::string info, bool _time) { @@ -13,39 +20,111 @@ void ClassLogFile::WriteToDedicatedFile(std::string _fn, std::string info, bool } pFile = fopen(_fn.c_str(), "a+"); + if (pFile!=NULL) { + if (_time) + { + time_t rawtime; + struct tm* timeinfo; + char buffer[80]; - if (_time) - { - time_t rawtime; - struct tm* timeinfo; - char buffer[80]; + time(&rawtime); + timeinfo = localtime(&rawtime); - time(&rawtime); - timeinfo = localtime(&rawtime); + strftime(buffer, 80, "%Y-%m-%d_%H-%M-%S", timeinfo); - strftime(buffer, 80, "%Y-%m-%d_%H-%M-%S", timeinfo); + zwtime = std::string(buffer); + info = zwtime + ": " + info; + } + fputs(info.c_str(), pFile); + fputs("\n", pFile); - zwtime = std::string(buffer); - info = zwtime + ": " + info; + fclose(pFile); + } else { + ESP_LOGI(TAG, "Can't open log file %s", _fn.c_str()); } - fputs(info.c_str(), pFile); - fputs("\n", pFile); - - fclose(pFile); } void ClassLogFile::SwitchOnOff(bool _doLogFile){ doLogFile = _doLogFile; }; +void ClassLogFile::SetRetention(unsigned short _retentionInDays){ + retentionInDays = _retentionInDays; +}; void ClassLogFile::WriteToFile(std::string info, bool _time) { - WriteToDedicatedFile(logfile, info, _time); + struct stat path_stat; + if (stat(logroot.c_str(), &path_stat) != 0) { + ESP_LOGI(TAG, "Create log folder: %s", logroot.c_str()); + if (mkdir_r(logroot.c_str(), S_IRWXU) == -1) { + ESP_LOGI(TAG, "Can't create log foolder"); + } + } + + time_t rawtime; + struct tm* timeinfo; + char buffer[30]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + + strftime(buffer, 30, logfile.c_str(), timeinfo); + std::string logpath = logroot + "/" + buffer; + + WriteToDedicatedFile(logpath, info, _time); } -ClassLogFile::ClassLogFile(std::string _logfile) +void ClassLogFile::RemoveOld() { - logfile = _logfile; + if (retentionInDays == 0) { + return; + } + + time_t rawtime; + struct tm* timeinfo; + char cmpfilename[30]; + + time(&rawtime); + rawtime = addDays(rawtime, -retentionInDays); + timeinfo = localtime(&rawtime); + + strftime(cmpfilename, 30, logfile.c_str(), timeinfo); + //ESP_LOGE(TAG, "log file name to compare: %s", cmpfilename); + + DIR *dir = opendir(logroot.c_str()); + if (!dir) { + ESP_LOGE(TAG, "Failed to stat dir : %s", logroot.c_str()); + return; + } + + struct dirent *entry; + int deleted = 0; + int notDeleted = 0; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_REG) { + //ESP_LOGE(TAG, "list log file : %s", entry->d_name); + if ((strlen(entry->d_name) == strlen(cmpfilename)) && (strcmp(entry->d_name, cmpfilename) < 0)) { + ESP_LOGE(TAG, "delete log file : %s", entry->d_name); + std::string filepath = logroot + "/" + entry->d_name; + if (unlink(filepath.c_str()) == 0) { + deleted ++; + } else { + ESP_LOGE(TAG, "can't delete file : %s", entry->d_name); + } + } else { + notDeleted ++; + } + } + } + ESP_LOGE(TAG, "%d older log files deleted. %d current log files not deleted.", deleted, notDeleted); + closedir(dir); +} + +ClassLogFile::ClassLogFile(std::string _logroot, std::string _logfile) +{ + logroot = _logroot; + logfile = _logfile; doLogFile = true; -} \ No newline at end of file + retentionInDays = 10; +} diff --git a/code/lib/jomjol_logfile/ClassLogFile.h b/code/lib/jomjol_logfile/ClassLogFile.h index fcb37ea1..5915c329 100644 --- a/code/lib/jomjol_logfile/ClassLogFile.h +++ b/code/lib/jomjol_logfile/ClassLogFile.h @@ -5,15 +5,19 @@ class ClassLogFile { private: + std::string logroot; std::string logfile; bool doLogFile; + unsigned short retentionInDays; public: - ClassLogFile(std::string _logfile); + ClassLogFile(std::string _logpath, std::string _logfile); void SwitchOnOff(bool _doLogFile); + void SetRetention(unsigned short _retentionInDays); void WriteToFile(std::string info, bool _time = true); void WriteToDedicatedFile(std::string _fn, std::string info, bool _time = true); + void RemoveOld(); }; extern ClassLogFile LogFile; \ No newline at end of file diff --git a/code/src/server_main.h b/code/src/server_main.h index 83471970..079aff8d 100644 --- a/code/src/server_main.h +++ b/code/src/server_main.h @@ -14,8 +14,6 @@ #include -static const char *TAG = "example"; - extern httpd_handle_t server; httpd_handle_t start_webserver(void); diff --git a/code/src/server_tflite.cpp b/code/src/server_tflite.cpp index f5e2d5e0..5241a9f8 100644 --- a/code/src/server_tflite.cpp +++ b/code/src/server_tflite.cpp @@ -433,6 +433,8 @@ void task_autodoFlow(void *pvParameter) printf("Autoflow: doFLow wird gestartet\n"); flowisrunning = true; doflow(); + printf("Remove older log files\n"); + LogFile.RemoveOld(); } LogFile.WriteToFile("task_autodoFlow - round done"); diff --git a/code/src/version.cpp b/code/src/version.cpp index d1393fa2..7f9646e5 100644 --- a/code/src/version.cpp +++ b/code/src/version.cpp @@ -1,4 +1,4 @@ -const char* GIT_REV="2ab2f07"; +const char* GIT_REV="05a0f6f"; const char* GIT_TAG=""; const char* GIT_BRANCH="master"; -const char* BUILD_TIME="2020-11-03 22:12"; \ No newline at end of file +const char* BUILD_TIME="2020-11-06 17:57"; \ No newline at end of file diff --git a/code/version.cpp b/code/version.cpp index d1393fa2..7f9646e5 100644 --- a/code/version.cpp +++ b/code/version.cpp @@ -1,4 +1,4 @@ -const char* GIT_REV="2ab2f07"; +const char* GIT_REV="05a0f6f"; const char* GIT_TAG=""; const char* GIT_BRANCH="master"; -const char* BUILD_TIME="2020-11-03 22:12"; \ No newline at end of file +const char* BUILD_TIME="2020-11-06 17:57"; \ No newline at end of file diff --git a/sd-card/config/config.ini b/sd-card/config/config.ini index 64219ec0..0d513c11 100644 --- a/sd-card/config/config.ini +++ b/sd-card/config/config.ini @@ -51,5 +51,7 @@ Intervall = 4.85 [Debug] Logfile = False +; Number of days before a log file is deleted. 0 = disabled. 10 is default value (if not defined) +;LogfileRetentionInDays = 10 [Ende] \ No newline at end of file diff --git a/sd-card/html/index.html b/sd-card/html/index.html index 36e2fa78..78e55a9d 100644 --- a/sd-card/html/index.html +++ b/sd-card/html/index.html @@ -91,7 +91,7 @@ li.dropdown { System From f4f871002b812dedcfed6f29bee85264feb43996 Mon Sep 17 00:00:00 2001 From: Jurij Retzlaff Date: Thu, 12 Nov 2020 23:27:00 +0100 Subject: [PATCH 2/2] rotation of image log files implemented improved rotation of message log files implement deletion of temp images before start new flow a little bit code cleaning --- code/lib/jomjol_flowcontroll/ClassFlow.h | 4 + .../jomjol_flowcontroll/ClassFlowAnalog.cpp | 37 +++---- .../lib/jomjol_flowcontroll/ClassFlowAnalog.h | 6 +- .../jomjol_flowcontroll/ClassFlowControll.cpp | 35 ++++++ .../jomjol_flowcontroll/ClassFlowControll.h | 2 + .../jomjol_flowcontroll/ClassFlowDigit.cpp | 29 +++-- code/lib/jomjol_flowcontroll/ClassFlowDigit.h | 6 +- .../jomjol_flowcontroll/ClassFlowImage.cpp | 101 ++++++++++++++++++ code/lib/jomjol_flowcontroll/ClassFlowImage.h | 22 ++++ .../lib/jomjol_flowcontroll/ClassFlowMQTT.cpp | 2 - .../ClassFlowMakeImage.cpp | 68 ++---------- .../jomjol_flowcontroll/ClassFlowMakeImage.h | 6 +- code/lib/jomjol_helper/Helper.cpp | 37 ++++++- code/lib/jomjol_helper/Helper.h | 1 + code/lib/jomjol_logfile/ClassLogFile.cpp | 8 +- code/lib/jomjol_time_sntp/time_sntp.cpp | 2 +- code/src/server_main.h | 2 + code/src/server_tflite.cpp | 3 +- code/src/version.cpp | 4 +- code/version.cpp | 4 +- sd-card/config/config.ini | 3 + 21 files changed, 259 insertions(+), 123 deletions(-) create mode 100644 code/lib/jomjol_flowcontroll/ClassFlowImage.cpp create mode 100644 code/lib/jomjol_flowcontroll/ClassFlowImage.h diff --git a/code/lib/jomjol_flowcontroll/ClassFlow.h b/code/lib/jomjol_flowcontroll/ClassFlow.h index 35928d91..18260229 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlow.h +++ b/code/lib/jomjol_flowcontroll/ClassFlow.h @@ -9,6 +9,10 @@ using namespace std; +#define LOGFILE_TIME_FORMAT "%Y%m%d-%H%M%S" +#define LOGFILE_TIME_FORMAT_DATE_EXTR substr(0, 8) +#define LOGFILE_TIME_FORMAT_HOUR_EXTR substr(9, 2) + struct HTMLInfo { float val; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp b/code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp index fa6085e7..d0dea860 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include // #define OHNETFLITE @@ -12,25 +12,23 @@ #include "ClassLogFile.h" +static const char* TAG = "flow_analog"; + bool debugdetailanalog = false; -ClassFlowAnalog::ClassFlowAnalog() +ClassFlowAnalog::ClassFlowAnalog() : ClassFlowImage(TAG) { - isLogImage = false; string cnnmodelfile = ""; modelxsize = 1; modelysize = 1; ListFlowControll = NULL; } -ClassFlowAnalog::ClassFlowAnalog(std::vector* lfc) +ClassFlowAnalog::ClassFlowAnalog(std::vector* lfc) : ClassFlowImage(lfc, TAG) { - isLogImage = false; string cnnmodelfile = ""; modelxsize = 1; modelysize = 1; - ListFlowControll = NULL; - ListFlowControll = lfc; } @@ -90,8 +88,12 @@ bool ClassFlowAnalog::ReadParameter(FILE* pfile, string& aktparamgraph) zerlegt = this->ZerlegeZeile(aktparamgraph); if ((zerlegt[0] == "LogImageLocation") && (zerlegt.size() > 1)) { + this->LogImageLocation = "/sdcard" + zerlegt[1]; this->isLogImage = true; - this->LogImageLocation = zerlegt[1]; + } + if ((toUpper(zerlegt[0]) == "LOGFILERETENTIONINDAYS") && (zerlegt.size() > 1)) + { + this->logfileRetentionInDays = std::stoi(zerlegt[1]); } if ((zerlegt[0] == "Model") && (zerlegt.size() > 1)) { @@ -153,6 +155,8 @@ bool ClassFlowAnalog::doFlow(string time) doNeuralNetwork(time); + RemoveOldLogs(); + return true; } @@ -233,10 +237,11 @@ bool ClassFlowAnalog::doAlignAndCut(string time) bool ClassFlowAnalog::doNeuralNetwork(string time) { + string logPath = CreateLogFolder(time); + string input = "/sdcard/img_tmp/alg.jpg"; string ioresize = "/sdcard/img_tmp/resize.bmp"; string output; - string nm; input = FormatFileName(input); #ifndef OHNETFLITE @@ -275,19 +280,7 @@ bool ClassFlowAnalog::doNeuralNetwork(string time) printf("Result Analog%i: %f\n", i, ROI[i]->result); - if (isLogImage) - { - std::stringstream stream; - stream << std::fixed << std::setprecision(1) << ROI[i]->result; - std::string s = stream.str(); -// std::snprintf(&s[0], s.size(), "%.2f", pi); - nm = "/sdcard" + LogImageLocation + "/" + s + "_" + ROI[i]->name + "_" + time + ".jpg"; - nm = FormatFileName(nm); - output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg"; - output = FormatFileName(output); - printf("Analog - save to file: %s\n", nm.c_str()); - CopyFile(output, nm); - } + LogImage(logPath, ROI[i]->name, &ROI[i]->result, NULL, time); } #ifndef OHNETFLITE delete tflite; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowAnalog.h b/code/lib/jomjol_flowcontroll/ClassFlowAnalog.h index ce41f17f..89f2f6e2 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowAnalog.h +++ b/code/lib/jomjol_flowcontroll/ClassFlowAnalog.h @@ -1,5 +1,5 @@ #pragma once -#include "ClassFlow.h" +#include "ClassFlowImage.h" // #include "CTfLiteClass.h" struct roianalog { @@ -10,11 +10,9 @@ struct roianalog { class ClassFlowAnalog : - public ClassFlow + public ClassFlowImage { protected: - string LogImageLocation; - bool isLogImage; std::vector ROI; string cnnmodelfile; int modelxsize, modelysize; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp b/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp index 68f3cdec..3307f5eb 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowControll.cpp @@ -1,10 +1,14 @@ #include "ClassFlowControll.h" +#include +#include #include "ClassLogFile.h" #include "time_sntp.h" #include "Helper.h" #include "server_ota.h" +static const char* TAG = "flow_controll"; + std::string ClassFlowControll::doSingleStep(std::string _stepname, std::string _host){ std::string _classname = ""; std::string result = ""; @@ -149,6 +153,8 @@ std::string ClassFlowControll::getActStatus(){ bool ClassFlowControll::doFlow(string time) { + CleanTempFolder(); + bool result = true; std::string zw_time; int repeat = 0; @@ -289,3 +295,32 @@ bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph) return true; } +int ClassFlowControll::CleanTempFolder() { + const char* folderPath = "/sdcard/img_tmp"; + + ESP_LOGI(TAG, "Clean up temporary folder to avoid damage of sdcard sectors : %s", folderPath); + DIR *dir = opendir(folderPath); + if (!dir) { + ESP_LOGE(TAG, "Failed to stat dir : %s", folderPath); + return -1; + } + + struct dirent *entry; + int deleted = 0; + while ((entry = readdir(dir)) != NULL) { + std::string path = string(folderPath) + "/" + entry->d_name; + if (entry->d_type == DT_REG) { + if (unlink(path.c_str()) == 0) { + deleted ++; + } else { + ESP_LOGE(TAG, "can't delete file : %s", path.c_str()); + } + } else if (entry->d_type == DT_DIR) { + deleted += removeFolder(path.c_str(), TAG); + } + } + closedir(dir); + ESP_LOGI(TAG, "%d files deleted", deleted); + + return 0; +} \ No newline at end of file diff --git a/code/lib/jomjol_flowcontroll/ClassFlowControll.h b/code/lib/jomjol_flowcontroll/ClassFlowControll.h index d3d9de9a..91eac5fa 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowControll.h +++ b/code/lib/jomjol_flowcontroll/ClassFlowControll.h @@ -42,6 +42,8 @@ public: std::vector GetAllDigital(); std::vector GetAllAnalog(); + int CleanTempFolder(); + string name(){return "ClassFlowControll";}; }; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp b/code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp index 70f1132b..90f02254 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp @@ -13,23 +13,21 @@ #include "ClassLogFile.h" -ClassFlowDigit::ClassFlowDigit() +static const char* TAG = "flow_digital"; + +ClassFlowDigit::ClassFlowDigit() : ClassFlowImage(TAG) { - isLogImage = false; string cnnmodelfile = ""; modelxsize = 1; modelysize = 1; ListFlowControll = NULL; } -ClassFlowDigit::ClassFlowDigit(std::vector* lfc) +ClassFlowDigit::ClassFlowDigit(std::vector* lfc) : ClassFlowImage(lfc, TAG) { - isLogImage = false; string cnnmodelfile = ""; modelxsize = 1; modelysize = 1; - ListFlowControll = NULL; - ListFlowControll = lfc; } string ClassFlowDigit::getReadout() @@ -66,8 +64,8 @@ bool ClassFlowDigit::ReadParameter(FILE* pfile, string& aktparamgraph) zerlegt = this->ZerlegeZeile(aktparamgraph); if ((zerlegt[0] == "LogImageLocation") && (zerlegt.size() > 1)) { - isLogImage = true; - LogImageLocation = zerlegt[1]; + LogImageLocation = "/sdcard" + zerlegt[1]; + isLogImage = true; } if ((zerlegt[0] == "Model") && (zerlegt.size() > 1)) { @@ -128,6 +126,8 @@ bool ClassFlowDigit::doFlow(string time) doNeuralNetwork(time); + RemoveOldLogs(); + return true; } @@ -194,6 +194,8 @@ bool ClassFlowDigit::doAlignAndCut(string time) bool ClassFlowDigit::doNeuralNetwork(string time) { + string logPath = CreateLogFolder(time); + string input = "/sdcard/img_tmp/alg.jpg"; string ioresize = "/sdcard/img_tmp/resize.bmp"; string output; @@ -221,16 +223,9 @@ bool ClassFlowDigit::doNeuralNetwork(string time) #ifndef OHNETFLITE ROI[i]->resultklasse = tflite->GetClassFromImage(ioresize); #endif - printf("Result Digit%i: %d\n", i, ROI[i]->resultklasse); + printf("Result Digit%i: %d\n", i, ROI[i]->resultklasse); - if (isLogImage) - { - nm = "/sdcard" + LogImageLocation + "/" + std::to_string(ROI[i]->resultklasse) + "/" + time + "_" + ROI[i]->name + ".jpg"; - output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg"; - output = FormatFileName(output); - nm = FormatFileName(nm); - CopyFile(output, nm); - } + LogImage(logPath, ROI[i]->name, NULL, &ROI[i]->resultklasse, time); } #ifndef OHNETFLITE delete tflite; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowDigit.h b/code/lib/jomjol_flowcontroll/ClassFlowDigit.h index b84c7f37..3546bb36 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowDigit.h +++ b/code/lib/jomjol_flowcontroll/ClassFlowDigit.h @@ -1,5 +1,5 @@ #pragma once -#include "ClassFlow.h" +#include "ClassFlowImage.h" #include "Helper.h" #include @@ -12,11 +12,9 @@ struct roi { }; class ClassFlowDigit : - public ClassFlow + public ClassFlowImage { protected: - string LogImageLocation; - bool isLogImage; std::vector ROI; string cnnmodelfile; int modelxsize, modelysize; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowImage.cpp b/code/lib/jomjol_flowcontroll/ClassFlowImage.cpp new file mode 100644 index 00000000..5c09160e --- /dev/null +++ b/code/lib/jomjol_flowcontroll/ClassFlowImage.cpp @@ -0,0 +1,101 @@ +#include "ClassFlowImage.h" +#include +#include +#include +#include +#include "time_sntp.h" +#include "ClassLogFile.h" + +ClassFlowImage::ClassFlowImage(const char* logTag) +{ + this->logTag = logTag; + isLogImage = false; +} + +ClassFlowImage::ClassFlowImage(std::vector * lfc, const char* logTag) : ClassFlow((std::vector*)lfc) +{ + this->logTag = logTag; + isLogImage = false; +} + +string ClassFlowImage::CreateLogFolder(string time) { + if (!isLogImage) + return ""; + + string logPath = LogImageLocation + "/" + time.LOGFILE_TIME_FORMAT_DATE_EXTR + "/" + time.LOGFILE_TIME_FORMAT_HOUR_EXTR; + isLogImage = mkdir_r(logPath.c_str(), S_IRWXU) == 0; + if (!isLogImage) { + ESP_LOGW(logTag, "Can't create log foolder for analog images. Path %s", logPath.c_str()); + LogFile.WriteToFile("Can't create log foolder for analog images. Path " + logPath); + } + + return logPath; +} + +void ClassFlowImage::LogImage(string logPath, string name, float *resultFloat, int *resultInt, string time) { + if (!isLogImage) + return; + + char buf[10]; + if (resultFloat != NULL) { + sprintf(buf, "%.1f_", *resultFloat); + } else if (resultInt != NULL) { + sprintf(buf, "%d_", *resultInt); + } else { + buf[0] = '\0'; + } + + string nm = logPath + "/" + buf + name + "_" + time + ".jpg"; + nm = FormatFileName(nm); + string output = "/sdcard/img_tmp/" + name + ".jpg"; + output = FormatFileName(output); + printf("save to file: %s\n", nm.c_str()); + CopyFile(output, nm); +} + +void ClassFlowImage::RemoveOldLogs() +{ + if (!isLogImage) + return; + + ESP_LOGI(logTag, "remove old log images"); + if (logfileRetentionInDays == 0) { + return; + } + + time_t rawtime; + struct tm* timeinfo; + char cmpfilename[30]; + + time(&rawtime); + rawtime = addDays(rawtime, -logfileRetentionInDays); + timeinfo = localtime(&rawtime); + + strftime(cmpfilename, 30, LOGFILE_TIME_FORMAT, timeinfo); + //ESP_LOGE(TAG, "log file name to compare: %s", cmpfilename); + string folderName = string(cmpfilename).LOGFILE_TIME_FORMAT_DATE_EXTR; + + DIR *dir = opendir(LogImageLocation.c_str()); + if (!dir) { + ESP_LOGI(logTag, "Failed to stat dir : %s", LogImageLocation.c_str()); + return; + } + + struct dirent *entry; + int deleted = 0; + int notDeleted = 0; + while ((entry = readdir(dir)) != NULL) { + string folderPath = LogImageLocation + "/" + entry->d_name; + if (entry->d_type == DT_DIR) { + //ESP_LOGI(logTag, "Compare %s %s", entry->d_name, folderName.c_str()); + if ((strlen(entry->d_name) == folderName.length()) && (strcmp(entry->d_name, folderName.c_str()) < 0)) { + deleted += removeFolder(folderPath.c_str(), logTag); + } else { + notDeleted ++; + } + } + } + ESP_LOGI(logTag, "%d older log files deleted. %d current log files not deleted.", deleted, notDeleted); + closedir(dir); +} + diff --git a/code/lib/jomjol_flowcontroll/ClassFlowImage.h b/code/lib/jomjol_flowcontroll/ClassFlowImage.h new file mode 100644 index 00000000..23154c64 --- /dev/null +++ b/code/lib/jomjol_flowcontroll/ClassFlowImage.h @@ -0,0 +1,22 @@ +#pragma once +#include "ClassFlow.h" + +using namespace std; + +class ClassFlowImage : public ClassFlow +{ +protected: + string LogImageLocation; + bool isLogImage; + unsigned short logfileRetentionInDays; + const char* logTag; + + string CreateLogFolder(string time); + void LogImage(string logPath, string name, float *resultFloat, int *resultInt, string time); + +public: + ClassFlowImage(const char* logTag); + ClassFlowImage(std::vector * lfc, const char* logTag); + + void RemoveOldLogs(); +}; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowMQTT.cpp b/code/lib/jomjol_flowcontroll/ClassFlowMQTT.cpp index c1d66426..92396e2b 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowMQTT.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowMQTT.cpp @@ -6,8 +6,6 @@ #include -static const char* TAG2 = "example"; - ClassFlowMQTT::ClassFlowMQTT() { uri = ""; diff --git a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp index d7f3d201..d1f4d78c 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp +++ b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp @@ -1,12 +1,12 @@ #include "ClassFlowMakeImage.h" #include "Helper.h" - #include "CFindTemplate.h" #include "ClassControllCamera.h" #include +static const char* TAG = "flow_make_image"; esp_err_t ClassFlowMakeImage::camera_capture(){ string nm = namerawimage; @@ -24,11 +24,8 @@ void ClassFlowMakeImage::takePictureWithFlash(int flashdauer) } - - -ClassFlowMakeImage::ClassFlowMakeImage() +ClassFlowMakeImage::ClassFlowMakeImage() : ClassFlowImage(TAG) { - isLogImage = false; waitbeforepicture = 5; isImageSize = false; ImageQuality = -1; @@ -36,16 +33,13 @@ ClassFlowMakeImage::ClassFlowMakeImage() namerawimage = "/sdcard/img_tmp/raw.jpg"; } -ClassFlowMakeImage::ClassFlowMakeImage(std::vector* lfc) +ClassFlowMakeImage::ClassFlowMakeImage(std::vector* lfc) : ClassFlowImage(lfc, TAG) { - isLogImage = false; waitbeforepicture = 5; isImageSize = false; ImageQuality = -1; TimeImageTaken = 0; namerawimage = "/sdcard/img_tmp/raw.jpg"; - - ListFlowControll = lfc; } bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph) @@ -66,8 +60,8 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph) zerlegt = this->ZerlegeZeile(aktparamgraph); if ((zerlegt[0] == "LogImageLocation") && (zerlegt.size() > 1)) { - this->isLogImage = true; - this->LogImageLocation = zerlegt[1]; + LogImageLocation = "/sdcard" + zerlegt[1]; + isLogImage = true; } if ((zerlegt[0] == "ImageQuality") && (zerlegt.size() > 1)) this->ImageQuality = std::stod(zerlegt[1]); @@ -81,45 +75,6 @@ bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph) return true; } - -void ClassFlowMakeImage::CopyFile(string input, string output) -{ - input = FormatFileName(input); - output = FormatFileName(output); - input = namerawimage; - - - printf("Copy Input : %s\n", input.c_str()); - printf("Copy Output: %s\n", output.c_str()); - - char cTemp; - FILE* fpSourceFile = fopen(input.c_str(), "rb"); - FILE* fpTargetFile = fopen(output.c_str(), "wb"); - - if (fpSourceFile == NULL) - { - printf("fpSourceFile == NULL\n"); - perror("Error"); - } - - if (fpTargetFile == NULL) - { - printf("fpTargetFile == NULL\n"); - perror("Error"); - } - - - while (fread(&cTemp, 1, 1, fpSourceFile) == 1) - { - fwrite(&cTemp, 1, 1, fpTargetFile); - } - - // Close The Files - fclose(fpSourceFile); - fclose(fpTargetFile); - printf("Copy done\n"); -} - string ClassFlowMakeImage::getHTMLSingleStep(string host) { string result; @@ -133,6 +88,8 @@ bool ClassFlowMakeImage::doFlow(string zwtime) // TakeImage and Store into /image_tmp/raw.jpg TO BE DONE //////////////////////////////////////////////////////////////////// + string logPath = CreateLogFolder(zwtime); + int flashdauer = (int) waitbeforepicture * 1000; @@ -140,16 +97,9 @@ bool ClassFlowMakeImage::doFlow(string zwtime) time(&TimeImageTaken); localtime(&TimeImageTaken); + LogImage(logPath, "raw", NULL, NULL, zwtime); - if (this->isLogImage) - { - string nm = "/sdcard" + this->LogImageLocation + "/" + zwtime + ".jpg"; - string input = "/sdcard/image_tmp/raw.jgp"; - printf("loginput from: %s to: %s\n", input.c_str(), nm.c_str()); - nm = FormatFileName(nm); - input = FormatFileName(input); - CopyFile(input, nm); - } + RemoveOldLogs(); return true; } diff --git a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h index 0bae9f0f..febb2ab3 100644 --- a/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h +++ b/code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h @@ -1,5 +1,5 @@ #pragma once -#include "ClassFlow.h" +#include "ClassFlowImage.h" #include "ClassControllCamera.h" #include @@ -11,11 +11,9 @@ class ClassFlowMakeImage : - public ClassFlow + public ClassFlowImage { protected: - string LogImageLocation; - bool isLogImage; float waitbeforepicture; framesize_t ImageSize; bool isImageSize; diff --git a/code/lib/jomjol_helper/Helper.cpp b/code/lib/jomjol_helper/Helper.cpp index 4263dd63..5ffd73e0 100644 --- a/code/lib/jomjol_helper/Helper.cpp +++ b/code/lib/jomjol_helper/Helper.cpp @@ -3,7 +3,9 @@ #include "Helper.h" #include #include +#include #include +#include //#define ISWINDOWS_TRUE #define PATH_MAX_STRING_SIZE 256 @@ -239,4 +241,37 @@ time_t addDays(time_t startTime, int days) { struct tm* tm = localtime(&startTime); tm->tm_mday += days; return mktime(tm); -} \ No newline at end of file +} + +int removeFolder(const char* folderPath, const char* logTag) { + ESP_LOGI(logTag, "Delete folder %s", folderPath); + + DIR *dir = opendir(folderPath); + if (!dir) { + ESP_LOGI(logTag, "Failed to stat dir : %s", folderPath); + return -1; + } + + struct dirent *entry; + int deleted = 0; + while ((entry = readdir(dir)) != NULL) { + std::string path = string(folderPath) + "/" + entry->d_name; + if (entry->d_type == DT_REG) { + if (unlink(path.c_str()) == 0) { + deleted ++; + } else { + ESP_LOGE(logTag, "can't delete file : %s", path.c_str()); + } + } else if (entry->d_type == DT_DIR) { + deleted += removeFolder(path.c_str(), logTag); + } + } + + closedir(dir); + if (rmdir(folderPath) != 0) { + ESP_LOGE(logTag, "can't delete file : %s", folderPath); + } + ESP_LOGI(logTag, "%d older log files in folder %s deleted.", deleted, folderPath); + + return deleted; +} diff --git a/code/lib/jomjol_helper/Helper.h b/code/lib/jomjol_helper/Helper.h index 55453a3c..7891263c 100644 --- a/code/lib/jomjol_helper/Helper.h +++ b/code/lib/jomjol_helper/Helper.h @@ -18,6 +18,7 @@ bool ctype_space(const char c, string adddelimiter); string getFileType(string filename); int mkdir_r(const char *dir, const mode_t mode); +int removeFolder(const char* folderPath, const char* logTag); string toUpper(string in); diff --git a/code/lib/jomjol_logfile/ClassLogFile.cpp b/code/lib/jomjol_logfile/ClassLogFile.cpp index 1f6b1df0..ccb75cf0 100644 --- a/code/lib/jomjol_logfile/ClassLogFile.cpp +++ b/code/lib/jomjol_logfile/ClassLogFile.cpp @@ -94,7 +94,7 @@ void ClassLogFile::RemoveOld() DIR *dir = opendir(logroot.c_str()); if (!dir) { - ESP_LOGE(TAG, "Failed to stat dir : %s", logroot.c_str()); + ESP_LOGI(TAG, "Failed to stat dir : %s", logroot.c_str()); return; } @@ -103,9 +103,9 @@ void ClassLogFile::RemoveOld() int notDeleted = 0; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_REG) { - //ESP_LOGE(TAG, "list log file : %s", entry->d_name); + //ESP_LOGI(TAG, "list log file : %s %s", entry->d_name, cmpfilename); if ((strlen(entry->d_name) == strlen(cmpfilename)) && (strcmp(entry->d_name, cmpfilename) < 0)) { - ESP_LOGE(TAG, "delete log file : %s", entry->d_name); + ESP_LOGI(TAG, "delete log file : %s", entry->d_name); std::string filepath = logroot + "/" + entry->d_name; if (unlink(filepath.c_str()) == 0) { deleted ++; @@ -117,7 +117,7 @@ void ClassLogFile::RemoveOld() } } } - ESP_LOGE(TAG, "%d older log files deleted. %d current log files not deleted.", deleted, notDeleted); + ESP_LOGI(TAG, "%d older log files deleted. %d current log files not deleted.", deleted, notDeleted); closedir(dir); } diff --git a/code/lib/jomjol_time_sntp/time_sntp.cpp b/code/lib/jomjol_time_sntp/time_sntp.cpp index 92dd1876..6c8b9047 100644 --- a/code/lib/jomjol_time_sntp/time_sntp.cpp +++ b/code/lib/jomjol_time_sntp/time_sntp.cpp @@ -124,4 +124,4 @@ static void initialize_sntp(void) sntp_setservername(0, "pool.ntp.org"); sntp_set_time_sync_notification_cb(time_sync_notification_cb); sntp_init(); -} \ No newline at end of file +} diff --git a/code/src/server_main.h b/code/src/server_main.h index 079aff8d..16a851ed 100644 --- a/code/src/server_main.h +++ b/code/src/server_main.h @@ -14,6 +14,8 @@ #include +static const char *TAG = "server-main"; + extern httpd_handle_t server; httpd_handle_t start_webserver(void); diff --git a/code/src/server_tflite.cpp b/code/src/server_tflite.cpp index 5241a9f8..b579ac59 100644 --- a/code/src/server_tflite.cpp +++ b/code/src/server_tflite.cpp @@ -58,7 +58,8 @@ void doInit(void) bool doflow(void) { - std::string zw_time = gettimestring("%Y%m%d-%H%M%S"); + + std::string zw_time = gettimestring(LOGFILE_TIME_FORMAT); printf("doflow - start %s\n", zw_time.c_str()); flowisrunning = true; tfliteflow.doFlow(zw_time); diff --git a/code/src/version.cpp b/code/src/version.cpp index 7f9646e5..7131f0fd 100644 --- a/code/src/version.cpp +++ b/code/src/version.cpp @@ -1,4 +1,4 @@ -const char* GIT_REV="05a0f6f"; +const char* GIT_REV="acc7253"; const char* GIT_TAG=""; const char* GIT_BRANCH="master"; -const char* BUILD_TIME="2020-11-06 17:57"; \ No newline at end of file +const char* BUILD_TIME="2020-11-09 00:12"; \ No newline at end of file diff --git a/code/version.cpp b/code/version.cpp index 7f9646e5..7131f0fd 100644 --- a/code/version.cpp +++ b/code/version.cpp @@ -1,4 +1,4 @@ -const char* GIT_REV="05a0f6f"; +const char* GIT_REV="acc7253"; const char* GIT_TAG=""; const char* GIT_BRANCH="master"; -const char* BUILD_TIME="2020-11-06 17:57"; \ No newline at end of file +const char* BUILD_TIME="2020-11-09 00:12"; \ No newline at end of file diff --git a/sd-card/config/config.ini b/sd-card/config/config.ini index 0d513c11..df9ba8a8 100644 --- a/sd-card/config/config.ini +++ b/sd-card/config/config.ini @@ -1,5 +1,6 @@ [MakeImage] ;LogImageLocation = /log/source +;LogfileRetentionInDays = 15 WaitBeforeTakingPicture=5 ImageQuality = 5 ImageSize = VGA @@ -15,6 +16,7 @@ SearchFieldY = 20 [Digits] Model=/config/dig0650s3.tflite LogImageLocation = /log/digit +LogfileRetentionInDays = 15 ModelInputSize 20, 32 digit1, 306, 120, 37, 67 digit2, 355, 120, 37, 67 @@ -23,6 +25,7 @@ digit3, 404, 120, 37, 67 [Analog] Model=/config/ana0630s2.tflite LogImageLocation = /log/analog +LogfileRetentionInDays = 15 ModelInputSize 32, 32 analog1, 444, 225, 92, 92 analog2, 391, 329, 92, 92
NameTypeSize (Bytes)Delete
" - "
" - "
NameTypeSize (Bytes)Delete
" + "
" + "
uri); + httpd_resp_sendstr_chunk(req, "/fileserver"); + httpd_resp_sendstr_chunk(req, uripath); httpd_resp_sendstr_chunk(req, entry->d_name); if (entry->d_type == DT_DIR) { httpd_resp_sendstr_chunk(req, "/"); @@ -184,11 +189,13 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) httpd_resp_sendstr_chunk(req, entrytype); httpd_resp_sendstr_chunk(req, ""); httpd_resp_sendstr_chunk(req, entrysize); - httpd_resp_sendstr_chunk(req, ""); - httpd_resp_sendstr_chunk(req, "
uri + strlen("/fileserver")); - httpd_resp_sendstr_chunk(req, entry->d_name); - httpd_resp_sendstr_chunk(req, "\">
"); + if (!readonly) { + httpd_resp_sendstr_chunk(req, "
"); + httpd_resp_sendstr_chunk(req, "
d_name); + httpd_resp_sendstr_chunk(req, "\">
"); + } httpd_resp_sendstr_chunk(req, "