2017-02-20 18:55:16 +00:00
|
|
|
#include <Arduino.h>
|
|
|
|
#include <wchar.h>
|
|
|
|
#include "ff.h" // File System
|
|
|
|
#include "teensy-filemanager.h"
|
|
|
|
#include <string.h> // strcpy
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME: globals are yucky.
|
|
|
|
DIR dir;
|
|
|
|
FILINFO fno;
|
|
|
|
FIL fil;
|
|
|
|
|
2017-12-29 20:35:47 +00:00
|
|
|
int8_t rawFd = -1;
|
|
|
|
FIL rawFil;
|
|
|
|
|
2017-02-20 18:55:16 +00:00
|
|
|
static TCHAR *char2tchar( const char *charString, int nn, TCHAR *output)
|
|
|
|
{
|
|
|
|
int ii;
|
|
|
|
for (ii=0; ii<nn; ii++) {
|
|
|
|
output[ii] = (TCHAR)charString[ii];
|
|
|
|
if (!charString[ii])
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char * tchar2char( const TCHAR * tcharString, int nn, char * charString)
|
|
|
|
{ int ii;
|
|
|
|
for(ii = 0; ii<nn; ii++)
|
|
|
|
{ charString[ii] = (char)tcharString[ii];
|
|
|
|
if(!charString[ii]) break;
|
|
|
|
}
|
|
|
|
return charString;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TeensyFileManager::TeensyFileManager()
|
|
|
|
{
|
|
|
|
numCached = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TeensyFileManager::~TeensyFileManager()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t TeensyFileManager::openFile(const char *name)
|
|
|
|
{
|
2017-12-29 20:35:47 +00:00
|
|
|
// invalidate the raw file cache
|
|
|
|
if (rawFd != -1) {
|
|
|
|
f_close(&rawFil);
|
|
|
|
rawFd = -1;
|
|
|
|
}
|
|
|
|
|
2017-02-20 18:55:16 +00:00
|
|
|
// See if there's a hole to re-use...
|
|
|
|
for (int i=0; i<numCached; i++) {
|
|
|
|
if (cachedNames[i][0] == '\0') {
|
|
|
|
strncpy(cachedNames[i], name, MAXPATH-1);
|
|
|
|
cachedNames[i][MAXPATH-1] = '\0'; // safety: ensure string terminator
|
|
|
|
fileSeekPositions[i] = 0;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for too many open files
|
|
|
|
if (numCached >= MAXFILES)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
// No, so we'll add it to the end
|
|
|
|
strncpy(cachedNames[numCached], name, MAXPATH-1);
|
|
|
|
cachedNames[numCached][MAXPATH-1] = '\0'; // safety: ensure string terminator
|
|
|
|
fileSeekPositions[numCached] = 0;
|
|
|
|
|
|
|
|
numCached++;
|
|
|
|
return numCached-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TeensyFileManager::closeFile(int8_t fd)
|
|
|
|
{
|
2017-12-29 20:35:47 +00:00
|
|
|
// invalidate the raw file cache
|
|
|
|
if (rawFd != -1) {
|
|
|
|
f_close(&rawFil);
|
|
|
|
rawFd = -1;
|
|
|
|
}
|
|
|
|
|
2017-02-20 18:55:16 +00:00
|
|
|
// invalid fd provided?
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// clear the name
|
|
|
|
cachedNames[fd][0] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *TeensyFileManager::fileName(int8_t fd)
|
|
|
|
{
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return cachedNames[fd];
|
|
|
|
}
|
|
|
|
|
|
|
|
// suffix may be comma-separated
|
|
|
|
int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint16_t maxlen)
|
|
|
|
{
|
|
|
|
// ... open, read, save next name, close, return name. Horribly
|
|
|
|
// inefficient but hopefully won't break the sd layer. And if it
|
|
|
|
// works then we can make this more efficient later.
|
|
|
|
|
|
|
|
// First entry is always "../"
|
|
|
|
if (startIdx == 0) {
|
|
|
|
strcpy(outputFN, "../");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t idxCount = 1;
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(where, MAXPATH, buf);
|
|
|
|
buf[strlen(where)-1] = '\0'; // this library doesn't want trailing slashes
|
|
|
|
FRESULT rc = f_opendir(&dir, buf);
|
|
|
|
if (rc) {
|
|
|
|
Serial.printf("f_opendir '%s' failed: %d\n", where, rc);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
rc = f_readdir(&dir, &fno);
|
|
|
|
if (rc || !fno.fname[0]) {
|
|
|
|
// No more - all done.
|
|
|
|
f_closedir(&dir);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fno.fname[0] == '.' || fno.fname[0] == '_' || fno.fname[0] == '~') {
|
|
|
|
// skip MAC fork files and any that have been deleted :/
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip anything that has the wrong suffix
|
|
|
|
char fn[MAXPATH];
|
|
|
|
tchar2char(fno.fname, MAXPATH, fn);
|
|
|
|
if (suffix && !(fno.fattrib & AM_DIR) && strlen(fn) >= 3) {
|
|
|
|
const char *fsuff = &fn[strlen(fn)-3];
|
|
|
|
if (strstr(suffix, ",")) {
|
|
|
|
// multiple suffixes to check
|
|
|
|
bool matchesAny = false;
|
|
|
|
const char *p = suffix;
|
|
|
|
while (p && strlen(p)) {
|
|
|
|
if (!strncasecmp(fsuff, p, 3)) {
|
|
|
|
matchesAny = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p = strstr(p, ",")+1;
|
|
|
|
}
|
2017-12-29 20:35:47 +00:00
|
|
|
if (!matchesAny)
|
2017-02-20 18:55:16 +00:00
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
// one suffix to check
|
|
|
|
if (strcasecmp(fsuff, suffix))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (idxCount == startIdx) {
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
|
|
strcat(fn, "/");
|
|
|
|
}
|
|
|
|
strncpy(outputFN, fn, maxlen);
|
|
|
|
f_closedir(&dir);
|
|
|
|
return idxCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
idxCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* NOTREACHED */
|
|
|
|
}
|
|
|
|
|
|
|
|
void TeensyFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
|
|
|
|
{
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fileSeekPositions[fd] = block * (isNib ? 416 : 256);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TeensyFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
|
|
|
|
{
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// open, seek, read, close.
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(cachedNames[fd], MAXPATH, buf);
|
|
|
|
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
|
|
|
|
if (rc) {
|
|
|
|
Serial.println("failed to open");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = f_lseek(&fil, fileSeekPositions[fd]);
|
|
|
|
if (rc) {
|
|
|
|
Serial.println("readTrack: seek failed");
|
|
|
|
f_close(&fil);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT v;
|
|
|
|
f_read(&fil, toWhere, isNib ? 0x1a00 : (256 * 16), &v);
|
|
|
|
f_close(&fil);
|
|
|
|
return (v == (isNib ? 0x1a00 : (256 * 16)));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TeensyFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
|
|
|
|
{
|
|
|
|
// open, seek, read, close.
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// open, seek, read, close.
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(cachedNames[fd], MAXPATH, buf);
|
|
|
|
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
|
|
|
|
if (rc) {
|
|
|
|
Serial.println("failed to open");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = f_lseek(&fil, fileSeekPositions[fd]);
|
|
|
|
if (rc) {
|
|
|
|
Serial.println("readBlock: seek failed");
|
|
|
|
f_close(&fil);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
UINT v;
|
|
|
|
f_read(&fil, toWhere, isNib ? 416 : 256, &v);
|
|
|
|
f_close(&fil);
|
|
|
|
return (v == (isNib ? 416 : 256));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TeensyFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
|
|
|
|
{
|
|
|
|
// open, seek, write, close.
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// can't write just a single block of a nibblized track
|
|
|
|
if (isNib)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// open, seek, write, close.
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(cachedNames[fd], MAXPATH, buf);
|
|
|
|
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE);
|
|
|
|
rc = f_lseek(&fil, fileSeekPositions[fd]);
|
|
|
|
UINT v;
|
|
|
|
f_write(&fil, fromWhere, 256, &v);
|
|
|
|
f_close(&fil);
|
|
|
|
return (v == 256);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TeensyFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
|
|
|
|
{
|
|
|
|
// open, seek, write, close.
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// open, seek, write, close.
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(cachedNames[fd], MAXPATH, buf);
|
|
|
|
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE);
|
|
|
|
rc = f_lseek(&fil, fileSeekPositions[fd]);
|
|
|
|
UINT v;
|
|
|
|
f_write(&fil, fromWhere, isNib ? 0x1a00 : (256*16), &v);
|
|
|
|
f_close(&fil);
|
|
|
|
return (v == (isNib ? 0x1a00 : (256*16)));
|
|
|
|
}
|
|
|
|
|
2017-12-29 20:35:47 +00:00
|
|
|
bool TeensyFileManager::_prepCache(int8_t fd)
|
|
|
|
{
|
|
|
|
FRESULT rc;
|
|
|
|
|
|
|
|
if (rawFd == -1 ||
|
|
|
|
rawFd != fd) {
|
|
|
|
|
|
|
|
// Not our cached file, or we have no cached file
|
|
|
|
if (rawFd != -1) {
|
|
|
|
// Close the old one if we had one
|
|
|
|
Serial.println("closing old HD cache");
|
|
|
|
f_close(&rawFil);
|
|
|
|
rawFd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the new one
|
|
|
|
TCHAR buf[MAXPATH];
|
|
|
|
char2tchar(cachedNames[fd], MAXPATH, buf);
|
|
|
|
rc = f_open(&rawFil, (TCHAR*) buf, FA_READ|FA_WRITE);
|
|
|
|
if (rc) {
|
|
|
|
Serial.println("readByteAt: failed to open");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Serial.println("new cache file open");
|
|
|
|
rawFd = fd; // cache is live
|
|
|
|
}
|
|
|
|
|
|
|
|
return (!rc);
|
|
|
|
}
|
|
|
|
|
2017-12-29 19:08:49 +00:00
|
|
|
uint8_t TeensyFileManager::readByteAt(int8_t fd, uint32_t pos)
|
|
|
|
{
|
|
|
|
// open, seek, read, close.
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
2017-12-29 20:35:47 +00:00
|
|
|
FRESULT rc;
|
2017-12-29 19:08:49 +00:00
|
|
|
|
2017-12-29 20:35:47 +00:00
|
|
|
_prepCache(fd);
|
|
|
|
|
|
|
|
rc = f_lseek(&rawFil, pos);
|
2017-12-29 19:08:49 +00:00
|
|
|
if (rc) {
|
|
|
|
Serial.println("readByteAt: seek failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
uint8_t b;
|
|
|
|
UINT v;
|
2017-12-29 20:35:47 +00:00
|
|
|
f_read(&rawFil, &b, 1, &v);
|
|
|
|
|
|
|
|
// FIXME: check v == 1 & handle error
|
|
|
|
return b;
|
2017-12-29 19:08:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool TeensyFileManager::writeByteAt(int8_t fd, uint8_t v, uint32_t pos)
|
|
|
|
{
|
|
|
|
// open, seek, write, close.
|
|
|
|
if (fd < 0 || fd >= numCached)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (cachedNames[fd][0] == 0)
|
|
|
|
return false;
|
|
|
|
|
2017-12-29 20:35:47 +00:00
|
|
|
FRESULT rc;
|
|
|
|
|
|
|
|
_prepCache(fd);
|
|
|
|
|
|
|
|
rc = f_lseek(&rawFil, pos);
|
2017-12-29 19:08:49 +00:00
|
|
|
UINT ret;
|
2017-12-29 20:35:47 +00:00
|
|
|
f_write(&rawFil, &v, 1, &ret);
|
|
|
|
|
2017-12-29 19:08:49 +00:00
|
|
|
return (ret == 1);
|
|
|
|
}
|
|
|
|
|