SD получить путь к файлу.
- Войдите на сайт для отправки комментариев
Ср, 13/01/2016 - 13:20
Доброго времени суток!
Есть скетч использующий библиотеку SD
С помощью метода openNextFile() просмтариваю все содержимое карты (и вложенных папок) и найдя необходимый файл пытаюсь его удалить, но для SD.remove(); необходимо указывать полный путь а свойство file.name(); возвращает только имя файла. Как можно узнать полный путь к файлу?
Вот пример функции. Она должна удалить все вложенные файлы и папки начиная с заданной
void _ClearFolder(File dir) { char *path; // указатель для хранения пути к файлу или папке while (true) { File entry = dir.openNextFile(); if (! entry) { // файлов более нет, выходим из цикла break; } // Собираем полный путь к файлу или папке path = dir.name(); strcat(path,"/"); strcat(path,entry.name()); if (entry.isDirectory()) { // это директория, вызываем функцию рекурсивно _ClearFolder(entry); Serial.print("Remove folder "); Serial.print(path); if (SD.rmdir(path)) Serial.println(" done"); else Serial.println (" error"); } else { // это файл, удаляем его Serial.print("Remove file "); Serial.print(path); if (SD.remove(path)) Serial.println(" done"); else Serial.println (" error"); } entry.close(); } }Проблема тут в том, что dir.name() (стр. 11) возвращает текущую папку, а не полный путь к ней, соответственно папки и файлы второго уровня я не могу уже удалить
Так Вы сохраняйтё накопленный путь. Вы же начинаете с корня. Вот и заведите строку в которой сидит адрес корня. А когда заходите в папку, присоединяйте в хвост "\"<имя папкаи>. Зайдёте в подкпапку, опять присоединяйте. Так у Вас всегда под рукой будет полный путь к текущей папке.
Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.
ЗЫ. Имею ввиду обсуждение приведенного кода из #1. Для задачи из ТК ваше решение вполне подходит.
Или предлагаете передавать полный путь в функцию отдельным параметром?
В файле SdFile.cpp есть функция, возможно это то что нужно:
/**
* Return a files directory entry
*
* \param[out] dir Location for return of the files directory entry.
*
* \return The value one, true, is returned for success and
* the value zero, false, is returned for failure.
*/
uint8_t SdFile::dirEntry(dir_t* dir) {
// make sure fields on SD are correct
if (!sync()) return false;
// read entry
dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
if (!p) return false;
// copy to caller's struct
memcpy(dir, p, sizeof(dir_t));
return true;
}
Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.
ЗЫ. Имею ввиду обсуждение приведенного кода из #1. Для задачи из ТК ваше решение вполне подходит.
Не, конечно же параметром. Например, типа String, причём передавать по значению. Т.е. при входе в функцию будет создаваться новый экземпляр. К нему присобачиваем "хвост", а при выходе и думать ни о чём не надо, т.к. оригинальный экземпляр не менялся.
В файле SdFile.cpp есть функция, возможно это то что нужно:
/**
* Return a files directory entry
*
* \param[out] dir Location for return of the files directory entry.
*
* \return The value one, true, is returned for success and
* the value zero, false, is returned for failure.
*/
uint8_t SdFile::dirEntry(dir_t* dir) {
// make sure fields on SD are correct
if (!sync()) return false;
// read entry
dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
if (!p) return false;
// copy to caller's struct
memcpy(dir, p, sizeof(dir_t));
return true;
}
Я так понимаю, для возможности использования данной функции необходимо внести изменения в библиотеку SD. Я до этого еще не дорос.
В том же файле есть и SD.remove();
/**
* Remove a file.
*
* The directory entry and all data for the file are deleted.
*
* \param[in] dirFile The directory that contains the file.
* \param[in] fileName The name of the file to be removed.
*
* \note This function should not be used to delete the 8.3 version of a
* file that has a long name. For example if a file has the long name
* "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
*
* \return The value one, true, is returned for success and
* the value zero, false, is returned for failure.
* Reasons for failure include the file is a directory, is read only,
* \a dirFile is not a directory, \a fileName is not found
* or an I/O error occurred.
*/
uint8_t SdFile::remove(SdFile* dirFile, const char* fileName) {
SdFile file;
if (!file.open(dirFile, fileName, O_WRITE)) return false;
return file.remove();
}
следовательно SD.dirEntry(dir_t* dir); должна вернуть директорию в переменной dir. Никаких изменений в библиотеку вносить не надо, все уже реализовано до нас, конечно если только мы говорим об одной и той же библиотеке.
Пробовал так:
Получаю в ответ: 'class SDClass' has no member named 'dirEntry'
Не видит компилятор такого метода у класса. Если не трудно, попробуйте пожалуйста у себя данный метод, стоит ли копать мне дальше в этом направлении?
Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.
ЗЫ. Имею ввиду обсуждение приведенного кода из #1. Для задачи из ТК ваше решение вполне подходит.
Не, конечно же параметром. Например, типа String, причём передавать по значению. Т.е. при входе в функцию будет создаваться новый экземпляр. К нему присобачиваем "хвост", а при выходе и думать ни о чём не надо, т.к. оригинальный экземпляр не менялся.
Мне крайне не удобно было использовать String, поскольку потом все равно пришлось бы переводить в массив char, поэтому накидал такой код:
void _ClearFolder(File dir, char *path) { Serial.println(">"); if (path[strlen(path) - 1] != '/') strcat(path, "/"); char *tempPath; while (true) { File entry = dir.openNextFile(); if (! entry) break; // файлов более нет, выходим из цикла // Собираем полный путь к файлу или папке tempPath = path; strcat(tempPath, entry.name()); Serial.println(tempPath); if (entry.isDirectory()) { // это директория, вызываем функцию рекурсивно _ClearFolder(entry, tempPath); Serial.print("Remove folder "); Serial.print(tempPath); if (SD.exists(tempPath)) Serial.println(" done"); else Serial.println (" error"); } else { // это файл, удаляем его Serial.print("Remove file "); Serial.print(tempPath); entry.close(); if (SD.exists(tempPath)) Serial.println(" done"); else Serial.println (" error"); } entry.close(); } Serial.println("<"); }Стркутура папок/файлов на карте такова:
Request to reading SD card
TEMP/
1/
2/
ERROR.TXT 18268
2.TXT 18268
1.TXT 18268
Client disonnected
При выполнении кода получаю следующее:
Request to clear directory /
>
/TEMP
>
/TEMP/1
>
/TEMP/1/2
>
<
Remove folder /TEMP/1/2/ done
/TEMP/1/2/ERROR.TXT
Remove file /TEMP/1/2/ERROR.TXT error
<
Remove folder /TEMP/1/2/ERROR.TXT error
/TEMP/1/2/ERROR.TXT2.TXT
Remove file /TEMP/1/2/ERROR.TXT2.TXT error
/TEMP/1/2/ERROR.TXT2.TXT1.TXT
Remove file /TEMP/1/2/ERROR.TXT2.TXT1.TXT error
<
Remove folder /TEMP/1/2/ERROR.TXT2.TXT1.TXT error
<
Client disonnected
т.е. при выходе из функции в переменной path продолжает хранится информация о предыдущем пути к папке, хотя это вроде как другой экземпляр. Как такое получается?
Это если бы там был String, то был бы другой экземпляр. А так - нет, так надо ручками удалять то. что добавил.
При использовании String в SD.remove() SD.mkdir() у меня контроллер уходит в бесконечный ребут, а более старые версии IDE вообще не принимают такой тип данных в этих методах. Поэтому приходится конвертировать в *char. Поясните пожалуйста в чем принципиальное отличие String от *char? Они по разному хранятся в памяти? Почему для *char не создается отдельный экземпляр при рекурсивном вызове функции?
Так Вы ж указатель передаёте, кто ж за Вас будет экземпляры создавать? Убирайте хвост руками - ничего страшного. При входе запомните указатель на конечный нулевой символ, а при выходе вписывайте туда 0. Вот и всё убирание. Делов-то!
ок, спасибо!
Спасибо за информацию
Все таки решил через String, ибо так оказалось проще. Все работает, единственное непонятный для меня момент. Вот функция:
void _ClearFolder(File dir, String path) { if (!path.endsWith("/")) path += "/"; // если в конце пути отсутствует слеш, добавляем его char charPath[path.length() + 1]; // массив для хранения полного пути к файлу/папке while (true) { File entry = dir.openNextFile(); if (! entry) break; // файлов более нет, выходим из цикла // Собираем полный путь к файлу или папке path.toCharArray(charPath, sizeof(charPath)); strcat(charPath, entry.name()); if (entry.isDirectory()) { // обнаружена директория, вызываем функцию рекурсивно _ClearFolder(entry, String(charPath)); path.toCharArray(charPath, sizeof(charPath)); strcat(charPath, entry.name()); Serial.print("Remove folder "); Serial.print(charPath); if (SD.rmdir(charPath)) Serial.println(" done"); else Serial.println (" error"); } else { // это файл, удаляем его Serial.print("Remove file "); Serial.print(charPath); if (SD.remove(charPath)) Serial.println(" done"); else Serial.println (" error"); } entry.close(); } }Обратите внимание что строки 16, 17 диблируют строки 11, 12, т.е. фактически после рекурсивного выхода из функции мне приходится по новой собирать полный путь. Если этого не делать, в переменной charPath помимо самого пути попадает всякий мусор, в частности в конце пути попадает символ '='. Откуда он там берется я так и не понял. Если не трудно, проясните пожалуйста этот момент.
rene, Вы всё слишком усложнили. Это не так делается. Зачем Вам вообще символьный массив, раз уж Вы взялись пользоваться String?
Я могу попытаться помочь Вам написать нормальный код, только у меня нет под рукой SD карты, чтобы всё проверить. Так что отлаживать придётся типа "я написал - Вы запустили". Согласны? Если да, то как писать, с char[] или со String?
Как я писал выше, метод SD.remove() не принимает тип String, по крайне мере у меня при попытке подсунуть в метод строку, устройство уходит в бесконечный ребут.
А так, да конечно согласен! Всегда интересно как выполняют ту же задачу профессионалы, если вас не затруднит, покажите как делать с char
Хорошо, завтра я сделаю с char[].
Ну, попробуйте вот так.
#include <SPI.h> #include <SD.h> //////////////////////////////////////////////// // // Значения возвращаемые функцией __ClearFolder // enum CLEAR_FOLDER_RESULTS : int8_t { SUCCESS = 0, BUFFER_OVERFLOW, FILE_REMOVE_ERROR, DIR_REMOVE_ERROR }; //////////////////////////////////////////////// // // Функция __ClearFolder - очищает указанный фолдер // // Параметры: // dir - открытый фолдер, который следует очистить // path - адрес буфера в котором находится путь фолдера dir с "/" на конце. // Буфер должен быть достаточно большим, чтобы вместить самый длинный из // возможных путей к файлу внутри фолдера dir // bufferLength - полная длина буфера path // Возвращаемые значения: // SUCCESS - нормально. Буфер path такой же, как и был при вызове // BUFFER_OVERFLOW - переполнение буфера. Буфер path содержит последний, поместившийся путь. // Следующее имя файла/фолдера добавить уже не удалось. // FILE_REMOVE_ERROR - ошибка удаления файла. Буфер path содержит полный путь к проблемному файлу // DIR_REMOVE_ERROR - ошибка удаления фолдера. Буфер path содержит полный путь к проблемному фолдеру // const int8_t __ClearFolder(File dir, char * path, const int bufferLength) { const int pathLength = strlen(path); char * pathEnd = path + pathLength; for (File entry = dir.openNextFile(); entry; entry = dir.openNextFile()) { const int newPathLength = pathLength + strlen(entry.name()); if (newPathLength >= bufferLength) { entry.close(); return BUFFER_OVERFLOW; } strcpy(pathEnd, entry.name()); if (entry.isDirectory()) { if (newPathLength + 1 >= bufferLength) { entry.close(); return BUFFER_OVERFLOW; } strcpy(path + newPathLength, "/"); const int8_t result = __ClearFolder(entry, path, bufferLength); if (result != SUCCESS) { entry.close(); return result; } if (! SD.rmdir(path)) { entry.close(); return DIR_REMOVE_ERROR; } } else { if (! SD.remove(path)) { entry.close(); return FILE_REMOVE_ERROR; } } entry.close(); } *pathEnd = '\0'; return SUCCESS; } // // Тестовый запуск // #define CS_PIN 4 void setup(void) { Serial.begin(115200); if (!SD.begin(CS_PIN)) { Serial.println("SD Initialization failed!"); return; } char szBuffer[81]; // Должно хватить для разумной структуры директорий. Если там много субфолдеров, придётся увеличить буфер strcpy(szBuffer, "/"); File root = SD.open(szBuffer); switch (__ClearFolder(root, szBuffer, sizeof(szBuffer)/sizeof(szBuffer[0]))) { case SUCCESS: Serial.println("Okay!"); break; case BUFFER_OVERFLOW: Serial.print("*** BUFFER OVERFLOW\nLast correct path: \""); Serial.print(szBuffer); Serial.println("\""); break; case FILE_REMOVE_ERROR: Serial.print("*** FILE REMOVING ERROR\nFile path: \""); Serial.print(szBuffer); Serial.println("\""); break; case DIR_REMOVE_ERROR: Serial.print("*** FOLDER REMOVING ERROR\nFolder path: \""); Serial.print(szBuffer); Serial.println("\""); break; } root.close(); } void loop(void){}Как видите, техника работы с массивом простая. В начале я запоминаю где находится терминальный 0, а в конце восстанавливаю его на место. Так что, что бы мы ни делали внутри, при нормальном (без ошибки) выходе иуффер всегда в точности восстанавливается. При выходе по ошибке я умышленно не восстанавливаю его, чтобы там сохранилось имя проблемного файла и т.п. (см. комментарий перед функцией).
Только, я уже предупреждал, сам я не запускал (нет у меня сейчас картридера), так что гарантировать, что заработает - не могу. Если будут проблемы, опишите подробно в чём выражаются. Я тогда. может быть отладочных печатей понаставлю - отладим. в общем, куда она денется.
Там я что-то прокомментировал, но если остались непонятки, спрашивайте.
Да, кстати. По уму бы в строках 27 и 37 надо вместо uint8_t писать CLEAR_FOLDER_RESULTS. Так более грамотно, но .... спасибо разработчикам IDE за то, что в текст грязными руками лезут. Кто-то здесь писал, что в новом IDE такого уже нет и там можно писать правильно, но я пока того нового IDE не пробовал.
Все работает, огромное человеческое спасибо! Буду изучать и применять полученный опыт.
Да, незачто. Я удивлён. что с первого тычка заработало. Но если чего потом вылезет, пишите.