Using the File System

From WikiPrizm
Jump to: navigation, search

This page has not been completed. Parts may be missing or reorganized before completed. Information is provided as-is and may have errors.

At some point you will want to save data after your addin closes. Run-Matrix saves variables, eActivity saves your documents, etc.. Saving files isn't the same as fopen(...), you have to be aware of the two file systems on the Prizm and when to use each one.

About The File systems

The Prizm contains two file systems, the main memory (RAM-based) and the storage memory (ROM-based). When you connect your Prizm to your PC you will notice a Main Memory folder listed. This will let you access the Main Memory.

Main Memory

The main memory is used for quick storage of temporary data. This includes variables, window settings, stat data, and your system preferences. The RAM can be quickly read and written to, which makes it perfect for saving changes as the addin runs. The downside is that the Main Memory is small. It is only 61KB in size and can get erased if the Prizm loses power for a long enough time. The other limitations include:

  • No subfolders are allowed. Only folders in the root of the Main Memory.
  • Long file names aren't allowed
  • All files are stored as .g3m files
  • Functions to read/write the data are very different from how files are written on PCs. You read/write the entire file at once, opposed to the Storage Memory.
  • USB access to the Main Memory is limited

Storage Memory

The storage memory is a 16MB region that is used to store large files or files that needed to survive power loss. The file system does have limits. You must know the size of the file before another file is created. This may be done to prevent corruption if a file of unknown size isn't closed. The other disadvantage is that writing to the ROM is very slow. The advantage is that you can read and write a little at a time rather than all at once.

For technical specs for the flash chip in the Prizm, refer to the flash article.

Interacting with File systems

Main Memory

The main memory uses the MCS functions. These functions use a state system to keep track of what file you are accessing. You have to select a file to use then call functions to operate on it, unlike the storage memory where you use a pointer to access multiplle files at once.

Opening Files

To open a file for reading, you must use MCSGetDlen2. Remember that the main memory cannot have subfolders, so the unsigned char* dir is either a folder name or empty. int* data_len is a pointer to where the length of the file will be stored to. If the file doesn't exist, TODO. If it does, then the size is stored in the int. Here is an example:

MCSGetDlen2((unsigned char*)"test", (unsigned char*)"test", &x);

Writing Files

To write data to a file, you have a choice of either replacing the file or just changing part of it. MCSOvwDat2 will add data to a specified location in a file, creating it if it doesn't already exist. Here is an example of its usage:

TODO

If you want to make a fresh file with given data, you can use MCSPutVar2. Be warned that if the file exists, you will get an error code back and the data will not be written. Here is an example on its usage:

MCSPutVar2((unsigned char*)"test", (unsigned char*)"test", strlen("Hello World")+1, (void*)"Hello World");

Reading Files

To read data from a file, you can use MCSGetData1. In order to use this function, a file must be marked as 'active' first. You can do that by calling MCSGetDlen2. Here is an example on reading in data:

unsigned char *buffer;
int size;

if(MCSGetDlen2((unsigned char*)"test", (unsigned char*)"test", &size) == 0) // Only on success
{
  buffer = (unsigned char*)malloc(size);
  MCSGetData1(0,size,(void*)buffer);
  // Do something
  free(buffer);
}

Create and Delete Folders

You have seen the folder name being used, so you should at least know how to manage folders. MCS_CreateDirectory is used to make folders. Here is an example:

MCS_CreateDirectory((unsigned char*)"test");

To delete folders, you can use MCS_DeleteDirectory.

MCS_DeleteDirectory((unsigned char*)"test");

Remember to always check the return values in case an error is detected, such as trying to write a file in a folder that doesn't exist, or reading a file that doesn't exist. I have included a few error codes in the section below.

Note that folders may be created and not visible. This is because only folders with data in them are seen by add-ins.

Example

