Writing a File

The file writing command handler invokes CExampleWriteFileDialog and, after you press the OK button or the Enter key, its OkToExitL() function is called. It's quite a large piece of code, so I'll take it a step at a time. First, I get the filename:

TBool CExampleWriteFileDialog::OkToExitL(TInt /* aKeycode */)

// Get filename

CEikEdwin* edwin = static_cast<CEikEdwin*H>

(Control(EExampleControlIdFileName)); HBufC* fileName = edwin->GetTextInHBufL(); // Check it's even been specified if(IfileName)

TrychangeFocusToL(EExamplecontrolldFileName); iEikonEnv->LeaveWithInfoMsg(R_EIK_TBUF_N0_FILENAME_ SPEciFIED);

cleanupStack::PushL(fileName);

There's nothing obviously file-related in here, but there's some interesting descriptor and cleanup stack code. If this function succeeds, I have an HBufc* filename, which is pushed to the cleanup stack and contains a nonempty string. It can leave for numerous reasons, the most interesting of which is my call to iEikonEnv->LeaveWithInfoMsg(). This leaves, cleans up anything that needs cleaning up (nothing does, as it happens) and notifies the error to the user.

In fact, iEikonEnv->LeaveWithInfoMsg () reads its message from a resource file. To do this, a CONE function that uses CONE's built-in RFs is called. I'll also be using this in the next step, which is to check that the filename is valid:

// check it's a valid filename if(!icoeEnv->FsSession().IsValidName(*fileName)) {

TrychangeFocusToL(EExamplecontrolIdFileName);

iEikonEnv->LeaveWithInfoMsg(R_EIK_TBUF_INVALID_FILE_NAME); }

I just use IsValidName() to check whether it's valid; if not, I move the focus to the filename control and leave with an info-message. This time, I'm getting value from the cleanup stack. The filename will be popped and destroyed as part of this leave processing.

Now, I get the text to write to the file:

// Get the text string edwin = static_cast(cEikEdwin*>(control EExamplecontrolIdText)); HBufc* text = edwin->GetTextInHBufL(); if(!text)

text = HBufc::NewL(0); cleanupStack::PushL(text);

I don't really mind if it's blank this time, but for uniformity reasons, I allocate a zero-length string rather than using the zero pointer returned by this dialog API.

There's plenty of leaving code to follow, so I push the text to the cleanup stack before moving on to ensure that all the directories needed for the file exist:

// Ensure the directories etc. needed for the file exist

TInt err = icoeEnv->FsSession().MkDirAll(*fileName);

if(err != KErrNone && err != KErrAlreadyExists)

User::Leave(err);

The code makes a simple MkDirAll() call. This time you can see why RFs functions don't leave with an error code: I'm happy if either the directories didn't exist and they were created successfully, or they did exist and nothing happened. Otherwise, I initiate a leave.

Now things get more delicate. I tentatively create the file, by opening it:

// Check whether it's going to be possible to create the file for writing RFile file;

err = file.Create(iCoeEnv->FsSession(), *fileName, EFileWrite); if(err != KErrNone && err != KErrAlreadyExists)

User::Leave(err); // No need to close file, since it didn't open

RFile::Create() creates a new file if possible. If the file was already there, err contains KErrAlreadyExists: I want to know whether the user really wants to replace this file, and I'll check that in the next step. If the file wasn't there, but was created successfully by RFile:: Create() , then err is set to KErrNone. In any other case, I leave.

If I had wanted to replace the file without checking with the user, I would have used RFile::Replace() to open the file. As it is, I want to check with the user:

// Check whether the user wants to replace the file, if it already exists if(err == KErrAlreadyExists) {

if(iEikonEnv->QueryWinL(R_EIK_TBUF_FILE_REPLACE_CONFIRM)) User::LeaveIfError(file.Replace(iCoeEnv->FsSession(),

*fileName, EFileWrite));

else iEikonEnv->LeaveWithInfoMsg(0); // Let user try again

If the attempt to create the file revealed that it already existed, I use a UIQ query dialog to confirm with the user. If the user insists, then I open the file again with RFile: :Replace (). Any errors this time must be genuine errors, so I don't have to check specific codes - I simply enclose the call with User: :LeaveIfError() .

But if the user didn't want to replace the file, I leave silently with iEikonEnv->LeaveWithInfoMsg(0). The leave helps to ensure that anything I've pushed to the cleanup stack is popped and destroyed. The 0 indicates that I'm not passing a resource ID of a message, because I don't want a message - none is needed, since the user knows exactly what has happened: they just replied No to a query.

Now I've done all the checks I need to make. I've verified user input and I've verified that I can create the file for writing.

I prefer to separate UI code (like this) from engine code (which actually does things, like writing files), so I don't write the file from this function. Instead, I pass the parameters back to the app UI, which will process them when I return:

