Skip to content

Instantly share code, notes, and snippets.

@me-no-dev
Created March 12, 2017 11:28
Show Gist options
  • Save me-no-dev/01d309cd3760737238cec3468b96f117 to your computer and use it in GitHub Desktop.
Save me-no-dev/01d309cd3760737238cec3468b96f117 to your computer and use it in GitHub Desktop.
#include <ESPAsyncWebServer.h>
#include "SPI.h"
#include "SD.h"
bool SD_exists(String path){
bool exists = false;
sd::File test = SD.open(path);
if(test){
test.close();
exists = true;
}
return exists;
}
class AsyncSDFileResponse: public AsyncAbstractResponse {
private:
sd::File _content;
String _path;
void _setContentType(String path){
if (path.endsWith(".html")) _contentType = "text/html";
else if (path.endsWith(".htm")) _contentType = "text/html";
else if (path.endsWith(".css")) _contentType = "text/css";
else if (path.endsWith(".txt")) _contentType = "text/plain";
else if (path.endsWith(".js")) _contentType = "application/javascript";
else if (path.endsWith(".png")) _contentType = "image/png";
else if (path.endsWith(".gif")) _contentType = "image/gif";
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
else if (path.endsWith(".xml")) _contentType = "text/xml";
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
else if (path.endsWith(".zip")) _contentType = "application/zip";
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
else _contentType = "application/octet-stream";
}
public:
AsyncSDFileResponse(String path, String contentType=String(), bool download=false){
_code = 200;
_path = path;
if(!download && !SD_exists(_path) && SD_exists(_path+".gz")){
_path = _path+".gz";
addHeader("Content-Encoding", "gzip");
}
if(download)
_contentType = "application/octet-stream";
else
_setContentType(path);
_content = SD.open(_path);
_contentLength = _content.size();
}
~AsyncSDFileResponse(){
if(_content)
_content.close();
}
bool _sourceValid(){ return !!(_content); }
size_t _fillBuffer(uint8_t *buf, size_t maxLen){
int r = _content.read(buf, maxLen);
if(r < 0){
os_printf("Error\n");
_content.close();
return 0;
}
return r;
}
};
class SDEditor: public AsyncWebHandler {
private:
String _username;
String _password;
bool _uploadAuthenticated;
uint32_t _startTime;
sd::File uploadFile;
void _delete(String path){
sd::File file = SD.open((char *)path.c_str());
if(!file.isDirectory()){
file.close();
SD.remove((char *)path.c_str());
return;
}
file.rewindDirectory();
sd::File entry;
String entryPath;
while(true) {
entry = file.openNextFile();
if (!entry) break;
entryPath = path + "/" +entry.name();
if(entry.isDirectory()){
entry.close();
_delete(entryPath);
} else {
entry.close();
SD.remove((char *)entryPath.c_str());
}
entryPath = String();
}
SD.rmdir((char *)path.c_str());
path = String();
file.close();
}
public:
SDEditor(String username=String(), String password=String()):_username(username),_password(password),_uploadAuthenticated(false),_startTime(0){}
bool canHandle(AsyncWebServerRequest *request){
if(request->method() == HTTP_GET && request->url() == "/edit" && (SD_exists((char*)"/edit.htm") || SD_exists((char*)"/edit.htm.gz")))
return true;
else if(request->method() == HTTP_GET && request->url() == "/list")
return true;
else if(request->method() == HTTP_GET && (request->url().endsWith("/") || SD_exists(request->url()) || (!request->hasParam("download") && SD_exists(request->url()+".gz"))))
return true;
else if(request->method() == HTTP_POST && request->url() == "/edit")
return true;
else if(request->method() == HTTP_DELETE && request->url() == "/edit")
return true;
else if(request->method() == HTTP_PUT && request->url() == "/edit")
return true;
return false;
}
void handleRequest(AsyncWebServerRequest *request){
if(_username.length() && (request->method() != HTTP_GET || request->url() == "/edit" || request->url() == "/list") && !request->authenticate(_username.c_str(),_password.c_str()))
return request->requestAuthentication();
if(request->method() == HTTP_GET && request->url() == "/edit"){
request->send(new AsyncSDFileResponse("/edit.htm"));
} else if(request->method() == HTTP_GET && request->url() == "/list"){
if(request->hasParam("dir")){
String path = request->getParam("dir")->value();
sd::File entry;
sd::File dir = SD.open((char *)path.c_str());
if(!dir.isDirectory()){
dir.close();
request->send(400);
} else {
dir.rewindDirectory();
String output = "[";
while(true) {
entry = dir.openNextFile();
if (!entry) break;
if (output != "[") output += ',';
output += "{\"type\":\"";
output += (entry.isDirectory())?"dir":"file";
output += "\",\"name\":\"";
String name = String(entry.name());
name.toLowerCase();
output += name;
output += "\"}";
entry.close();
}
output += "]";
dir.close();
request->send(200, "text/json", output);
output = String();
}
}
else
request->send(400);
} else if(request->method() == HTTP_GET){
String path = request->url();
if(path.endsWith("/"))
path += "index.htm";
request->send(new AsyncSDFileResponse(path, String(), request->hasParam("download")));
} else if(request->method() == HTTP_DELETE){
if(request->hasParam("path", true) && SD_exists(request->getParam("path", true)->value())){
_delete(request->getParam("path", true)->value());
request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
} else
request->send(404);
} else if(request->method() == HTTP_POST){
if(request->hasParam("data", true, true) && SD_exists(request->getParam("data", true, true)->value()))
request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
else
request->send(500);
} else if(request->method() == HTTP_PUT){
if(request->hasParam("path", true)){
String filename = request->getParam("path", true)->value();
if(SD_exists(filename)){
request->send(200);
} else {
sd::File f = SD.open(filename, FILE_WRITE | O_TRUNC);
if(f){
f.write((uint8_t)0x00);
f.close();
request->send(200, "", "CREATE: "+filename);
} else {
request->send(500);
}
}
} else
request->send(400);
}
}
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
uploadFile = SD.open(filename, FILE_WRITE | O_TRUNC);
_startTime = millis();
}
if(len && uploadFile)
uploadFile.write(data, len);
if(final){
if(uploadFile) uploadFile.close();
uint32_t uploadTime = millis() - _startTime;
os_printf("upload: %s, %u B, %u ms\n", filename.c_str(), index+len, uploadTime);
}
}
};
const char *getSdTypeString(){
if (SD.type() == SD_CARD_TYPE_SD1){
return "SD1";
} else if(SD.type() == SD_CARD_TYPE_SD2){
return "SD2";
} else if(SD.type() == SD_CARD_TYPE_SDHC){
return "SDHC";
} else {
return "UNKNOWN";
}
}
String getSizeString(size_t bytes){
if (bytes < 1024){
return String(String(bytes)+"B");
} else if(bytes < (1024 * 1024)){
return String(String(bytes/1024.0)+"KB");
} else if(bytes < (1024 * 1024 * 1024)){
return String(String(bytes/1024.0/1024.0)+"MB");
} else {
return String(String(bytes/1024.0/1024.0/1024.0)+"GB");
}
}
bool initSD(){
if (SD.begin(SS, 32000000)){
Serial.print("\n==== SD Card Info ====\n");
Serial.printf("SD Type: %s FAT%d, Size: %s\n", getSdTypeString(), SD.fatType(), getSizeString(SD.size()).c_str());
Serial.printf("SD Blocks: total: %d, size: %s\n", SD.totalBlocks(), getSizeString(SD.blockSize()).c_str());
Serial.printf("SD Clusters: total: %d, blocks: %d, size: %s\n", SD.totalClusters(), SD.blocksPerCluster(), getSizeString(SD.clusterSize()).c_str());
sd::File entry;
sd::File dir = SD.open("/");
dir.rewindDirectory();
while(true){
entry = dir.openNextFile();
if (!entry) break;
Serial.printf("SD File: %s, type: %s, size: %s\n", entry.name(), (entry.isDirectory())?"dir":"file", getSizeString(entry.size()).c_str());
entry.close();
}
dir.close();
Serial.println();
return true;
} else
Serial.println("SD Card Not Found!");
return false;
}
@rokk0
Copy link

rokk0 commented Apr 26, 2017

Hi, Im trying to compile it, but getting errors:
'sd' has not been declared
When remove 'sd::', I get:
'class fs::File' has no member named 'isDirectory'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment