Programming:  Storing data

We access the storage system by using two selectors, each of which is made of 4 bits, theoretically providing up to 16 x 16 areas.

The first selector addresses what is called a main entry or mode. These modes are mainly used by the built-in applications, here the are:

  • 10h: TEL
  • 20h: TEL (Company)
  • 30h: Memo
  • 40h: Schedule
  • 50h: Spreadsheet
  • 60h: Mail
  • 70h: EXPENSE
  • 80h: Clock (City setting)
  • 90h: Dual window
  • A0h: ADD IN
  • B0h: Quick Memo

For each of these entries we have 16 sub entries or sub modes, addressed by the second selector.

Now, let's mind our own business and just focus the attention to ADD IN mode (0xA0). We can store our data here. Moreover this section lets us assign a name to each subentry (Other main entry's submodes don't have names). ADD IN section has specialized APIs for 'names management', here are some:

  • LibSubEntrySearch
  • LibSubEntrySave
  • LibSubEntryDel
  • LibSubEntryRename
  • LibGetAllEntry

Anyway this doesn't mean we only have 16 files!!! At this point data is further divided into records. How many records? Well, as many as the total memory allows, i guess.

Each submode manages a pool of fixed size blocks. We can allocate, free, read and write them. But HOW?

FILE_BUF and FILE_INFO structures, LibFileRead, LibFileWrite will save our day. FILE_BUF is about data definition. FILE_INFO is about data operations.

FILE_BUF

Now don't waste your time with all the fields this structure shows. We only need:

  • fsb_main_entry_
  • fsb_sub_entry_
  • fsb_scrt_info_
  • fbuf.text.fsb_text_buf_[2048+1]
  • fbuf.bin.char_num
  • fbuf.bin.bin_buf[3072]

'fsb_main_entry_' is the main entry selector (0xA0) for ADD IN. 'fsb_sub_entry_' is the submode selector. 'fsb_scrt_info_' specifies whether the block we're working with is hidden in the secret area or not. Set it to FILE_SECRET_MODE to hide it or else set it to FILE_OPEN_MODE.

Now it makes difference whether we're working with text or binary data. If working with text we use 'fbuf.text.fsb_text_buf_'. It's a 2k buffer and accepts a NULL terminated BIG BIG string. That's the reason why MEMO stores messages no greater than 2k. If working with binary data we use 'fbuf.bin.bin_buf' and put or get the data size in 'fbuf.bin.char_num'. Whatever we choose, these buffers are stored into memory by LibFileWrite and filled up by LibFileRead.

FILE_INFO

It only has 2 fields, 'fp' which identifies the block we're working with and 'kind', which says whether the block is binary FILE_KIND_BIN or text FILE_KIND_TEXT

ADD IN

If we're working with something other than ADD IN we must fill these structures by hand. While, with ADD IN, some functions will help us.

  • LibSubEntrySearch searches for a named submode and returns its selector.
  • LibSubEntrySave associates a name to a free submode and returns its selector.
  • LibGetAllEntry returns both the main and sub entries.

Well, time to put it all together.

The following code lets us configure, store and retrieve one byte of information. It shows an arrays of 8 special buttons (bits) to configure data. By clicking on a button we alternately set and clear the bit. Two buttons are provided to load and save the information. Try setting a configuration, save it, quit and run the application again, you will see the saved data back.

#include	<stdrom.h>
#include	"define.h"
#include	"libc.h"

Here's what we have. One button that we are going to cut into 8 pieces, and the load and save buttons.

#define BITS_X	30
#define BITS_Y	10
#define BITS_W	100
#define BITS_H	10

#define BUTTONS_X1 50
#define BUTTONS_X2 110

#define BUTTON_LOAD_Y1 50
#define BUTTON_LOAD_Y2 69
 
#define BUTTON_SAVE_Y1 80
#define BUTTON_SAVE_Y2 99

#define BUTTON_LOAD_CAPTION		"Load"
#define BUTTON_SAVE_CAPTION		"Save"

#define OBJ_LOAD 0xc012
#define OBJ_SAVE 0xc013
#define OBJ_BITS 0xc014

Let's use a named submode and call it "Example". We wrap all those long chains of identifiers, in the structure, with macros. We'd better put FILE_BUF variables into global space, rather than local. Even though someone says that in the smallest memory model the stack gets 8k, when i put this structure inside a function, i had troubles with the stack. Is sizeof FILE_BUF something like 8K? I don't know, i don't feel like finding out. BlockNumber reminds us which block data is stored into. Data is... well guess.

#define ENTRYNAME		"Example"

#define SUBENTRY(f)	((f).fsb_sub_entry_)
#define MAINENTRY(f)	((f).fsb_main_entry_)
#define AREA(f)		((f).fsb_scrt_info_)
#define BUFFER(f)		((f).fbuf.bin.bin_buf)
#define BSIZE(f)		((f).fbuf.bin.char_num)

FILE_BUF fb;
int RecordNumber;
byte Data;