// Finished with user interaction: communicate parameters and return delete iAppUi->iFileName; iAppUi->iFileName = fileName; delete iAppUi->iText; iAppUi->iText = text;

CleanupStack::Pop(2); // text, fileName return ETrue; }

I start by closing the file because that's no longer needed here. I then set the HBufC pointers in the app UI to refer to the new strings and make sure that whatever was there before is deleted. Finally, I pop both the new string pointers from the cleanup stack because they are stored safely as member variables now.

The scene of processing now returns to my writeFileL() function:

void CExampleAppUi::WriteFileL() {

// Create a write stream on the file RFileWriteStream writer;

writer.PushL(); // Writer on cleanup stack

User::LeaveIfError(writer.Replace(iCoeEnv->FsSession(),

*iFileName, EFileWrite));

// Write the text writer << *iText; writer.CommitL(); // Finish

CleanupStack::PopAndDestroy(); // Writer }

Having used an RFile to check that this file could be written, I don't use RFile::Write() to write it. Instead, I'm now using a higher-level class, RFileWriteStream, which is derived from RWriteStream. Every function you see in the code above, except the Replace () function I use to open the stream, is actually a member of the base class. RWriteStream provides a rich API including the insertion operator <<, which is used to write the text.

Before writing the text, I have to open the write stream, which, in reality means opening the file and then initiating the stream for writing. And before I do that, I use PushL() so the write stream can push itself to the cleanup stack.

After writing, I commit the stream data using CommitL() and then close the stream using CleanupStack::PopAndDestroy (). Committing to a write stream causes buffers to be flushed and sent to the file using RFile::Write(). This may fail, which is why commitL() is a leaving function.

There are two good reasons for preferring to write this file through an RWritestream rather than through an RFile:

■ RFile ::Write() is inconvenient to use and sometimes positively harmful. It's much safer to use the stream functions.

■ RFile ::Write() sends data immediately to the server and RFile::Flush() flushes server-side buffers to the file. In contrast, RFileWritestream keeps its buffers in memory on your thread's default heap. RFileWritestream::CommitL() flushes client-side buffers by doing an RFile ::Write(). If your pattern of writing activity consists of many operations that write small amounts of data, then writing to the file would involve significantly more client-server messages, which would severely impact performance.

RFileWritestream includes a range of << functions for all Symbian OS built-in types. As we'll see below, you can code an ExternalizeL () function on any class to allow it to be written out to any type of write stream using <<.

As usual, writeFileL() is fully error checked. Every function that can leave is handled. Note that << can also leave, but doesn't have an l o indicate it!

writeFileL() is extremely unlikely to fail in this case: only a few microseconds before calling it, I established that the conditions for writing were good and I'm only writing a small amount of data. But it could fail because conditions could change even in those few microseconds and if I was writing a lot of data, it could easily fail with KErrDiskFull.

There is actually no way to ensure that writeFileL () can't fail, so we have to handle failure in the calling function. This is one of those cases where the cleanup stack doesn't do what we want: we actually need a trap. Here's the code I use to invoke the dialog and to call writeFileL() under a trap:

void CExampleAppUi: :CmdWriteFileL() {

// Use a dialog to get parameters and verify them CEikDialog* dialog = new(ELeave) CExampleWriteFileDialog(this); if(!dialog->ExecuteLD(R_EXAMPLE_WRITE_FILE_DIALOG))

return; // Write file under a trap TRAPD(err, WriteFileL());

delete iText; iText = 0;

iCoeEnv->FsSession().Delete(*iFileName) ;

// Don't check errors here! iAppView->DrawNow(); User::Leave(err);

The normal sequence of processing here is that I invoke the dialog (using the terse syntax that was covered in Chapter 10), write the file, and redraw the app view to reflect the new iText value.

If writeFileL () leaves, I handle it by deleting iText and setting its pointer to 0. I also delete the file. Then I redraw the app view and use user::Leave () to propagate the error so that Uikon can display an error message corresponding to the error code. Test this, if you like, by inserting User ::Leave(KErrDiskFull) somewhere in WriteFileL() .

The trap handler has one virtue: it lets the user know what's going on. It's still not good enough for serious application use, though, because this approach loses user and file data. It's a cardinal rule that Symbian OS shouldn't do that. A better approach would be to do the following:

■ Write the new data to a temporary file: if this fails, keep the user data in RAM, but delete the temporary file.

■ Delete the existing file: this is very unlikely to fail, but if it does, keep the user data in RAM but delete the temporary file.

■ Rename the temporary file: this is very unlikely to fail, and if it does, we're in trouble. Keep the user data in RAM anyway.

I would need to restructure my program to achieve this; the application architecture's framework for load/save and embeddable documents looks after these issues for you.

For database-type documents, you face these issues with each entry in the database. They are managed by the permanent file store class, which we'll encounter below.

0 0

Post a comment

  • Receive news updates via email from this site