]> git.piffa.net Git - arduino/blob - books/pdummies/Libraries/SD/SD.cpp
first commit
[arduino] / books / pdummies / Libraries / SD / SD.cpp
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, int8_t mosi, int8_t miso, int8_t sck) {
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, mosi, miso, sck) &&
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(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   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(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 void SDClass::enableCRC(boolean mode) {
566   card.enableCRC(mode);
567 }
568
569
570 // allows you to recurse into a directory
571 File File::openNextFile(uint8_t mode) {
572   dir_t p;
573
574   //Serial.print("\t\treading dir...");
575   while (_file->readDir(&p) > 0) {
576
577     // done if past last used entry
578     if (p.name[0] == DIR_NAME_FREE) {
579       //Serial.println("end");
580       return File();
581     }
582
583     // skip deleted entry and entries for . and  ..
584     if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') {
585       //Serial.println("dots");
586       continue;
587     }
588
589     // only list subdirectories and files
590     if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
591       //Serial.println("notafile");
592       continue;
593     }
594
595     // print file name with possible blank fill
596     SdFile f;
597     char name[13];
598     _file->dirName(p, name);
599     //Serial.print("try to open file ");
600     //Serial.println(name);
601
602     if (f.open(_file, name, mode)) {
603       //Serial.println("OK!");
604       return File(f, name);    
605     } else {
606       //Serial.println("ugh");
607       return File();
608     }
609   }
610
611   //Serial.println("nothing");
612   return File();
613 }
614
615 void File::rewindDirectory(void) {  
616   if (isDirectory())
617     _file->rewind();
618 }
619
620 SDClass SD;