Project

General

Profile

Statistics
| Branch: | Revision:

root / arduino-1.0 / libraries / SD / SD.cpp @ 58d82c77

History | View | Annotate | Download (14.6 KB)

1 58d82c77 Tom Mullins
/*
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;