void DoLoad();
void DoSave();

We should actually add 8 + 2 buttons, but we cheat ;P

TCHTBL TchList[4] = 
{
	/* Load button */
		BUTTONS_X1, BUTTON_LOAD_Y1, 
		BUTTONS_X2, BUTTON_LOAD_Y2,
		ACT_MAKE,
		OBJ_LOAD,
		0x0000,

	/* Save button */
		BUTTONS_X1, BUTTON_SAVE_Y1, 
		BUTTONS_X2, BUTTON_SAVE_Y2,
		ACT_MAKE,
		OBJ_SAVE,
		0x0000,

	/* Bits button(s) */
		BITS_X, BITS_Y, 
		BITS_X + BITS_W, BITS_Y + BITS_H, 
		ACT_MAKE,
		OBJ_BITS,
		0x0000,

	/* End */
		0, 0, 0, 0,
		ACT_NONE,
		OBJ_END,
		0x0000
};

With this function the program says hello to the storage system and checks the situation. We LibSubEntrySearch to see if a previous instance of the program has already allocated things. If this is the case we know we stored the byte in block 0, we just have to DoLoad it. On the contrary, if no subentry named "Example" exists, we create it (LibSubEntrySave). If we fail, we're nice enough to let the program go on, but we have to tell the next data functions not to operate (RecordNumber = -1). We created an entry, what are its mode and submode? Don't think about it, ask LibGetAllEntry, it stores the corresponding values into fb. We want to save the byte into the open area, we have nothing to hide ;) We save our value as binary in the first position of the bin_buf(fer). If we got to here, that's because nothing has been saved yet. So we must ask the system for a NEW block. If we want to allocate a NEW block we have to set FILE_INFO's file pointer to 0xFFFF. LibFileWrite will return us the number of the block it allocated, placing it into the same field (fp). Since we just defined a whole subentry, why should we expect it doesn't return 0? If there's a clear blue sky why should it rain....?

UPDATE!!!

...and as it always happens it rained!!! It's a mistake to believe that for every new subentry, the first block we get is '0'. I guess the block number has a global nature, it is not just related to its current subentry. So the programmer must run after the blocks he/she stored with a fly-catcher. TWISTED just TWISTED, it's like we have to remember where we placed what we wanted to remember.

WORKAROUND

Well, the old version of the sample did only work on the emulator, since, unless you add other stuff, it contains no data stored. The code did not work on my PV until i made the modification marked in red. If our data is already in memory we have to search for the block number we allocated, LibFileFindNext being our fly-catcher, or just imagine the programmer having to knock on every block's door. WHY !?!?!

void DataSetup()
{
	FILE_INFO fi;

	Data = 0; 

	if (LibSubEntrySearch(ENTRYNAME, &SUBENTRY(fb)))
	{
		bool res;

		if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
		{
			LibPutMsgDlg("LibGetAllEntry error");		
			RecordNumber = -1;
			return;
		}

		AREA(fb) = FILE_OPEN_MODE;

		fi.fp = 0xFFFF;
		fi.kind = FILE_KIND_BIN;

		res = LibFileFindNext(&fb, &fi, 0x00);
		if (res)
		{
			RecordNumber = fi.fp;
			DoLoad();
		}
		else 
			RecordNumber = -1;
		return;
	}

	if (!LibSubEntrySave(ENTRYNAME, &SUBENTRY(fb)))
	{
		LibPutMsgDlg("LibSubEntrySave error");
		RecordNumber = -1;
		return;
	}

	if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
	{
		LibPutMsgDlg("LibGetAllEntry error");		
		RecordNumber = -1;
		return;
	}

	AREA(fb) = FILE_OPEN_MODE;

	memset(BUFFER(fb), 0, 3072);
	BSIZE(fb) = 1;
	*BUFFER(fb) = Data;

	fi.fp = 0xFFFF;
	fi.kind = FILE_KIND_BIN;

	if (!LibFileWrite(&fb, &fi))
	{
		LibPutMsgDlg("LibFileWrite error");
		RecordNumber = -1;
		return;
	}

	RecordNumber = fi.fp;
}

This loads the byte into 'Data'. If something went wrong (RecordNumber == -1) we do nothing, else LibGetAllEntry fills the 'entry' fields with values referring to our name. This time we know which block we're addressing and tell it (fi.fp = RecordNumber). If it is alright after LibFileRead, the bin_buf(fer) contains, in the first position, the value previously stored.

void DoLoad()
{
	FILE_INFO fi;

	if (RecordNumber == -1) return;

	if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
	{
		LibPutMsgDlg("LibGetAllEntry error");		
		return;
	}

	AREA(fb) = FILE_OPEN_MODE;

	fi.fp = RecordNumber;
	fi.kind = FILE_KIND_BIN;

	if (!LibFileRead(&fb, &fi))
		LibPutMsgDlg("LibFileRead error");

	Data = *BUFFER(fb);
}