Lets take the above an make a quick example on using the main memory.

 #include <display.h>
 #include <color.h>
 #include <keyboard.h>
 #include <MCS_syscalls.h>
 #include <CONVERT_syscalls.h>
 #include <STD_syscalls.h>
 #define TEST_STR "Hello World"
 
 int main()
 {
   int x;
   unsigned char buffer[20];
   
   locate_OS(1,1);
   Print_OS((unsigned char*)"Making folder:",0,0);
   itoa(MCS_CreateDirectory((unsigned char*)"test"),buffer);
   Print_OS(buffer,0,0);
   
   locate_OS(1,2);
   Print_OS((unsigned char*)"Making folder:",0,0);
   itoa(MCS_CreateDirectory((unsigned char*)"test"),buffer);
   Print_OS(buffer,0,0);
   
   locate_OS(1,3);
   Print_OS((unsigned char*)"Writing:",0,0);
   itoa(MCSPutVar2((unsigned char*)"test", (unsigned char*)"test", strlen(TEST_STR)+1, (void*)TEST_STR), buffer);
   Print_OS(buffer,0,0);
   
   locate_OS(1,4);
   Print_OS((unsigned char*)"Opening:",0,0);
   itoa(MCSGetDlen2((unsigned char*)"test", (unsigned char*)"test", &x), buffer);
   Print_OS(buffer, 0,0);
   
   locate_OS(1,5);
   Print_OS((unsigned char*)"Reading:",0,0);
   MCSGetData1(0, (x > 20 ? 20 : x), buffer);
   Print_OS(buffer,0,0);
   
   locate_OS(1,6);
   Print_OS((unsigned char*)"Deleting folder:", 0,0);
   itoa(MCS_DeleteDirectory((unsigned char*)"test"), buffer);
   Print_OS(buffer,0,0);
   
   int key;
   while(1) GetKey(&key);
 }

Here is the output of the addin when run the first time on a Prizm:

Making folder:0
Making folder:64
Writing:0
Opening:0
Reading:Hello World
Deleting folder:69

I made the program leave the file and folder. If you run it again, you will see more errors being reported:

Making folder:64
Making folder:64
Writing:37
Opening:0
Reading:Hello World
Deleting folder:69

If you are looking for more information, make sure that you visit the MCS functions page.

TODO: deleting files, overwriting, and searching for files.

Error Codes

Storage Memory

The storage memory is the long term storage for the Prizm, unlike the main memory. The storage memory uses Bfile syscalls, just like the main memory uses MCS functions. Remember that even though a lot of storage space is available for use, try to use the main memory when possible. Even if you are making documents for calc use only, the main memory is still an option (Basic programs, spread sheets, etc. are stored in the main memory).

Making Files and Folders

When starting out with the storage memory, you should realize that it doesn't work as easily as fopen("/some/path/test.txt", "w");. The Prizm is very strict on the usage of the file system. Before you can open a file for writing, you must make sure it exists first. (You do this simply by trying to open it, but I will do that later on in the tutorial). If the file doesn't exist, you must create it manually. Bfile_CreateEntry_OS will create a blank entry (note entry, not file). If you look at the parameters for it, you will notice that it needs a filename (of course), but it also needs a mode and a size. These are specific to the Prizm.

The mode specifies what the entry is, either a file or a folder. This combines file/folder creation and deletion. Since we want a file, we also will need a size. The size of files is static. Once you create the file, it cannot be resized unless you make another file with a new size and copy data over. Now that you know about the mode and size, I need to touch on the filename. It isn't just a simple string, it is a 16 bit string. Even though GCC can produce 16 bit strings and work on the Prizm, they aren't exactly the same. To convert a string into the Prizm's string format, you must use Bfile_StrToName_ncpy (and Bfile_NameToStr_ncpy to go the other way around, but not needed now).

Bfile_StrToName_ncpy will convert a character array to a buffer of n*2 bytes. Here is an example of the usage:

#define PATH "\\\\fls0\\file.txt"
unsigned short buffer[sizeof(PATH)*2];

Bfile_StrToName_ncpy(buffer, (unsigned char*)PATH, sizeof(PATH));

You can see that the output buffer must be twice the size of the original string. You will also note that paths start with \\fls0\ (escaped to \\\\fls0\\ in C sources). This is because the OS was based off of a previous calculator that had an SD card, accessed using \\crd0\. Now that we have the filename, we can finally create a folder and file. Here is an example for creating both:

