|
- /*
-
- 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;
|