Skip to content

Commit 0e52215

Browse files
authored
Listing files with valid HTTP header (#326)
* HTTP headers for rootdir() * iterateOnFiles() handles both counting and listing * Counting files is non-blocking (10 files per loop) * Add serial log for rootdir() * Remove extra file * Formatting changes * Update version number
1 parent 077d747 commit 0e52215

10 files changed

+174
-100
lines changed

library.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=TankController
2-
version=22.7.2
2+
version=22.8.1
33
author=Kirt Onthank <[email protected]>, Preston Carman <[email protected]>, James Foster <[email protected]>
44
maintainer=James Foster <[email protected]>
55
sentence=Software for the Arduino that controls pH and temperature in the Open-Acidification project.

src/Devices/EthernetServer_TC.cpp

+41-22
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ void EthernetServer_TC::apiHandler() {
9999
} else if (memcmp_P(buffer + 11, F("display"), 7) == 0) {
100100
display();
101101
} else if (memcmp_P(buffer + 11, F("rootdir"), 7) == 0) {
102-
state = IN_PROGRESS;
103102
rootdir();
104103
} else if (memcmp_P(buffer + 11, F("testRead"), 8) == 0) {
105104
testReadSpeed();
@@ -173,11 +172,32 @@ void writeToClientBufferCallback(char* buffer, bool isFinished) {
173172
EthernetServer_TC::instance()->writeToClientBuffer(buffer, isFinished);
174173
}
175174

175+
// Non-member callback wrapper for singleton
176+
void countFilesCallback(int fileCount) {
177+
// Called when the count is complete
178+
EthernetServer_TC::instance()->sendHeadersForRootdir(fileCount);
179+
}
180+
176181
// List the root directory to the client
177182
void EthernetServer_TC::rootdir() {
178183
// Call function on SD Card
179184
// Provide callback to call when writing to the client buffer
180-
SD_TC::instance()->listRootToBuffer(writeToClientBufferCallback);
185+
if (state != LISTING_FILES) {
186+
state = LISTING_FILES;
187+
isDoneCountingFiles = false;
188+
startTime = millis();
189+
serial(F("Preparing list of files in root directory..."));
190+
} else {
191+
if (isDoneCountingFiles) {
192+
SD_TC::instance()->listRootToBuffer(writeToClientBufferCallback);
193+
} else {
194+
#ifndef MOCK_PINS_COUNT
195+
SD_TC::instance()->countFiles(countFilesCallback);
196+
#else
197+
countFilesCallback(0);
198+
#endif
199+
}
200+
}
181201
}
182202

183203
// Write to the client buffer
@@ -187,10 +207,25 @@ void EthernetServer_TC::writeToClientBuffer(char* buffer, bool isFinished) {
187207
if (isFinished) {
188208
client.write('\r');
189209
client.write('\n');
210+
int endTime = millis();
211+
serial(F("...Done sending, time = %i ms"), endTime - startTime);
190212
state = FINISHED;
191213
}
192214
}
193215

216+
void EthernetServer_TC::sendHeadersForRootdir(int fileCount) {
217+
#ifndef MOCK_PINS_COUNT
218+
isDoneCountingFiles = true;
219+
serial(F("...%i files..."), fileCount);
220+
sendHeadersWithSize((uint32_t)fileCount * 24); // 24 characters per line
221+
state = LISTING_FILES; // TODO: This is here only because sendHeadersWithSize() changes the state prematurely.
222+
#else
223+
isDoneCountingFiles = true;
224+
sendHeadersWithSize((uint32_t)49);
225+
state = LISTING_FILES;
226+
#endif
227+
}
228+
194229
// Tests speed for reading a file from the SD Card
195230
// Empirical results show about 1 ms per 512 B
196231
void EthernetServer_TC::testReadSpeed() {
@@ -255,15 +290,14 @@ void EthernetServer_TC::fileSetup() {
255290
file = SD_TC::instance()->open(buffer + 4);
256291
uint32_t size = file.size();
257292
serial(F("file \"%s\" has a size of %lu"), buffer + 4, size);
258-
sendFileHeadersWithSize(size);
293+
sendHeadersWithSize(size);
259294
state = IN_TRANSFER;
260295
startTime = millis();
261296
fileContinue();
262297
}
263298

264299
// Continue file transfer (return value is whether we are finished)
265300
bool EthernetServer_TC::fileContinue() {
266-
client.write(boundary);
267301
if (file.available()) {
268302
int readSize = file.read(buffer, sizeof(buffer)); // Flawfinder: ignore
269303
int writeSize = client.write(buffer, readSize);
@@ -297,8 +331,8 @@ void EthernetServer_TC::loop() {
297331
state = FINISHED;
298332
}
299333
break;
300-
case IN_PROGRESS:
301-
// In progress (so far only for SD Card)
334+
case LISTING_FILES:
335+
// Listing files from SD Card
302336
rootdir();
303337
break;
304338
case NOT_CONNECTED:
@@ -370,22 +404,7 @@ void EthernetServer_TC::sendHeadersWithSize(uint32_t size) {
370404
// blank line indicates end of headers
371405
client.write('\r');
372406
client.write('\n');
373-
state = FINISHED;
374-
}
375-
376-
void EthernetServer_TC::sendFileHeadersWithSize(uint32_t size) {
377-
snprintf_P(buffer, sizeof(buffer),
378-
(PGM_P)F("HTTP/1.1 200 OK\r\n"
379-
"Content-Type: multipart/form-data; boundary=%s\r\n"
380-
"Access-Control-Allow-Origin: *\r\n"
381-
"Content-Length: %lu\r\n"),
382-
boundary, (unsigned long)size);
383-
client.write(buffer);
384-
385-
// blank line indicates end of headers
386-
client.write('\r');
387-
client.write('\n');
388-
state = FINISHED;
407+
state = FINISHED; // TODO: Why?! This is awkward when we want to send a body next.
389408
}
390409

391410
// 303 response

src/Devices/EthernetServer_TC.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include <Ethernet.h>
1010
#endif
1111

12-
enum serverState_t { NOT_CONNECTED, READ_REQUEST, GET_REQUEST, POST_REQUEST, IN_PROGRESS, IN_TRANSFER, FINISHED };
12+
enum serverState_t { NOT_CONNECTED, READ_REQUEST, GET_REQUEST, POST_REQUEST, LISTING_FILES, IN_TRANSFER, FINISHED };
1313

1414
/**
1515
* EthernetServer_TC provides wrapper for web server for TankController
@@ -29,6 +29,7 @@ class EthernetServer_TC : public EthernetServer {
2929
}
3030
void loop();
3131
void writeToClientBuffer(char*, bool);
32+
void sendHeadersForRootdir(int);
3233

3334
private:
3435
// class variables
@@ -43,12 +44,12 @@ class EthernetServer_TC : public EthernetServer {
4344
unsigned long connectedAt = 0;
4445
File file;
4546
int startTime;
47+
bool isDoneCountingFiles = true; // TODO: Perhaps replace this with a new COUNTING_FILES state
4648

4749
// instance methods: constructor
4850
EthernetServer_TC(uint16_t port);
4951
// instance methods: utility
5052
void sendHeadersWithSize(uint32_t size);
51-
void sendFileHeadersWithSize(uint32_t size);
5253
void sendRedirectHeaders();
5354
void sendBadRequestHeaders();
5455
int weekday(int year, int month, int day);

src/Devices/SD_TC.cpp

+82-65
Original file line numberDiff line numberDiff line change
@@ -84,91 +84,108 @@ bool SD_TC::format() {
8484
return sd.format();
8585
}
8686

87-
void SD_TC::listFiles(void (*callWhenFull)(char*, bool), byte tabulation) {
88-
// Only called on real device
87+
bool SD_TC::iterateOnFiles(doOnFile functionName, void* userData) {
8988
#ifndef MOCK_PINS_COUNT
90-
File* parent = &hierarchy[hierarchySize - 1];
91-
File current;
92-
char fileName[15];
93-
char buffer[300]; // Each line shouldn't be more than 30 characters long
94-
int linePos = 0;
95-
int filesWritten = 0; // When we hit 10 here we will call buffer and suspend
96-
97-
while (filesWritten < 10) {
98-
if (current.openNext(parent, O_READ)) {
99-
if (!current.isHidden()) {
100-
current.getName(fileName, sizeof(fileName));
101-
for (int i = 0; i < tabulation; i++) {
102-
buffer[i] = '\t';
103-
++linePos;
104-
}
105-
if (current.isDir()) {
106-
int bytesWritten = snprintf_P(buffer + linePos, sizeof(buffer) - linePos, (PGM_P)F("%s/\n"), fileName);
107-
// "Overwrite" null terminator
108-
linePos += bytesWritten;
109-
++filesWritten;
110-
// Create a new array with bigger size
111-
File* newHierarchy = new File[hierarchySize + 1];
112-
memcpy(newHierarchy, hierarchy, sizeof(File) * hierarchySize);
113-
delete[] hierarchy;
114-
hierarchy = newHierarchy;
115-
++hierarchySize;
116-
// Add the current file to the new array
117-
hierarchy[hierarchySize - 1] = current;
118-
// Now we change parent directory
119-
parent = &hierarchy[hierarchySize - 1];
89+
// Only called on real device
90+
// Returns false only when all files have been iterated on
91+
bool flag = true;
92+
while (flag) {
93+
if (fileStack[fileStackSize].openNext(&fileStack[fileStackSize - 1], O_READ)) {
94+
if (!fileStack[fileStackSize].isHidden()) {
95+
flag = functionName(&fileStack[fileStackSize], userData);
96+
if (fileStack[fileStackSize].isDir()) {
97+
// maxDepth was set to 2 in SD_TC.h
98+
// So this code is untested
99+
if (fileStackSize < maxDepth - 1) {
100+
++fileStackSize;
101+
};
120102
} else {
121-
int bytesWritten = snprintf_P(buffer + linePos, sizeof(buffer) - linePos, (PGM_P)F("%s\t%6u bytes\n"),
122-
fileName, current.fileSize());
123-
// "Overwrite" null terminator
124-
linePos += bytesWritten;
125-
++filesWritten;
126-
// Close current (if it's a file); close current (if it's a directory) as a parent later
127-
current.close();
103+
// Close current file; directories are closed later
104+
fileStack[fileStackSize].close();
128105
}
129106
}
130107
} else {
131-
if (hierarchySize == 1) {
132-
// We're done listing root
133-
parent->close();
134-
--hierarchySize;
135-
delete[] hierarchy;
136-
hierarchy = nullptr;
137-
buffer[linePos] = '\0';
138-
inProgress = false;
139-
callWhenFull(buffer, true);
140-
return;
108+
// We're done with a directory
109+
fileStack[--fileStackSize].close();
110+
if (fileStackSize == 0) {
111+
return false; // Done with root---there are no more files
141112
}
142-
// All done with parent, remove directory from hierarchy
143-
parent->close();
144-
--hierarchySize;
145-
parent = &hierarchy[hierarchySize - 1];
146113
}
147114
}
148-
// We have 10 lines written, so add null terminator
149-
buffer[linePos] = '\0';
150-
callWhenFull(buffer, false);
115+
return true; // There are (probably) more files remaining
116+
#else
117+
return false;
118+
#endif
119+
}
120+
121+
bool SD_TC::incrementFileCount(File* myFile, void* pFileCount) {
122+
return ++(*(int*)pFileCount) % 10 != 0; // Pause after counting 10 files
123+
}
124+
125+
void SD_TC::countFiles(void (*callWhenFinished)(int)) {
126+
if (!inProgress) {
127+
const char path[] PROGMEM = "/";
128+
fileStack[0] = SD_TC::instance()->open(path);
129+
if (!fileStack[0]) {
130+
serial(F("SD_TC open() failed"));
131+
return;
132+
}
133+
fileStack[0].rewind();
134+
fileStackSize = 1;
135+
fileCount = 0;
136+
inProgress = true;
137+
}
138+
inProgress = iterateOnFiles(incrementFileCount, (void*)&fileCount);
139+
if (!inProgress) {
140+
callWhenFinished(fileCount);
141+
}
142+
}
143+
144+
// Issue: This function does not visually display depth for items in subfolders
145+
// With maxDepth set to 2, no subfolders are traversed
146+
bool SD_TC::listFile(File* myFile, void* userData) {
147+
#ifndef MOCK_PINS_COUNT
148+
listFilesData_t* pListFileData = static_cast<listFilesData_t*>(userData);
149+
char fileName[15];
150+
myFile->getName(fileName, sizeof(fileName));
151+
int bytesWritten;
152+
if (myFile->isDir()) {
153+
bytesWritten = snprintf_P(pListFileData->buffer + pListFileData->linePos,
154+
sizeof(pListFileData->buffer) - pListFileData->linePos,
155+
(PGM_P)F("%11.11s/ \r\n"), fileName);
156+
} else {
157+
bytesWritten = snprintf_P(pListFileData->buffer + pListFileData->linePos,
158+
sizeof(pListFileData->buffer) - pListFileData->linePos, (PGM_P)F("%s\t%6u KB\r\n"),
159+
fileName, myFile->fileSize() / 1024 + 1);
160+
}
161+
// "Overwrite" null terminator
162+
pListFileData->linePos += bytesWritten;
163+
return (++(pListFileData->filesWritten)) % 10 != 0; // Stop iterating after 10 files
164+
#else
165+
return false;
151166
#endif
152167
}
153168

154169
void SD_TC::listRootToBuffer(void (*callWhenFull)(char*, bool)) {
155170
#ifndef MOCK_PINS_COUNT
156171
if (!inProgress) {
157-
// Initialize hierarchy
158-
hierarchy = new File;
159-
++hierarchySize;
160172
const char path[] PROGMEM = "/";
161-
File root = SD_TC::instance()->open(path);
162-
if (!root) {
173+
fileStack[0] = SD_TC::instance()->open(path);
174+
if (!fileStack[0]) {
163175
serial(F("SD_TC open() failed"));
164176
return;
165177
}
166-
// Add root to hierarchy if successful
167-
root.rewind();
168-
hierarchy[0] = root;
178+
fileStack[0].rewind();
179+
fileStackSize = 1;
169180
inProgress = true;
170181
}
171-
listFiles(callWhenFull);
182+
listFilesData_t listFileData;
183+
listFileData.linePos = 0;
184+
listFileData.filesWritten = 0;
185+
inProgress = iterateOnFiles(listFile, (void*)&listFileData);
186+
// Terminate the buffer
187+
listFileData.buffer[listFileData.linePos] = '\0';
188+
callWhenFull(listFileData.buffer, !inProgress);
172189
#else
173190
static const char notImplemented[] PROGMEM = "Root directory not supported by CI framework.\r\n";
174191
char buffer[sizeof(notImplemented)];

src/Devices/SD_TC.h

+20-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22

33
#include <Arduino.h>
44

5+
#define maxDepth 2
56
#define SS 4
67
#include <SdFat.h>
78

9+
typedef bool (*doOnFile)(File*, void*);
10+
11+
struct listFilesData_t {
12+
char buffer[250]; // Each line should be 24 characters long; 10 lines
13+
int linePos;
14+
int filesWritten;
15+
};
16+
817
class SD_TC {
918
public:
1019
// class methods
@@ -16,6 +25,7 @@ class SD_TC {
1625
bool exists(const char* path);
1726
bool format();
1827
void listRootToBuffer(void (*callWhenFull)(char*, bool));
28+
void countFiles(void (*callWhenFinished)(int));
1929
bool mkdir(const char* path);
2030
File open(const char* path, oflag_t oflag = 0x00);
2131
void printRootDirectory();
@@ -30,12 +40,19 @@ class SD_TC {
3040
const uint8_t SD_SELECT_PIN = SS;
3141
bool hasHadError = false;
3242
SdFat sd;
33-
File* hierarchy = nullptr;
34-
int hierarchySize = 0;
43+
44+
// Max depth of file system search for rootdir()
45+
// Two is minimum: First for root, second for files
46+
// Each is 64 bytes
47+
File fileStack[maxDepth];
48+
int fileStackSize;
49+
int fileCount;
3550
bool inProgress = false;
3651

3752
// instance methods
3853
SD_TC();
3954
void appendDataToPath(const char* data, const char* path);
40-
void listFiles(void (*callWhenFull)(char*, bool), byte tabulation = 0);
55+
bool iterateOnFiles(doOnFile functionName, void* userData);
56+
static bool incrementFileCount(File* myFile, void* pFileCount);
57+
static bool listFile(File* myFile, void* userData);
4158
};

src/TankController.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include "UIState/MainMenu.h"
2222
#include "UIState/UIState.h"
2323

24-
const char TANK_CONTROLLER_VERSION[] = "22.07.2";
24+
const char TANK_CONTROLLER_VERSION[] = "22.08.1";
2525

2626
// ------------ Class Methods ------------
2727
/**

0 commit comments

Comments
 (0)