SD получить путь к файлу.

rene
Offline
Зарегистрирован: 21.01.2014

Доброго времени суток!

Есть скетч использующий библиотеку SD

С помощью метода openNextFile() просмтариваю все содержимое карты (и вложенных папок) и найдя необходимый файл пытаюсь его удалить, но для SD.remove(); необходимо указывать полный путь а свойство file.name(); возвращает только имя файла. Как можно узнать полный путь к файлу?

rene
Offline
Зарегистрирован: 21.01.2014

Вот пример функции. Она должна удалить все вложенные файлы и папки начиная с заданной

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) возвращает текущую папку, а не полный путь к ней, соответственно папки и файлы второго уровня я не могу уже удалить

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так Вы сохраняйтё накопленный путь. Вы же начинаете с корня. Вот и заведите строку в которой сидит адрес корня. А когда заходите в папку, присоединяйте в хвост "\"<имя папкаи>. Зайдёте в подкпапку, опять присоединяйте. Так у Вас всегда под рукой будет полный путь к текущей папке.

rene
Offline
Зарегистрирован: 21.01.2014

Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.

ЗЫ. Имею ввиду обсуждение приведенного кода из #1. Для задачи из ТК ваше решение вполне подходит.

rene
Offline
Зарегистрирован: 21.01.2014

Или предлагаете передавать полный путь в функцию отдельным параметром?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

В файле 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;
}
 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

rene пишет:

Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.

ЗЫ. Имею ввиду обсуждение приведенного кода из #1. Для задачи из ТК ваше решение вполне подходит.

Не, конечно же параметром. Например, типа String, причём передавать по значению. Т.е. при входе в функцию будет создаваться новый экземпляр. К нему присобачиваем "хвост", а при выходе и думать ни о чём не надо, т.к. оригинальный экземпляр не менялся. 

rene
Offline
Зарегистрирован: 21.01.2014

Andy пишет:

В файле 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. Я до этого еще не дорос.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

В том же файле есть и 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. Никаких изменений в библиотеку вносить не надо, все уже реализовано до нас, конечно если только мы говорим об одной и той же библиотеке.

rene
Offline
Зарегистрирован: 21.01.2014

Пробовал так:

Serial.println(SD.dirEntry(dir.name());

Получаю в ответ: 'class SDClass' has no member named 'dirEntry'

Не видит компилятор такого метода у класса. Если не трудно, попробуйте пожалуйста у себя данный метод, стоит ли копать мне дальше в этом направлении?

 

rene
Offline
Зарегистрирован: 21.01.2014

ЕвгенийП пишет:

rene пишет:

Использовать глобальную переменную для хранения пути? А когда я начну возвращаться из рекурсивного дерева, мне этот путь обрезать с конца придется? Не очень красиво получается.

ЗЫ. Имею ввиду обсуждение приведенного кода из #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 продолжает хранится информация о предыдущем пути к папке, хотя это вроде как другой экземпляр. Как такое получается?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Это если бы там был String, то был бы другой экземпляр. А так - нет, так надо ручками удалять то. что добавил.

rene
Offline
Зарегистрирован: 21.01.2014

При использовании String в SD.remove() SD.mkdir() у меня контроллер уходит в бесконечный ребут, а более старые версии IDE вообще не принимают такой тип данных в этих методах. Поэтому приходится конвертировать в *char. Поясните пожалуйста в чем принципиальное отличие String от *char? Они по разному хранятся в памяти? Почему для *char не создается отдельный экземпляр при рекурсивном вызове функции?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Так Вы ж указатель передаёте, кто ж за Вас будет экземпляры создавать? Убирайте хвост руками - ничего страшного. При входе запомните указатель на конечный нулевой символ, а при выходе вписывайте туда 0. Вот и всё убирание. Делов-то!

rene
Offline
Зарегистрирован: 21.01.2014

ок, спасибо!

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

rene пишет:
Получаю в ответ: 'class SDClass' has no member named 'dirEntry'
Доступ к методу dirEntry можно получить через переменную класса _file, беда в том, что она объявлена, как приватная. Нужно либо делать её публичной, что идеологически не правильно, либо добавлять новый метод в класс.

rene
Offline
Зарегистрирован: 21.01.2014

Andy пишет:

rene пишет:
Получаю в ответ: 'class SDClass' has no member named 'dirEntry'
Доступ к методу dirEntry можно получить через переменную класса _file, беда в том, что она объявлена, как приватная. Нужно либо делать её публичной, что идеологически не правильно, либо добавлять новый метод в класс.

Спасибо за информацию

rene
Offline
Зарегистрирован: 21.01.2014

Все таки решил через 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 помимо самого пути попадает всякий мусор, в частности в конце пути попадает символ '='. Откуда он там берется я так и не понял. Если не трудно, проясните пожалуйста этот момент.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

rene, Вы всё слишком усложнили. Это не так делается. Зачем Вам вообще символьный массив, раз уж Вы взялись пользоваться String?

Я могу попытаться помочь Вам написать нормальный код, только у меня нет под рукой SD карты, чтобы всё проверить. Так что отлаживать придётся типа "я написал - Вы запустили". Согласны? Если да, то как писать, с char[] или со String?

rene
Offline
Зарегистрирован: 21.01.2014

Как я писал выше, метод SD.remove() не принимает тип String, по крайне мере у меня при попытке подсунуть в метод строку, устройство уходит в бесконечный ребут.

А так, да конечно согласен! Всегда интересно как выполняют ту же задачу профессионалы, если вас не затруднит, покажите как делать с char

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Хорошо, завтра я сделаю с char[].

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, попробуйте вот так.

#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 не пробовал.

rene
Offline
Зарегистрирован: 21.01.2014

Все работает, огромное человеческое спасибо! Буду изучать и применять полученный опыт.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Да, незачто. Я удивлён. что с первого тычка заработало. Но если чего потом вылезет, пишите.