@@ -438,3 +438,156 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
438438 }
439439 return false ;
440440}
441+
442+ // copy a file, delete destination file if incomplete to prevent corrupted files
443+ bool copyFile (const char * src_path, const char * dst_path) {
444+ DEBUG_PRINTF (" copyFile from %s to %s\n " , src_path, dst_path);
445+ if (!WLED_FS.exists (src_path)) {
446+ DEBUG_PRINTLN (F (" file not found" ));
447+ return false ;
448+ }
449+
450+ bool success = true ; // is set to false on error
451+ File src = WLED_FS.open (src_path, " r" );
452+ File dst = WLED_FS.open (dst_path, " w" );
453+
454+ if (src && dst) {
455+ uint8_t buf[128 ]; // copy file in 128-byte blocks
456+ while (src.available () > 0 ) {
457+ size_t bytesRead = src.read (buf, sizeof (buf));
458+ if (bytesRead == 0 ) {
459+ success = false ;
460+ break ; // error, no data read
461+ }
462+ size_t bytesWritten = dst.write (buf, bytesRead);
463+ if (bytesWritten != bytesRead) {
464+ success = false ;
465+ break ; // error, not all data written
466+ }
467+ }
468+ } else {
469+ success = false ; // error, could not open files
470+ }
471+ if (src) src.close ();
472+ if (dst) dst.close ();
473+ if (!success) {
474+ DEBUG_PRINTLN (F (" copy failed" ));
475+ WLED_FS.remove (dst_path); // delete incomplete file
476+ }
477+ return success;
478+ }
479+
480+ // compare two files, return true if identical
481+ bool compareFiles (const char * path1, const char * path2) {
482+ DEBUG_PRINTF (" compareFile %s and %s\n " , path1, path2);
483+ if (!WLED_FS.exists (path1) || !WLED_FS.exists (path2)) {
484+ DEBUG_PRINTLN (F (" file not found" ));
485+ return false ;
486+ }
487+
488+ bool identical = true ; // set to false on mismatch
489+ File f1 = WLED_FS.open (path1, " r" );
490+ File f2 = WLED_FS.open (path2, " r" );
491+
492+ if (f1 && f2) {
493+ uint8_t buf1[128 ], buf2[128 ];
494+ while (f1.available () > 0 || f2.available () > 0 ) {
495+ size_t len1 = f1.read (buf1, sizeof (buf1));
496+ size_t len2 = f2.read (buf2, sizeof (buf2));
497+
498+ if (len1 != len2) {
499+ identical = false ;
500+ break ; // files differ in size or read failed
501+ }
502+
503+ if (memcmp (buf1, buf2, len1) != 0 ) {
504+ identical = false ;
505+ break ; // files differ in content
506+ }
507+ }
508+ } else {
509+ identical = false ; // error opening files
510+ }
511+
512+ if (f1) f1.close ();
513+ if (f2) f2.close ();
514+ return identical;
515+ }
516+
517+ static const char s_backup_fmt[] PROGMEM = " /bkp.%s" ;
518+
519+ bool backupFile (const char * filename) {
520+ DEBUG_PRINTF (" backup %s \n " , filename);
521+ if (!validateJsonFile (filename)) {
522+ DEBUG_PRINTLN (F (" broken file" ));
523+ return false ;
524+ }
525+ char backupname[32 ];
526+ snprintf_P (backupname, sizeof (backupname), s_backup_fmt, filename + 1 ); // skip leading '/' in filename
527+
528+ if (copyFile (filename, backupname)) {
529+ DEBUG_PRINTLN (F (" backup ok" ));
530+ return true ;
531+ }
532+ DEBUG_PRINTLN (F (" backup failed" ));
533+ return false ;
534+ }
535+
536+ bool restoreFile (const char * filename) {
537+ DEBUG_PRINTF (" restore %s \n " , filename);
538+ char backupname[32 ];
539+ snprintf_P (backupname, sizeof (backupname), s_backup_fmt, filename + 1 ); // skip leading '/' in filename
540+
541+ if (!WLED_FS.exists (backupname)) {
542+ DEBUG_PRINTLN (F (" no backup found" ));
543+ return false ;
544+ }
545+
546+ if (!validateJsonFile (backupname)) {
547+ DEBUG_PRINTLN (F (" broken backup" ));
548+ return false ;
549+ }
550+
551+ if (copyFile (backupname, filename)) {
552+ DEBUG_PRINTLN (F (" restore ok" ));
553+ return true ;
554+ }
555+ DEBUG_PRINTLN (F (" restore failed" ));
556+ return false ;
557+ }
558+
559+ bool validateJsonFile (const char * filename) {
560+ if (!WLED_FS.exists (filename)) return false ;
561+ File file = WLED_FS.open (filename, " r" );
562+ if (!file) return false ;
563+ StaticJsonDocument<0 > doc, filter; // https://arduinojson.org/v6/how-to/validate-json/
564+ bool result = deserializeJson (doc, file, DeserializationOption::Filter (filter)) == DeserializationError::Ok;
565+ file.close ();
566+ if (!result) {
567+ DEBUG_PRINTF_P (PSTR (" Invalid JSON file %s\n " ), filename);
568+ } else {
569+ DEBUG_PRINTF_P (PSTR (" Valid JSON file %s\n " ), filename);
570+ }
571+ return result;
572+ }
573+
574+ // print contents of all files in root dir to Serial except wsec files
575+ void dumpFilesToSerial () {
576+ File rootdir = WLED_FS.open (" /" , " r" );
577+ File rootfile = rootdir.openNextFile ();
578+ while (rootfile) {
579+ size_t len = strlen (rootfile.name ());
580+ // skip files starting with "wsec" and dont end in .json
581+ if (strncmp (rootfile.name (), " wsec" , 4 ) != 0 && len >= 6 && strcmp (rootfile.name () + len - 5 , " .json" ) == 0 ) {
582+ Serial.println (rootfile.name ());
583+ while (rootfile.available ()) {
584+ Serial.write (rootfile.read ());
585+ }
586+ Serial.println ();
587+ Serial.println ();
588+ }
589+ rootfile.close ();
590+ rootfile = rootdir.openNextFile ();
591+ }
592+ }
593+
0 commit comments