#include #include #include "ff.h" // File System #include "teensy-filemanager.h" #include // strcpy // FIXME: globals are yucky. DIR dir; FILINFO fno; FIL fil; int8_t rawFd = -1; FIL rawFil; static TCHAR *char2tchar( const char *charString, int nn, TCHAR *output) { int ii; for (ii=0; ii= 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) { // invalidate the raw file cache if (rawFd != -1) { Serial.print("Invalidating raw file cache "); Serial.println(rawFd); f_close(&rawFil); rawFd = -1; } // 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; } if (!matchesAny) 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))); } 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 cache file"); f_close(&rawFil); rawFd = -1; } Serial.println("opening new cache file"); // Open the new one TCHAR buf[MAXPATH]; char2tchar(cachedNames[fd], MAXPATH, buf); rc = f_open(&rawFil, (TCHAR*) buf, FA_READ|FA_WRITE|FA_OPEN_ALWAYS); if (rc) { Serial.print("_prepCache: failed to open "); Serial.println(cachedNames[fd]); return false; } rawFd = fd; // cache is live Serial.print("New cache file is "); Serial.println(fd); } else { // Serial.println("reopning same cache"); } return (!rc); } 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; FRESULT rc; _prepCache(fd); rc = f_lseek(&rawFil, pos); if (rc) { Serial.println("readByteAt: seek failed"); return false; } uint8_t b; UINT v; f_read(&rawFil, &b, 1, &v); // FIXME: check v == 1 & handle error return b; } 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; FRESULT rc; _prepCache(fd); rc = f_lseek(&rawFil, pos); UINT ret; f_write(&rawFil, &v, 1, &ret); return (ret == 1); } uint8_t TeensyFileManager::readByte(int8_t fd) { // open, seek, read, close. if (fd < 0 || fd >= numCached) return false; if (cachedNames[fd][0] == 0) return false; FRESULT rc; _prepCache(fd); uint32_t pos = fileSeekPositions[fd]; rc = f_lseek(&rawFil, pos); if (rc) { Serial.println("readByteAt: seek failed"); return false; } uint8_t b; UINT v; f_read(&rawFil, &b, 1, &v); fileSeekPositions[fd]++; // FIXME: check v == 1 & handle error return b; } bool TeensyFileManager::writeByte(int8_t fd, uint8_t v) { // open, seek, write, close. if (fd < 0 || fd >= numCached) { Serial.println("failed writeByte - invalid fd"); return false; } if (cachedNames[fd][0] == 0) { Serial.println("failed writeByte - no cache name"); return false; } FRESULT rc; _prepCache(fd); uint32_t pos = fileSeekPositions[fd]; rc = f_lseek(&rawFil, pos); UINT ret; f_write(&rawFil, &v, 1, &ret); fileSeekPositions[fd]++; if (ret != 1) { Serial.println("Write failed"); } return (ret == 1); }