#define CREATEMODE_FILE 1
#define CREATEMODE_FOLDER 5
// These aren't in the PrizmSDK, but they are easier to remember.
#define FOLDER_PATH "\\\\fls0\\Example"
#define FILE_PATH "\\\\fls0\\Example\\File.txt"

unsigned short pFolder[sizeof(FOLDER_PATH)*2], pFile[sizeof(FILE_PATH)*2]; // Make buffers
int size = 128; // 128 byte file

Bfile_StrToName_ncpy(pFolder, (unsigned char*)FOLDER_PATH, sizeof(FOLDER_PATH)); // Copy
Bfile_StrToName_ncpy(pFile, (unsigned char*)FILE_PATH, sizeof(FILE_PATH));

Bfile_CreateEntry_OS(pFolder, CREATEMODE_FOLDER, 0);
Bfile_CreateEntry_OS(pFile, CREATEMODE_FILE, &size);

Deleting Files and Folders

If you run this code, you will see that it creates the folder and a file inside (can be viewed on a PC or from the calc's Memory Manager). If you check the value of size, you will notice that for this example it changes. You must use the value that it gives you, not what you intend for it to be. Now, the opposite of creating is deleting and is very similar to creation. Bfile_DeleteEntry is used to remove entries. It only has a filename parameter (needs to be 16 bit as well), but doesn't ask the mode. This is because it autodetects what you are deleting. Here is an example on deleting:

#define CREATEMODE_FILE 1
#define CREATEMODE_FOLDER 5
// These aren't in the PrizmSDK, but they are easier to remember.
#define FOLDER_PATH "\\\\fls0\\Example"
#define FILE_PATH "\\\\fls0\\Example\\File.txt"

unsigned short pFolder[sizeof(FOLDER_PATH)*2], pFile[sizeof(FILE_PATH)*2]; // Make buffers

Bfile_StrToName_ncpy(pFolder, (unsigned char*)FOLDER_PATH, sizeof(FOLDER_PATH));
Bfile_StrToName_ncpy(pFile, (unsigned char*)FILE_PATH, sizeof(FILE_PATH)); 

Bfile_DeleteEntry(pFolder, CREATEMODE_FOLDER, 0); // Deleting a folder will remove contents - Careful

Opening Files

Now that you know how to make and delete files and folders, lets move on to opening files with Bfile_OpenFile_OS. This takes a 16 bit filename and a mode. The mode is explained on the function page. Now, this function returns a handle to the file if opened, negative if failed. This is one way to detect if a file exists or not. The other way is to use Bfile_FindFirst and friend and will be touched on later. Here is an example on how to properly open a file, creating it if needed:

#define CREATEMODE_FILE 1
#define CREATEMODE_FOLDER 5
#define READ 0
#define READ_SHARE 1
#define WRITE 2
#define READWRITE 3
#define READWRITE_SHARE 4
// These aren't in the PrizmSDK, but they are easier to remember.
#define FILE_PATH "\\\\fls0\\File.txt"

unsigned short pFile[sizeof(FILE_PATH)*2]; // Make buffer
int hFile;

Bfile_StrToName_ncpy(pFile, (unsigned char*)FILE_PATH, sizeof(FILE_PATH)); 
hFile = Bfile_OpenFile_OS(pFile, READWRITE); // Get handle

if(hFile < 0) // Check if it opened
{
  int size = 256;
  if(Bfile_CreateEntry_OS(pFile, CREATEMODE_FILE, &size) >= 0) // Did it create?
  {
    hFile = Bfile_OpenFile_OS(pFile, READWRITE);
    if(hFile < 0) // Still failing?
    {
      // Die with a fatal error
      // This shouldn't happen
    }
  }
  else
  {
    // Die with a fatal error
    // This shouldn't happen
  }
}

Writing to Files

Now that you have a file handle open, you can read and/or write to the file. Remember that files have a static size and cannot be expanded. For writing, you must use Bfile_WriteFile_OS. This function is very easy to understand. Using the handle returned by Bfile_OpenFile_OS, you can write data to the current position in the file, such as:

#define STR "Hello World!"
// Using hFile from before
Bfile_WriteFile_OS(hFile, STR, sizeof(STR));

Seeking and Telling

NOTE: The latest libfxcg has both of these functions and is working.

Now, before I move on to reading and such, let me first touch on Bfile_SeekFile_OS and Bfile_TellFile_OS since the use can be easily shown here. Still remembering that file size is static, you can seek anywhere in the file (in absolute position) and write data. Here is an example that builds off of the above:

#define STR "Hello People"
// Using hFile from before
Bfile_WriteFile_OS(hFile, STR, sizeof(STR));

// Now, seek and change that horrible string to something nicer :-)
Bfile_SeekFile_OS(hFile, 6); // Position indicated by |:  "Hello |People"
Bfile_WriteFile_OS(hFile, "World!", 7); // Now the file is "Hello World!"
int position = Bfile_TellFile_OS(hFile); // Gives us the location 13.

Reading Files

Congrats, you know how to open a file and write anywhere inside of it. Now, lets get onto reading the file with Bfile_ReadFile_OS. This function isn't as simple as Bfile_WriteFile_OS because we can move the position in the file two ways. You will notice a new parameter int readpos. Normally, you would just seek to a new position if you need to read somewhere else. For some reason, the Prizm lets you change the position in the same function. Lets say you need to read a byte at position 10. You would use:

Bfile_ReadFile_OS(hFile, buffer, 1, 10);

That will store 1 byte into buffer that was read at position 10. Lets say that you just want to read data in order, letting the position move along with each read. In that case, you can set readpos to -1 and it will use the position stored in the file handle.

Bfile_ReadFile_OS(hFile, buffer+0, 1, 0); // Sets position to 0 and reads 1 byte
Bfile_ReadFile_OS(hFile, buffer+1, 1, -1); // Uses the position 1, moved by the previous function

You can also use Bfile_SeekFile_OS, but it isn't needed at all for reading. Now, when you are done with a file, you need to close it. For that, you will use Bfile_CloseFile_OS. Simply:

Bfile_CloseFile_OS(hFile);

There are a few other functions that can be helpful. If you are opening a file, but don't know how big it is, you can use Bfile_GetFileSize_OS to get the size. FIXME: This is a guess. If you call Bfile_GetFileSize_OS, it will return the remaining bytes in a file at position int pos. It may allow you to use -1 to use the current position stored in the file handle.

Renaming Entries

NOTE: This function is not in any released SDK here. It only exists in AHelper's fork of libfxcg.

You have your files and folders made, everything is ok. Lets say that you want to have the user move stuff around. A problem comes up when you want to move a file. Luckily, Bfile_RenameEntry can be used to rename a file or a folder. Remember that the usage of the path names is the same as all other Bfile functions. Here is an example on renaming a file:

#define CREATEMODE_FILE 1
// These aren't in the PrizmSDK, but they are easier to remember.
#define FILE_PATH "\\\\fls0\\File.txt"
#define NEW_PATH "\\\\fls0\\File2.old"

unsigned short pFile[sizeof(FILE_PATH)*2]; // Make buffer
unsigned short pFile2[sizeof(NEW_PATH)*2]; // Make another buffer
int size = 128;

Bfile_StrToName_ncpy(pFile, (unsigned char*)FILE_PATH, sizeof(FILE_PATH)); 
Bfile_StrToName_ncpy(pFile2, (unsigned char*)NEW_PATH, sizeof(NEW_PATH)); 
Bfile_CreateEntry(pFile, CREATEMODE_FILE, &size); 

Bfile_RenameEntry(pFile, pFile2); // Autodetects if you want a file or a folder

TODO: Searching for files

Finding and Listing Files and Folders

The Prizm's OS lets you find/list files and folders by specifying a path (and one '*' wildcard in the filename). Bfile_FindFirst will initialize the find handle and search for the first hit. You should pay attention to the return value in case there are no matches or the path is invalid. Note that you will be using 16 bit strings to do this.

unsigned short filter[0x100], found[0x100];
int ret, handle;
file_type_t info; // See Bfile_FindFirst for the definition of this struct

Bfile_strToName_ncpy(filter, "\\\\fls0\\*.g3a", 0x50); // Overkill

ret = Bfile_FindFirst(filter, &handle, found, &info);

if(ret == -16)
  PrintXY(1,1,"xxNo files found");
else if(ret == 0)
{
  unsigned char path[50];
  Bfile_NameToStr_ncpy(path, found, 50);
  locate_OS(1,1);
  Print_OS(path,0,0);
}

Bfile_FindClose(handle);

To read all files and folders from the find handle, use Bfile_FindNext.

// Note that I am using uClibc++.  You may use itoa and friends instead
#include <iostream>
using namespace std;

unsigned short filter[0x100], found[0x100];
int ret, handle;
file_type_t info; // See Bfile_FindFirst for the definition of this struct

Bfile_strToName_ncpy(filter, "\\\\fls0\\*.g3a", 0x50); // Overkill

ret = Bfile_FindFirst(filter, &handle, found, &info);

while(ret == 0)
  unsigned char path[50];
  Bfile_NameToStr_ncpy(path, found, 50);
  cout << path << " :: " << info.fsize << endl;
  
  ret = Bfile_FindNext(handle, found, &info);
}

