diff options
Diffstat (limited to 'libraries/SD/SD.cpp')
-rw-r--r-- | libraries/SD/SD.cpp | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/libraries/SD/SD.cpp b/libraries/SD/SD.cpp new file mode 100644 index 0000000..c746809 --- /dev/null +++ b/libraries/SD/SD.cpp @@ -0,0 +1,616 @@ +/* + + SD - a slightly more friendly wrapper for sdfatlib + + This library aims to expose a subset of SD card functionality + in the form of a higher level "wrapper" object. + + License: GNU General Public License V3 + (Because sdfatlib is licensed with this.) + + (C) Copyright 2010 SparkFun Electronics + + + This library provides four key benefits: + + * Including `SD.h` automatically creates a global + `SD` object which can be interacted with in a similar + manner to other standard global objects like `Serial` and `Ethernet`. + + * Boilerplate initialisation code is contained in one method named + `begin` and no further objects need to be created in order to access + the SD card. + + * Calls to `open` can supply a full path name including parent + directories which simplifies interacting with files in subdirectories. + + * Utility methods are provided to determine whether a file exists + and to create a directory heirarchy. + + + Note however that not all functionality provided by the underlying + sdfatlib library is exposed. + + */ + +/* + + Implementation Notes + + In order to handle multi-directory path traversal, functionality that + requires this ability is implemented as callback functions. + + Individual methods call the `walkPath` function which performs the actual + directory traversal (swapping between two different directory/file handles + along the way) and at each level calls the supplied callback function. + + Some types of functionality will take an action at each level (e.g. exists + or make directory) which others will only take an action at the bottom + level (e.g. open). + + */ + +#include "SD.h" + +// Used by `getNextPathComponent` +#define MAX_COMPONENT_LEN 12 // What is max length? +#define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1 + +bool getNextPathComponent(char *path, unsigned int *p_offset, + char *buffer) { + /* + + Parse individual path components from a path. + + e.g. after repeated calls '/foo/bar/baz' will be split + into 'foo', 'bar', 'baz'. + + This is similar to `strtok()` but copies the component into the + supplied buffer rather than modifying the original string. + + + `buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size. + + `p_offset` needs to point to an integer of the offset at + which the previous path component finished. + + Returns `true` if more components remain. + + Returns `false` if this is the last component. + (This means path ended with 'foo' or 'foo/'.) + + */ + + // TODO: Have buffer local to this function, so we know it's the + // correct length? + + int bufferOffset = 0; + + int offset = *p_offset; + + // Skip root or other separator + if (path[offset] == '/') { + offset++; + } + + // Copy the next next path segment + while (bufferOffset < MAX_COMPONENT_LEN + && (path[offset] != '/') + && (path[offset] != '\0')) { + buffer[bufferOffset++] = path[offset++]; + } + + buffer[bufferOffset] = '\0'; + + // Skip trailing separator so we can determine if this + // is the last component in the path or not. + if (path[offset] == '/') { + offset++; + } + + *p_offset = offset; + + return (path[offset] != '\0'); +} + + + +boolean walkPath(char *filepath, SdFile& parentDir, + boolean (*callback)(SdFile& parentDir, + char *filePathComponent, + boolean isLastComponent, + void *object), + void *object = NULL) { + /* + + When given a file path (and parent directory--normally root), + this function traverses the directories in the path and at each + level calls the supplied callback function while also providing + the supplied object for context if required. + + e.g. given the path '/foo/bar/baz' + the callback would be called at the equivalent of + '/foo', '/foo/bar' and '/foo/bar/baz'. + + The implementation swaps between two different directory/file + handles as it traverses the directories and does not use recursion + in an attempt to use memory efficiently. + + If a callback wishes to stop the directory traversal it should + return false--in this case the function will stop the traversal, + tidy up and return false. + + If a directory path doesn't exist at some point this function will + also return false and not subsequently call the callback. + + If a directory path specified is complete, valid and the callback + did not indicate the traversal should be interrupted then this + function will return true. + + */ + + + SdFile subfile1; + SdFile subfile2; + + char buffer[PATH_COMPONENT_BUFFER_LEN]; + + unsigned int offset = 0; + + SdFile *p_parent; + SdFile *p_child; + + SdFile *p_tmp_sdfile; + + p_child = &subfile1; + + p_parent = &parentDir; + + while (true) { + + boolean moreComponents = getNextPathComponent(filepath, &offset, buffer); + + boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object); + + if (!shouldContinue) { + // TODO: Don't repeat this code? + // If it's one we've created then we + // don't need the parent handle anymore. + if (p_parent != &parentDir) { + (*p_parent).close(); + } + return false; + } + + if (!moreComponents) { + break; + } + + boolean exists = (*p_child).open(*p_parent, buffer, O_RDONLY); + + // If it's one we've created then we + // don't need the parent handle anymore. + if (p_parent != &parentDir) { + (*p_parent).close(); + } + + // Handle case when it doesn't exist and we can't continue... + if (exists) { + // We alternate between two file handles as we go down + // the path. + if (p_parent == &parentDir) { + p_parent = &subfile2; + } + + p_tmp_sdfile = p_parent; + p_parent = p_child; + p_child = p_tmp_sdfile; + } else { + return false; + } + } + + if (p_parent != &parentDir) { + (*p_parent).close(); // TODO: Return/ handle different? + } + + return true; +} + + + +/* + + The callbacks used to implement various functionality follow. + + Each callback is supplied with a parent directory handle, + character string with the name of the current file path component, + a flag indicating if this component is the last in the path and + a pointer to an arbitrary object used for context. + + */ + +boolean callback_pathExists(SdFile& parentDir, char *filePathComponent, + boolean isLastComponent, void *object) { + /* + + Callback used to determine if a file/directory exists in parent + directory. + + Returns true if file path exists. + + */ + SdFile child; + + boolean exists = child.open(parentDir, filePathComponent, O_RDONLY); + + if (exists) { + child.close(); + } + + return exists; +} + + + +boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent, + boolean isLastComponent, void *object) { + /* + + Callback used to create a directory in the parent directory if + it does not already exist. + + Returns true if a directory was created or it already existed. + + */ + boolean result = false; + SdFile child; + + result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object); + if (!result) { + result = child.makeDir(parentDir, filePathComponent); + } + + return result; +} + + + /* + +boolean callback_openPath(SdFile& parentDir, char *filePathComponent, + boolean isLastComponent, void *object) { + + Callback used to open a file specified by a filepath that may + specify one or more directories above it. + + Expects the context object to be an instance of `SDClass` and + will use the `file` property of the instance to open the requested + file/directory with the associated file open mode property. + + Always returns true if the directory traversal hasn't reached the + bottom of the directory heirarchy. + + Returns false once the file has been opened--to prevent the traversal + from descending further. (This may be unnecessary.) + + if (isLastComponent) { + SDClass *p_SD = static_cast<SDClass*>(object); + p_SD->file.open(parentDir, filePathComponent, p_SD->fileOpenMode); + if (p_SD->fileOpenMode == FILE_WRITE) { + p_SD->file.seekSet(p_SD->file.fileSize()); + } + // TODO: Return file open result? + return false; + } + return true; +} + */ + + + +boolean callback_remove(SdFile& parentDir, char *filePathComponent, + boolean isLastComponent, void *object) { + if (isLastComponent) { + return SdFile::remove(parentDir, filePathComponent); + } + return true; +} + +boolean callback_rmdir(SdFile& parentDir, char *filePathComponent, + boolean isLastComponent, void *object) { + if (isLastComponent) { + SdFile f; + if (!f.open(parentDir, filePathComponent, O_READ)) return false; + return f.rmDir(); + } + return true; +} + + + +/* Implementation of class used to create `SDCard` object. */ + + + +boolean SDClass::begin(uint8_t csPin) { + /* + + Performs the initialisation required by the sdfatlib library. + + Return true if initialization succeeds, false otherwise. + + */ + return card.init(SPI_HALF_SPEED, csPin) && + volume.init(card) && + root.openRoot(volume); +} + + + +// this little helper is used to traverse paths +SdFile SDClass::getParentDir(const char *filepath, int *index) { + // get parent directory + SdFile d1 = root; // start with the mostparent, root! + SdFile d2; + + // we'll use the pointers to swap between the two objects + SdFile *parent = &d1; + SdFile *subdir = &d2; + + const char *origpath = filepath; + + while (strchr(filepath, '/')) { + + // get rid of leading /'s + if (filepath[0] == '/') { + filepath++; + continue; + } + + if (! strchr(filepath, '/')) { + // it was in the root directory, so leave now + break; + } + + // extract just the name of the next subdirectory + uint8_t idx = strchr(filepath, '/') - filepath; + if (idx > 12) + idx = 12; // dont let them specify long names + char subdirname[13]; + strncpy(subdirname, filepath, idx); + subdirname[idx] = 0; + + // close the subdir (we reuse them) if open + subdir->close(); + if (! subdir->open(parent, subdirname, O_READ)) { + // failed to open one of the subdirectories + return SdFile(); + } + // move forward to the next subdirectory + filepath += idx; + + // we reuse the objects, close it. + parent->close(); + + // swap the pointers + SdFile *t = parent; + parent = subdir; + subdir = t; + } + + *index = (int)(filepath - origpath); + // parent is now the parent diretory of the file! + return *parent; +} + + +File SDClass::open(const char *filepath, uint8_t mode) { + /* + + Open the supplied file path for reading or writing. + + The file content can be accessed via the `file` property of + the `SDClass` object--this property is currently + a standard `SdFile` object from `sdfatlib`. + + Defaults to read only. + + If `write` is true, default action (when `append` is true) is to + append data to the end of the file. + + If `append` is false then the file will be truncated first. + + If the file does not exist and it is opened for writing the file + will be created. + + An attempt to open a file for reading that does not exist is an + error. + + */ + + int pathidx; + + // do the interative search + SdFile parentdir = getParentDir(filepath, &pathidx); + // no more subdirs! + + filepath += pathidx; + + if (! filepath[0]) { + // it was the directory itself! + return File(parentdir, "/"); + } + + // Open the file itself + SdFile file; + + // failed to open a subdir! + if (!parentdir.isOpen()) + return File(); + + // there is a special case for the Root directory since its a static dir + if (parentdir.isRoot()) { + if ( ! file.open(SD.root, filepath, mode)) { + // failed to open the file :( + return File(); + } + // dont close the root! + } else { + if ( ! file.open(parentdir, filepath, mode)) { + return File(); + } + // close the parent + parentdir.close(); + } + + if (mode & (O_APPEND | O_WRITE)) + file.seekSet(file.fileSize()); + return File(file, filepath); +} + + +/* +File SDClass::open(char *filepath, uint8_t mode) { + // + + Open the supplied file path for reading or writing. + + The file content can be accessed via the `file` property of + the `SDClass` object--this property is currently + a standard `SdFile` object from `sdfatlib`. + + Defaults to read only. + + If `write` is true, default action (when `append` is true) is to + append data to the end of the file. + + If `append` is false then the file will be truncated first. + + If the file does not exist and it is opened for writing the file + will be created. + + An attempt to open a file for reading that does not exist is an + error. + + // + + // TODO: Allow for read&write? (Possibly not, as it requires seek.) + + fileOpenMode = mode; + walkPath(filepath, root, callback_openPath, this); + + return File(); + +} +*/ + + +//boolean SDClass::close() { +// /* +// +// Closes the file opened by the `open` method. +// +// */ +// file.close(); +//} + + +boolean SDClass::exists(char *filepath) { + /* + + Returns true if the supplied file path exists. + + */ + return walkPath(filepath, root, callback_pathExists); +} + + +//boolean SDClass::exists(char *filepath, SdFile& parentDir) { +// /* +// +// Returns true if the supplied file path rooted at `parentDir` +// exists. +// +// */ +// return walkPath(filepath, parentDir, callback_pathExists); +//} + + +boolean SDClass::mkdir(char *filepath) { + /* + + Makes a single directory or a heirarchy of directories. + + A rough equivalent to `mkdir -p`. + + */ + return walkPath(filepath, root, callback_makeDirPath); +} + +boolean SDClass::rmdir(char *filepath) { + /* + + Makes a single directory or a heirarchy of directories. + + A rough equivalent to `mkdir -p`. + + */ + return walkPath(filepath, root, callback_rmdir); +} + +boolean SDClass::remove(char *filepath) { + return walkPath(filepath, root, callback_remove); +} + + +// allows you to recurse into a directory +File File::openNextFile(uint8_t mode) { + dir_t p; + + //Serial.print("\t\treading dir..."); + while (_file->readDir(&p) > 0) { + + // done if past last used entry + if (p.name[0] == DIR_NAME_FREE) { + //Serial.println("end"); + return File(); + } + + // skip deleted entry and entries for . and .. + if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') { + //Serial.println("dots"); + continue; + } + + // only list subdirectories and files + if (!DIR_IS_FILE_OR_SUBDIR(&p)) { + //Serial.println("notafile"); + continue; + } + + // print file name with possible blank fill + SdFile f; + char name[13]; + _file->dirName(p, name); + //Serial.print("try to open file "); + //Serial.println(name); + + if (f.open(_file, name, mode)) { + //Serial.println("OK!"); + return File(f, name); + } else { + //Serial.println("ugh"); + return File(); + } + } + + //Serial.println("nothing"); + return File(); +} + +void File::rewindDirectory(void) { + if (isDirectory()) + _file->rewind(); +} + +SDClass SD; |