This saves the byte. This time we don't need to allocate a NEW block, we want to rewrite the old one, so fi.fp = 'the block we want to override'.

void DoSave()
{
	FILE_INFO fi;

	if (RecordNumber == -1) return;

	if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
	{
		LibPutMsgDlg("LibGetAllEntry error");		
		return;
	}

	AREA(fb) = FILE_OPEN_MODE;

	fi.fp = RecordNumber;
	fi.kind = FILE_KIND_BIN;

	*BUFFER(fb) = Data;

	if (!LibFileWrite(&fb, &fi))
		LibPutMsgDlg("LibFileWrite error");
}

This shows an array of small buttons.

void ShowBits()
{
	int i, bit_w;
	byte acc;
	int x1, y1, x2, y2;

	bit_w = BITS_W / 8;

	acc = 0x80;

	for (i = 0; i < 8; i++)
	{
		x1 = BITS_X + bit_w * i;
		y1 = BITS_Y;
		x2 = BITS_X + bit_w * (i + 1);
		y2 = BITS_Y + BITS_H; 

		LibGdsBox(x1, y1, x2, y2);
		LibGdsClr(x1 + 2, y1 + 2, x2 - 2, y2 - 2); 
	
		if (Data & acc)
		{
			LibGdsReverse(x1 + 2, y1 + 2, x2 - 2, y2 - 2); 
		}

		acc >>= 1;
	}

	LibPutDispBox(BITS_X, BITS_Y, BITS_W + 1, BITS_H + 1); 
}

The main 'data and display' setup function.

void Setup()
{
	int sz;

	DataSetup();

	LibClrDisp();

	LibGdsBox(BUTTONS_X1, BUTTON_LOAD_Y1, BUTTONS_X2, BUTTON_LOAD_Y2); 
	sz = LibGetProStrSize(IB_PFONT2, BUTTON_LOAD_CAPTION);
	LibPutProStr(IB_PFONT2, 
		BUTTONS_X1 + (BUTTONS_X2 - BUTTONS_X1 - sz) / 2, 
		BUTTON_LOAD_Y1 + (BUTTON_LOAD_Y2 - BUTTON_LOAD_Y1 - 9) / 2, 
		BUTTON_LOAD_CAPTION, sz);
	

	LibGdsBox(BUTTONS_X1, BUTTON_SAVE_Y1, BUTTONS_X2, BUTTON_SAVE_Y2); 
	sz = LibGetProStrSize(IB_PFONT2, BUTTON_SAVE_CAPTION);
	LibPutProStr(IB_PFONT2, 
		BUTTONS_X1 + (BUTTONS_X2 - BUTTONS_X1 - sz) / 2, 
		BUTTON_SAVE_Y1 + (BUTTON_SAVE_Y2 - BUTTON_SAVE_Y1 - 9) / 2, 
		BUTTON_SAVE_CAPTION, sz);

	LibPutDisp();

	ShowBits();
}

Just because we haven't provided the bitmaps for the buttons it doesn't mean we can't show some press-release action...

void DoAnimate(int x1, int y1, int x2, int y2)
{
	LibGdsReverse(x1, y1, x2, y2);
	LibPutDispBox(x1, y1, x2 - x1, y2 - y1);
	LibWait(IB_125MWAIT);
	LibGdsReverse(x1, y1, x2, y2);
	LibPutDispBox(x1, y1, x2 - x1, y2 - y1);
}

If we press Load or Save we animate the button, perform its action and show the results. If we clicked on the BIG configuration button we have to see which bit we selected. Some formulas saves us from adding 8 touch areas :)

void AnimateButtons(TCHSTS *tsts)
{
	int bit, mask;

	if (tsts->obj == OBJ_LOAD)
	{
		DoAnimate(	BUTTONS_X1, BUTTON_LOAD_Y1, 
					BUTTONS_X2, BUTTON_LOAD_Y2);
		DoLoad();
		ShowBits();
	}

	if (tsts->obj == OBJ_SAVE)
	{
		DoAnimate(	BUTTONS_X1, BUTTON_SAVE_Y1, 
					BUTTONS_X2, BUTTON_SAVE_Y2);
		DoSave();
	}

	if (tsts->obj == OBJ_BITS)
	{
		bit = (tsts->x - BITS_X) / (BITS_W / 8);
		mask = 0x80 >> bit;
		if (Data & mask)
			Data &= ~mask;
		else
			Data |= mask;
		ShowBits();
	}
}

No comment.

void main()
{
	TCHSTS tsts;
	bool bExit = FALSE;

	LibTchStackClr();
	LibTchStackPush(NULL);

	LibTchStackPush(TchHardIcon);
	LibTchStackPush(&TchList[0]);

	Setup();

	LibTchInit();

	for (;bExit == FALSE;)
	{
		LibTchWait(&tsts);           

		if (tsts.obj == OBJ_HIC_ESC)        
			bExit = TRUE;

		AnimateButtons(&tsts);
	}

	LibTchStackClr();
	LibJumpMenu();
}