aboutsummaryrefslogtreecommitdiff
path: root/libraries/SD/SD.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/SD/SD.cpp')
-rw-r--r--libraries/SD/SD.cpp616
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;