3 SD - a slightly more friendly wrapper for sdfatlib
5 This library aims to expose a subset of SD card functionality
6 in the form of a higher level "wrapper" object.
8 License: GNU General Public License V3
9 (Because sdfatlib is licensed with this.)
11 (C) Copyright 2010 SparkFun Electronics
14 This library provides four key benefits:
16 * Including `SD.h` automatically creates a global
17 `SD` object which can be interacted with in a similar
18 manner to other standard global objects like `Serial` and `Ethernet`.
20 * Boilerplate initialisation code is contained in one method named
21 `begin` and no further objects need to be created in order to access
24 * Calls to `open` can supply a full path name including parent
25 directories which simplifies interacting with files in subdirectories.
27 * Utility methods are provided to determine whether a file exists
28 and to create a directory heirarchy.
31 Note however that not all functionality provided by the underlying
32 sdfatlib library is exposed.
40 In order to handle multi-directory path traversal, functionality that
41 requires this ability is implemented as callback functions.
43 Individual methods call the `walkPath` function which performs the actual
44 directory traversal (swapping between two different directory/file handles
45 along the way) and at each level calls the supplied callback function.
47 Some types of functionality will take an action at each level (e.g. exists
48 or make directory) which others will only take an action at the bottom
55 // Used by `getNextPathComponent`
56 #define MAX_COMPONENT_LEN 12 // What is max length?
57 #define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1
59 bool getNextPathComponent(char *path, unsigned int *p_offset,
63 Parse individual path components from a path.
65 e.g. after repeated calls '/foo/bar/baz' will be split
66 into 'foo', 'bar', 'baz'.
68 This is similar to `strtok()` but copies the component into the
69 supplied buffer rather than modifying the original string.
72 `buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size.
74 `p_offset` needs to point to an integer of the offset at
75 which the previous path component finished.
77 Returns `true` if more components remain.
79 Returns `false` if this is the last component.
80 (This means path ended with 'foo' or 'foo/'.)
84 // TODO: Have buffer local to this function, so we know it's the
89 int offset = *p_offset;
91 // Skip root or other separator
92 if (path[offset] == '/') {
96 // Copy the next next path segment
97 while (bufferOffset < MAX_COMPONENT_LEN
98 && (path[offset] != '/')
99 && (path[offset] != '\0')) {
100 buffer[bufferOffset++] = path[offset++];
103 buffer[bufferOffset] = '\0';
105 // Skip trailing separator so we can determine if this
106 // is the last component in the path or not.
107 if (path[offset] == '/') {
113 return (path[offset] != '\0');
118 boolean walkPath(char *filepath, SdFile& parentDir,
119 boolean (*callback)(SdFile& parentDir,
120 char *filePathComponent,
121 boolean isLastComponent,
123 void *object = NULL) {
126 When given a file path (and parent directory--normally root),
127 this function traverses the directories in the path and at each
128 level calls the supplied callback function while also providing
129 the supplied object for context if required.
131 e.g. given the path '/foo/bar/baz'
132 the callback would be called at the equivalent of
133 '/foo', '/foo/bar' and '/foo/bar/baz'.
135 The implementation swaps between two different directory/file
136 handles as it traverses the directories and does not use recursion
137 in an attempt to use memory efficiently.
139 If a callback wishes to stop the directory traversal it should
140 return false--in this case the function will stop the traversal,
141 tidy up and return false.
143 If a directory path doesn't exist at some point this function will
144 also return false and not subsequently call the callback.
146 If a directory path specified is complete, valid and the callback
147 did not indicate the traversal should be interrupted then this
148 function will return true.
156 char buffer[PATH_COMPONENT_BUFFER_LEN];
158 unsigned int offset = 0;
163 SdFile *p_tmp_sdfile;
167 p_parent = &parentDir;
171 boolean moreComponents = getNextPathComponent(filepath, &offset, buffer);
173 boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object);
175 if (!shouldContinue) {
176 // TODO: Don't repeat this code?
177 // If it's one we've created then we
178 // don't need the parent handle anymore.
179 if (p_parent != &parentDir) {
185 if (!moreComponents) {
189 boolean exists = (*p_child).open(*p_parent, buffer, O_RDONLY);
191 // If it's one we've created then we
192 // don't need the parent handle anymore.
193 if (p_parent != &parentDir) {
197 // Handle case when it doesn't exist and we can't continue...
199 // We alternate between two file handles as we go down
201 if (p_parent == &parentDir) {
202 p_parent = &subfile2;
205 p_tmp_sdfile = p_parent;
207 p_child = p_tmp_sdfile;
213 if (p_parent != &parentDir) {
214 (*p_parent).close(); // TODO: Return/ handle different?
224 The callbacks used to implement various functionality follow.
226 Each callback is supplied with a parent directory handle,
227 character string with the name of the current file path component,
228 a flag indicating if this component is the last in the path and
229 a pointer to an arbitrary object used for context.
233 boolean callback_pathExists(SdFile& parentDir, char *filePathComponent,
234 boolean isLastComponent, void *object) {
237 Callback used to determine if a file/directory exists in parent
240 Returns true if file path exists.
245 boolean exists = child.open(parentDir, filePathComponent, O_RDONLY);
256 boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent,
257 boolean isLastComponent, void *object) {
260 Callback used to create a directory in the parent directory if
261 it does not already exist.
263 Returns true if a directory was created or it already existed.
266 boolean result = false;
269 result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object);
271 result = child.makeDir(parentDir, filePathComponent);
280 boolean callback_openPath(SdFile& parentDir, char *filePathComponent,
281 boolean isLastComponent, void *object) {
283 Callback used to open a file specified by a filepath that may
284 specify one or more directories above it.
286 Expects the context object to be an instance of `SDClass` and
287 will use the `file` property of the instance to open the requested
288 file/directory with the associated file open mode property.
290 Always returns true if the directory traversal hasn't reached the
291 bottom of the directory heirarchy.
293 Returns false once the file has been opened--to prevent the traversal
294 from descending further. (This may be unnecessary.)
296 if (isLastComponent) {
297 SDClass *p_SD = static_cast<SDClass*>(object);
298 p_SD->file.open(parentDir, filePathComponent, p_SD->fileOpenMode);
299 if (p_SD->fileOpenMode == FILE_WRITE) {
300 p_SD->file.seekSet(p_SD->file.fileSize());
302 // TODO: Return file open result?
311 boolean callback_remove(SdFile& parentDir, char *filePathComponent,
312 boolean isLastComponent, void *object) {
313 if (isLastComponent) {
314 return SdFile::remove(parentDir, filePathComponent);
319 boolean callback_rmdir(SdFile& parentDir, char *filePathComponent,
320 boolean isLastComponent, void *object) {
321 if (isLastComponent) {
323 if (!f.open(parentDir, filePathComponent, O_READ)) return false;
331 /* Implementation of class used to create `SDCard` object. */
335 boolean SDClass::begin(uint8_t csPin, int8_t mosi, int8_t miso, int8_t sck) {
338 Performs the initialisation required by the sdfatlib library.
340 Return true if initialization succeeds, false otherwise.
343 return card.init(SPI_HALF_SPEED, csPin, mosi, miso, sck) &&
345 root.openRoot(volume);
350 // this little helper is used to traverse paths
351 SdFile SDClass::getParentDir(char *filepath, int *index) {
352 // get parent directory
353 SdFile d1 = root; // start with the mostparent, root!
356 // we'll use the pointers to swap between the two objects
357 SdFile *parent = &d1;
358 SdFile *subdir = &d2;
360 char *origpath = filepath;
362 while (strchr(filepath, '/')) {
364 // get rid of leading /'s
365 if (filepath[0] == '/') {
370 if (! strchr(filepath, '/')) {
371 // it was in the root directory, so leave now
375 // extract just the name of the next subdirectory
376 uint8_t idx = strchr(filepath, '/') - filepath;
378 idx = 12; // dont let them specify long names
380 strncpy(subdirname, filepath, idx);
383 // close the subdir (we reuse them) if open
385 if (! subdir->open(parent, subdirname, O_READ)) {
386 // failed to open one of the subdirectories
389 // move forward to the next subdirectory
392 // we reuse the objects, close it.
401 *index = (int)(filepath - origpath);
402 // parent is now the parent diretory of the file!
407 File SDClass::open(char *filepath, uint8_t mode) {
410 Open the supplied file path for reading or writing.
412 The file content can be accessed via the `file` property of
413 the `SDClass` object--this property is currently
414 a standard `SdFile` object from `sdfatlib`.
416 Defaults to read only.
418 If `write` is true, default action (when `append` is true) is to
419 append data to the end of the file.
421 If `append` is false then the file will be truncated first.
423 If the file does not exist and it is opened for writing the file
426 An attempt to open a file for reading that does not exist is an
433 // do the interative search
434 SdFile parentdir = getParentDir(filepath, &pathidx);
440 // it was the directory itself!
441 return File(parentdir, "/");
444 // Open the file itself
447 // failed to open a subdir!
448 if (!parentdir.isOpen())
451 // there is a special case for the Root directory since its a static dir
452 if (parentdir.isRoot()) {
453 if ( ! file.open(SD.root, filepath, mode)) {
454 // failed to open the file :(
457 // dont close the root!
459 if ( ! file.open(parentdir, filepath, mode)) {
466 if (mode & (O_APPEND | O_WRITE))
467 file.seekSet(file.fileSize());
468 return File(file, filepath);
473 File SDClass::open(char *filepath, uint8_t mode) {
476 Open the supplied file path for reading or writing.
478 The file content can be accessed via the `file` property of
479 the `SDClass` object--this property is currently
480 a standard `SdFile` object from `sdfatlib`.
482 Defaults to read only.
484 If `write` is true, default action (when `append` is true) is to
485 append data to the end of the file.
487 If `append` is false then the file will be truncated first.
489 If the file does not exist and it is opened for writing the file
492 An attempt to open a file for reading that does not exist is an
497 // TODO: Allow for read&write? (Possibly not, as it requires seek.)
500 walkPath(filepath, root, callback_openPath, this);
508 //boolean SDClass::close() {
511 // Closes the file opened by the `open` method.
518 boolean SDClass::exists(char *filepath) {
521 Returns true if the supplied file path exists.
524 return walkPath(filepath, root, callback_pathExists);
528 //boolean SDClass::exists(char *filepath, SdFile& parentDir) {
531 // Returns true if the supplied file path rooted at `parentDir`
535 // return walkPath(filepath, parentDir, callback_pathExists);
539 boolean SDClass::mkdir(char *filepath) {
542 Makes a single directory or a heirarchy of directories.
544 A rough equivalent to `mkdir -p`.
547 return walkPath(filepath, root, callback_makeDirPath);
550 boolean SDClass::rmdir(char *filepath) {
553 Makes a single directory or a heirarchy of directories.
555 A rough equivalent to `mkdir -p`.
558 return walkPath(filepath, root, callback_rmdir);
561 boolean SDClass::remove(char *filepath) {
562 return walkPath(filepath, root, callback_remove);
565 void SDClass::enableCRC(boolean mode) {
566 card.enableCRC(mode);
570 // allows you to recurse into a directory
571 File File::openNextFile(uint8_t mode) {
574 //Serial.print("\t\treading dir...");
575 while (_file->readDir(&p) > 0) {
577 // done if past last used entry
578 if (p.name[0] == DIR_NAME_FREE) {
579 //Serial.println("end");
583 // skip deleted entry and entries for . and ..
584 if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') {
585 //Serial.println("dots");
589 // only list subdirectories and files
590 if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
591 //Serial.println("notafile");
595 // print file name with possible blank fill
598 _file->dirName(p, name);
599 //Serial.print("try to open file ");
600 //Serial.println(name);
602 if (f.open(_file, name, mode)) {
603 //Serial.println("OK!");
604 return File(f, name);
606 //Serial.println("ugh");
611 //Serial.println("nothing");
615 void File::rewindDirectory(void) {