Bfile_FindClose(handle);

Just remember to use Bfile_FindClose on the handle when you are done with it. It is untested, but the address in file_type_t should be pointing to the ROM and can be read from.

Example

NOTE: The PrizmSDK 0.3 has an issue with these functions. You can use a patched version that has these functions fixed, along with other changed. You can find the latest patched version on the Prizm Programming Portal.

 #include <stdlib.h>
 #include <fxcg/keyboard.h>
 #include <fxcg/display.h>
 #include <fxcg/file.h>
 #define CREATEMODE_FILE 1
 #define CREATEMODE_FOLDER 5
 #define READ 0
 #define READ_SHARE 1
 #define WRITE 2
 #define READWRITE 3
 #define READWRITE_SHARE 4
 #define FOLDER_PATH "\\\\fls0\\Example"
 #define FILE_PATH "\\\\fls0\\Example\\File.txt"
 #define STR "Hello People"
 
 // Easy printing
 int y = 1;
 void printi(const char* string, int a)
 {
   unsigned char buffer[20];
   
   itoa(a,buffer);
   
   locate_OS(1,y++);
   Print_OS((unsigned char*)string,0,0);
   Print_OS(buffer,0,0);
 }
 void prints(const char* string1, const char* string2)
 {
   locate_OS(1,y++);
   Print_OS((unsigned char*)string1,0,0);
   Print_OS((unsigned char*)string2,0,0);
 }
 
 int main()
 {
   unsigned short pFolder[sizeof(FOLDER_PATH)*2], pFile[sizeof(FILE_PATH)*2]; // Make buffers
   int size = 128; // 128 byte file
   int hFile; // File handle
   
   Bfile_StrToName_ncpy(pFolder, (unsigned char*)FOLDER_PATH, sizeof(FOLDER_PATH)); // Copy
   Bfile_StrToName_ncpy(pFile, (unsigned char*)FILE_PATH, sizeof(FILE_PATH)); // Copy
   
   Bfile_CreateEntry_OS(pFolder, CREATEMODE_FOLDER, 0); // Make the folder first
   Bfile_CreateEntry_OS(pFile, CREATEMODE_FILE, &size); // Put the file in the folder
   
   hFile = Bfile_OpenFile_OS(pFile, READWRITE); // This opens the file in read/write mode
   Bfile_WriteFile_OS(hFile, STR, sizeof(STR));
   
   size = Bfile_GetFileSize_OS(hFile, 0); 
   printi("actual size:", size); // This shows that the logical file size is different
   
   char *buffer = (char*)malloc(size);
   printi("seek:",Bfile_SeekFile_OS(hFile, 6)); // Moves to before 'People'
   printi("write:",Bfile_WriteFile_OS(hFile, "World!", 7)); // Overwrites
   printi("tell:",Bfile_TellFile_OS(hFile)); // Prints the location
   Bfile_ReadFile_OS(hFile, buffer, size, 0); // Reads in the entire file
   Bfile_CloseFile_OS(hFile); // Close it since we are done
   Bfile_DeleteEntry(pFolder); // Deleting the folder also deletes everything inside (!)
   
   prints("string:",buffer); // Prints "Hello World!"
   free(buffer);
   
   int key;
   while(1) GetKey(&key);
 }

Error Codes