root / arduino-1.0 / libraries / SD / SD.cpp @ 58d82c77
History | View | Annotate | Download (14.6 KB)
1 |
/*
|
---|---|
2 |
|
3 |
SD - a slightly more friendly wrapper for sdfatlib
|
4 |
|
5 |
This library aims to expose a subset of SD card functionality
|
6 |
in the form of a higher level "wrapper" object.
|
7 |
|
8 |
License: GNU General Public License V3
|
9 |
(Because sdfatlib is licensed with this.)
|
10 |
|
11 |
(C) Copyright 2010 SparkFun Electronics
|
12 |
|
13 |
|
14 |
This library provides four key benefits:
|
15 |
|
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`.
|
19 |
|
20 |
* Boilerplate initialisation code is contained in one method named
|
21 |
`begin` and no further objects need to be created in order to access
|
22 |
the SD card.
|
23 |
|
24 |
* Calls to `open` can supply a full path name including parent
|
25 |
directories which simplifies interacting with files in subdirectories.
|
26 |
|
27 |
* Utility methods are provided to determine whether a file exists
|
28 |
and to create a directory heirarchy.
|
29 |
|
30 |
|
31 |
Note however that not all functionality provided by the underlying
|
32 |
sdfatlib library is exposed.
|
33 |
|
34 |
*/
|
35 |
|
36 |
/*
|
37 |
|
38 |
Implementation Notes
|
39 |
|
40 |
In order to handle multi-directory path traversal, functionality that
|
41 |
requires this ability is implemented as callback functions.
|
42 |
|
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.
|
46 |
|
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
|
49 |
level (e.g. open).
|
50 |
|
51 |
*/
|
52 |
|
53 |
#include "SD.h" |
54 |
|
55 |
// Used by `getNextPathComponent`
|
56 |
#define MAX_COMPONENT_LEN 12 // What is max length? |
57 |
#define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1 |
58 |
|
59 |
bool getNextPathComponent(char *path, unsigned int *p_offset, |
60 |
char *buffer) {
|
61 |
/*
|
62 |
|
63 |
Parse individual path components from a path.
|
64 |
|
65 |
e.g. after repeated calls '/foo/bar/baz' will be split
|
66 |
into 'foo', 'bar', 'baz'.
|
67 |
|
68 |
This is similar to `strtok()` but copies the component into the
|
69 |
supplied buffer rather than modifying the original string.
|
70 |
|
71 |
|
72 |
`buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size.
|
73 |
|
74 |
`p_offset` needs to point to an integer of the offset at
|
75 |
which the previous path component finished.
|
76 |
|
77 |
Returns `true` if more components remain.
|
78 |
|
79 |
Returns `false` if this is the last component.
|
80 |
(This means path ended with 'foo' or 'foo/'.)
|
81 |
|
82 |
*/
|
83 |
|
84 |
// TODO: Have buffer local to this function, so we know it's the
|
85 |
// correct length?
|
86 |
|
87 |
int bufferOffset = 0; |
88 |
|
89 |
int offset = *p_offset;
|
90 |
|
91 |
// Skip root or other separator
|
92 |
if (path[offset] == '/') { |
93 |
offset++; |
94 |
} |
95 |
|
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++]; |
101 |
} |
102 |
|
103 |
buffer[bufferOffset] = '\0';
|
104 |
|
105 |
// Skip trailing separator so we can determine if this
|
106 |
// is the last component in the path or not.
|
107 |
if (path[offset] == '/') { |
108 |
offset++; |
109 |
} |
110 |
|
111 |
*p_offset = offset; |
112 |
|
113 |
return (path[offset] != '\0'); |
114 |
} |
115 |
|
116 |
|
117 |
|
118 |
boolean walkPath(char *filepath, SdFile& parentDir,
|
119 |
boolean (*callback)(SdFile& parentDir, |
120 |
char *filePathComponent,
|
121 |
boolean isLastComponent, |
122 |
void *object),
|
123 |
void *object = NULL) { |
124 |
/*
|
125 |
|
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.
|
130 |
|
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'.
|
134 |
|
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.
|
138 |
|
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.
|
142 |
|
143 |
If a directory path doesn't exist at some point this function will
|
144 |
also return false and not subsequently call the callback.
|
145 |
|
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.
|
149 |
|
150 |
*/
|
151 |
|
152 |
|
153 |
SdFile subfile1; |
154 |
SdFile subfile2; |
155 |
|
156 |
char buffer[PATH_COMPONENT_BUFFER_LEN];
|
157 |
|
158 |
unsigned int offset = 0; |
159 |
|
160 |
SdFile *p_parent; |
161 |
SdFile *p_child; |
162 |
|
163 |
SdFile *p_tmp_sdfile; |
164 |
|
165 |
p_child = &subfile1; |
166 |
|
167 |
p_parent = &parentDir; |
168 |
|
169 |
while (true) { |
170 |
|
171 |
boolean moreComponents = getNextPathComponent(filepath, &offset, buffer); |
172 |
|
173 |
boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object); |
174 |
|
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) {
|
180 |
(*p_parent).close(); |
181 |
} |
182 |
return false; |
183 |
} |
184 |
|
185 |
if (!moreComponents) {
|
186 |
break;
|
187 |
} |
188 |
|
189 |
boolean exists = (*p_child).open(*p_parent, buffer, O_RDONLY); |
190 |
|
191 |
// If it's one we've created then we
|
192 |
// don't need the parent handle anymore.
|
193 |
if (p_parent != &parentDir) {
|
194 |
(*p_parent).close(); |
195 |
} |
196 |
|
197 |
// Handle case when it doesn't exist and we can't continue...
|
198 |
if (exists) {
|
199 |
// We alternate between two file handles as we go down
|
200 |
// the path.
|
201 |
if (p_parent == &parentDir) {
|
202 |
p_parent = &subfile2; |
203 |
} |
204 |
|
205 |
p_tmp_sdfile = p_parent; |
206 |
p_parent = p_child; |
207 |
p_child = p_tmp_sdfile; |
208 |
} else {
|
209 |
return false; |
210 |
} |
211 |
} |
212 |
|
213 |
if (p_parent != &parentDir) {
|
214 |
(*p_parent).close(); // TODO: Return/ handle different?
|
215 |
} |
216 |
|
217 |
return true; |
218 |
} |
219 |
|
220 |
|
221 |
|
222 |
/*
|
223 |
|
224 |
The callbacks used to implement various functionality follow.
|
225 |
|
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.
|
230 |
|
231 |
*/
|
232 |
|
233 |
boolean callback_pathExists(SdFile& parentDir, char *filePathComponent,
|
234 |
boolean isLastComponent, void *object) {
|
235 |
/*
|
236 |
|
237 |
Callback used to determine if a file/directory exists in parent
|
238 |
directory.
|
239 |
|
240 |
Returns true if file path exists.
|
241 |
|
242 |
*/
|
243 |
SdFile child; |
244 |
|
245 |
boolean exists = child.open(parentDir, filePathComponent, O_RDONLY); |
246 |
|
247 |
if (exists) {
|
248 |
child.close(); |
249 |
} |
250 |
|
251 |
return exists;
|
252 |
} |
253 |
|
254 |
|
255 |
|
256 |
boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent,
|
257 |
boolean isLastComponent, void *object) {
|
258 |
/*
|
259 |
|
260 |
Callback used to create a directory in the parent directory if
|
261 |
it does not already exist.
|
262 |
|
263 |
Returns true if a directory was created or it already existed.
|
264 |
|
265 |
*/
|
266 |
boolean result = false;
|
267 |
SdFile child; |
268 |
|
269 |
result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object); |
270 |
if (!result) {
|
271 |
result = child.makeDir(parentDir, filePathComponent); |
272 |
} |
273 |
|
274 |
return result;
|
275 |
} |
276 |
|
277 |
|
278 |
/*
|
279 |
|
280 |
boolean callback_openPath(SdFile& parentDir, char *filePathComponent,
|
281 |
boolean isLastComponent, void *object) {
|
282 |
|
283 |
Callback used to open a file specified by a filepath that may
|
284 |
specify one or more directories above it.
|
285 |
|
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.
|
289 |
|
290 |
Always returns true if the directory traversal hasn't reached the
|
291 |
bottom of the directory heirarchy.
|
292 |
|
293 |
Returns false once the file has been opened--to prevent the traversal
|
294 |
from descending further. (This may be unnecessary.)
|
295 |
|
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());
|
301 |
}
|
302 |
// TODO: Return file open result?
|
303 |
return false;
|
304 |
}
|
305 |
return true;
|
306 |
}
|
307 |
*/
|
308 |
|
309 |
|
310 |
|
311 |
boolean callback_remove(SdFile& parentDir, char *filePathComponent,
|
312 |
boolean isLastComponent, void *object) {
|
313 |
if (isLastComponent) {
|
314 |
return SdFile::remove(parentDir, filePathComponent);
|
315 |
} |
316 |
return true; |
317 |
} |
318 |
|
319 |
boolean callback_rmdir(SdFile& parentDir, char *filePathComponent,
|
320 |
boolean isLastComponent, void *object) {
|
321 |
if (isLastComponent) {
|
322 |
SdFile f; |
323 |
if (!f.open(parentDir, filePathComponent, O_READ)) return false; |
324 |
return f.rmDir();
|
325 |
} |
326 |
return true; |
327 |
} |
328 |
|
329 |
|
330 |
|
331 |
/* Implementation of class used to create `SDCard` object. */
|
332 |
|
333 |
|
334 |
|
335 |
boolean SDClass::begin(uint8_t csPin) { |
336 |
/*
|
337 |
|
338 |
Performs the initialisation required by the sdfatlib library.
|
339 |
|
340 |
Return true if initialization succeeds, false otherwise.
|
341 |
|
342 |
*/
|
343 |
return card.init(SPI_HALF_SPEED, csPin) &&
|
344 |
volume.init(card) && |
345 |
root.openRoot(volume); |
346 |
} |
347 |
|
348 |
|
349 |
|
350 |
// this little helper is used to traverse paths
|
351 |
SdFile SDClass::getParentDir(const char *filepath, int *index) { |
352 |
// get parent directory
|
353 |
SdFile d1 = root; // start with the mostparent, root!
|
354 |
SdFile d2; |
355 |
|
356 |
// we'll use the pointers to swap between the two objects
|
357 |
SdFile *parent = &d1; |
358 |
SdFile *subdir = &d2; |
359 |
|
360 |
const char *origpath = filepath; |
361 |
|
362 |
while (strchr(filepath, '/')) { |
363 |
|
364 |
// get rid of leading /'s
|
365 |
if (filepath[0] == '/') { |
366 |
filepath++; |
367 |
continue;
|
368 |
} |
369 |
|
370 |
if (! strchr(filepath, '/')) { |
371 |
// it was in the root directory, so leave now
|
372 |
break;
|
373 |
} |
374 |
|
375 |
// extract just the name of the next subdirectory
|
376 |
uint8_t idx = strchr(filepath, '/') - filepath;
|
377 |
if (idx > 12) |
378 |
idx = 12; // dont let them specify long names |
379 |
char subdirname[13]; |
380 |
strncpy(subdirname, filepath, idx); |
381 |
subdirname[idx] = 0;
|
382 |
|
383 |
// close the subdir (we reuse them) if open
|
384 |
subdir->close(); |
385 |
if (! subdir->open(parent, subdirname, O_READ)) {
|
386 |
// failed to open one of the subdirectories
|
387 |
return SdFile();
|
388 |
} |
389 |
// move forward to the next subdirectory
|
390 |
filepath += idx; |
391 |
|
392 |
// we reuse the objects, close it.
|
393 |
parent->close(); |
394 |
|
395 |
// swap the pointers
|
396 |
SdFile *t = parent; |
397 |
parent = subdir; |
398 |
subdir = t; |
399 |
} |
400 |
|
401 |
*index = (int)(filepath - origpath);
|
402 |
// parent is now the parent diretory of the file!
|
403 |
return *parent;
|
404 |
} |
405 |
|
406 |
|
407 |
File SDClass::open(const char *filepath, uint8_t mode) { |
408 |
/*
|
409 |
|
410 |
Open the supplied file path for reading or writing.
|
411 |
|
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`.
|
415 |
|
416 |
Defaults to read only.
|
417 |
|
418 |
If `write` is true, default action (when `append` is true) is to
|
419 |
append data to the end of the file.
|
420 |
|
421 |
If `append` is false then the file will be truncated first.
|
422 |
|
423 |
If the file does not exist and it is opened for writing the file
|
424 |
will be created.
|
425 |
|
426 |
An attempt to open a file for reading that does not exist is an
|
427 |
error.
|
428 |
|
429 |
*/
|
430 |
|
431 |
int pathidx;
|
432 |
|
433 |
// do the interative search
|
434 |
SdFile parentdir = getParentDir(filepath, &pathidx); |
435 |
// no more subdirs!
|
436 |
|
437 |
filepath += pathidx; |
438 |
|
439 |
if (! filepath[0]) { |
440 |
// it was the directory itself!
|
441 |
return File(parentdir, "/"); |
442 |
} |
443 |
|
444 |
// Open the file itself
|
445 |
SdFile file; |
446 |
|
447 |
// failed to open a subdir!
|
448 |
if (!parentdir.isOpen())
|
449 |
return File();
|
450 |
|
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 :(
|
455 |
return File();
|
456 |
} |
457 |
// dont close the root!
|
458 |
} else {
|
459 |
if ( ! file.open(parentdir, filepath, mode)) {
|
460 |
return File();
|
461 |
} |
462 |
// close the parent
|
463 |
parentdir.close(); |
464 |
} |
465 |
|
466 |
if (mode & (O_APPEND | O_WRITE))
|
467 |
file.seekSet(file.fileSize()); |
468 |
return File(file, filepath);
|
469 |
} |
470 |
|
471 |
|
472 |
/*
|
473 |
File SDClass::open(char *filepath, uint8_t mode) {
|
474 |
//
|
475 |
|
476 |
Open the supplied file path for reading or writing.
|
477 |
|
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`.
|
481 |
|
482 |
Defaults to read only.
|
483 |
|
484 |
If `write` is true, default action (when `append` is true) is to
|
485 |
append data to the end of the file.
|
486 |
|
487 |
If `append` is false then the file will be truncated first.
|
488 |
|
489 |
If the file does not exist and it is opened for writing the file
|
490 |
will be created.
|
491 |
|
492 |
An attempt to open a file for reading that does not exist is an
|
493 |
error.
|
494 |
|
495 |
//
|
496 |
|
497 |
// TODO: Allow for read&write? (Possibly not, as it requires seek.)
|
498 |
|
499 |
fileOpenMode = mode;
|
500 |
walkPath(filepath, root, callback_openPath, this);
|
501 |
|
502 |
return File();
|
503 |
|
504 |
}
|
505 |
*/
|
506 |
|
507 |
|
508 |
//boolean SDClass::close() {
|
509 |
// /*
|
510 |
//
|
511 |
// Closes the file opened by the `open` method.
|
512 |
//
|
513 |
// */
|
514 |
// file.close();
|
515 |
//}
|
516 |
|
517 |
|
518 |
boolean SDClass::exists(char *filepath) {
|
519 |
/*
|
520 |
|
521 |
Returns true if the supplied file path exists.
|
522 |
|
523 |
*/
|
524 |
return walkPath(filepath, root, callback_pathExists);
|
525 |
} |
526 |
|
527 |
|
528 |
//boolean SDClass::exists(char *filepath, SdFile& parentDir) {
|
529 |
// /*
|
530 |
//
|
531 |
// Returns true if the supplied file path rooted at `parentDir`
|
532 |
// exists.
|
533 |
//
|
534 |
// */
|
535 |
// return walkPath(filepath, parentDir, callback_pathExists);
|
536 |
//}
|
537 |
|
538 |
|
539 |
boolean SDClass::mkdir(char *filepath) {
|
540 |
/*
|
541 |
|
542 |
Makes a single directory or a heirarchy of directories.
|
543 |
|
544 |
A rough equivalent to `mkdir -p`.
|
545 |
|
546 |
*/
|
547 |
return walkPath(filepath, root, callback_makeDirPath);
|
548 |
} |
549 |
|
550 |
boolean SDClass::rmdir(char *filepath) {
|
551 |
/*
|
552 |
|
553 |
Makes a single directory or a heirarchy of directories.
|
554 |
|
555 |
A rough equivalent to `mkdir -p`.
|
556 |
|
557 |
*/
|
558 |
return walkPath(filepath, root, callback_rmdir);
|
559 |
} |
560 |
|
561 |
boolean SDClass::remove(char *filepath) {
|
562 |
return walkPath(filepath, root, callback_remove);
|
563 |
} |
564 |
|
565 |
|
566 |
// allows you to recurse into a directory
|
567 |
File File::openNextFile(uint8_t mode) { |
568 |
dir_t p; |
569 |
|
570 |
//Serial.print("\t\treading dir...");
|
571 |
while (_file->readDir(&p) > 0) { |
572 |
|
573 |
// done if past last used entry
|
574 |
if (p.name[0] == DIR_NAME_FREE) { |
575 |
//Serial.println("end");
|
576 |
return File();
|
577 |
} |
578 |
|
579 |
// skip deleted entry and entries for . and ..
|
580 |
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') { |
581 |
//Serial.println("dots");
|
582 |
continue;
|
583 |
} |
584 |
|
585 |
// only list subdirectories and files
|
586 |
if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
|
587 |
//Serial.println("notafile");
|
588 |
continue;
|
589 |
} |
590 |
|
591 |
// print file name with possible blank fill
|
592 |
SdFile f; |
593 |
char name[13]; |
594 |
_file->dirName(p, name); |
595 |
//Serial.print("try to open file ");
|
596 |
//Serial.println(name);
|
597 |
|
598 |
if (f.open(_file, name, mode)) {
|
599 |
//Serial.println("OK!");
|
600 |
return File(f, name);
|
601 |
} else {
|
602 |
//Serial.println("ugh");
|
603 |
return File();
|
604 |
} |
605 |
} |
606 |
|
607 |
//Serial.println("nothing");
|
608 |
return File();
|
609 |
} |
610 |
|
611 |
void File::rewindDirectory(void) { |
612 |
if (isDirectory())
|
613 |
_file->rewind(); |
614 |
} |
615 |
|
616 |
SDClass SD; |