Click here to Skip to main content
Click here to Skip to main content

Autosave and Crash Recovery

, 24 Feb 2000
Rate this:
Please Sign up or sign in to vote.
How to implement autosave and autorecover features in your application.

Introduction

How to autosave files and then recover these files after a program crashes, is something that is seldom discussed, but adds power and flexibility to a program. This article will describe how to implement an autosave and autorecover method similar to that used by MS Office.

How it works

Autosaving is quite simple. After a specific amount of time, your program must serialize the loaded documents to the disk at a specific location. These serializations must not overwrite the files that are currently in use by the user, because this would eliminate the user's choice of whether or not they would like to save the file on exit from your program. Each time the program loads up, it must search for autosaved files and restore them as needed.

As with Office, this implementation saves these temporary files to a temporary directory inside your Windows directory and searches this directory at load time for autosaved files. If any are found, it will then begin its recovery process.

Assigning an Autosave Directory

First, you must choose where you are going to save the files. This directory must remain constant, or else you would have to search the entire hard disk for autosaved files... and that is out of the question. For practical purposes, your program should place them inside a folder in the most constant directory on the HD, the Windows directory. This directory should be stored in the environment variable WINDIR. If for some reason, the user's system does not contain a WINDIR environment variable, or the environment variable is incorrectly set, your program needs to detect this and choose another directory. You can easily check to see if the stored directory exists, by using the CFileFind class to search for it. The following code will do just that (gm_autosave directory should be a string, either global, as with this example, or accessible through a member function in your application). It is best preformed within the InitInstance function of your application.

::GetEnvironmentVariable("WINDIR",buffer,512);
CFileFind CFF;
if(CFF.FindFile(buffer,0))
{
    base=buffer;
}
else base = "C:\\TEMP";

gm_autosaveDirectory = base+"\\TEMP";

Optionally, you could use GetTempPath(DWORD nBufferLength, LPTSTR lpBuffer); to retrieve a temporary path. However, the above implementation will allow you to specify a specific directory for the files to occupy, such as "Temporary Autosaved Files".

Autosaving files

Now that the path is determined, your program will know where to save its files. The next part is, therefore, implementing the save routine in your application. To avoid soaking up system resources, use the OnTimer function in your Main Frame window. On initialization, your program needs to call the SetTimer(...) function with the appropriate values. SetTimer(0,m_autosavetime * 60 * 1000,NULL) will install the timer for you (assuming m_autosavetime is in minutes). The OnTimer() function of your Main Frame will now be called each time the timer has expired. Within this function, you will need to send autosave messages to all the views contained by your app. Use EnumChildWindows to call a procedure with each child window.

::EnumChildWindows(m_hWnd,AutosaveTimerChildProc,NULL);

AutosaveTimerChildProc needs to verify that the given child is a view for the document before sending the message to the window. DYNAMIC_DOWNCAST the pointer passed to your procedure to verify that it is a view, and then use PostMessage to notify your view of the change:

BOOL CALLBACK AutosaveTimerChildProc( HWND hwnd,  LPARAM lParam)
{
    if(DYNAMIC_DOWNCAST(CMyView,CWnd::FromHandle(hwnd)) != NULL)
    {
        ::PostMessage(hwnd,WM_MYVIEW_AUTOSAVETIMER,0,0);
    }
    return(TRUE);
}

Note: WM_MYVIEW_AUTOSAVETIMER should be defined as an Application Message, which is based of WM_APP.

Now, your view must process the message and save the file in a restorable way. Add a message map such as this:

ON_MESSAGE(WM_MYVIEW_AUTOSAVETIMER,OnAutosaveTimer)

and prototype the function in your view's class definition as a void accepting the standard WPARAM and LPARAM messages. This function can get a little tricky. The problem is that if the user saves the file in another location, then the restore path for the file will change also. Since the autosave name is based off the actual filename (in case the user needs to manually access the file in some strange incident) and you don't want more than one copy of the autosaved file, your program must always store the name of the last autosave backup and delete the autosaved backup, before it writes its copy of the file. This implementation uses a vector of CStrings to store the backup filenames (this vector will contain either the name of the backup or nothing at all). Additionally, your program must make sure that the directory for your autosave has been created before saving to it! If an error occurs during the process, the choice is up to you how you will recover or handle it.

void CMyView::OnAutosaveTimer(WPARAM w, LPARAM l)
{
    CFileFind CFF;
    if(CFF.FindFile(gm_autosaveDirectory.GetBuffer(1))==FALSE) 
    {
        if(CreateDirectory(gm_autosaveDirectory.GetBuffer(1), 
                                 NULL) ==0) //create directory
        {
            //an error has occured, process the error here
        }

    }

    // document name and your autosave extension
    CString  fname = (gm_autosaveDirectory + 
          "\\"+GetDocument()->GetTitle()+".MBK"); 
    if(m_autosave_names.size() > 0)  //delete old file
    {
        if(CFF.FindFile((*m_autosave_names.begin()))==TRUE)
        {
            if(::DeleteFile(((*m_autosave_names.begin()))) == 0)
            {
                //an error has occured, process the error here if you want
                //however, if the file simply does not exist, 
                //that is fine, ignore the error and continue
            }
        }
        // remove the old filename from the vector
        m_autosave_names.erase(m_autosave_names.begin());
    }
    m_autosave_names.push_back(fname); //add the new filename to the vector

    //call store function
    GetDocument()->StoreAutoRecoveryInformation(fname);
}

The StoreAutoRecoveryInformation function is implementation dependant. An easy implementation would simply serialize the document's true path to an archive, and then call the Serialize function to save the file after the path. For our example, this will suffice:

CMyDoc::StoreAutoRecoveryInformation(CString path)
{
    CFile f;
    if(f.Open(path,CFile::modeWrite | CFile::modeCreate)!=0)
    {
        CArchive ar(f,CArchive::store);
        ar.WriteString(path);
        try
        { 
            Serialize(ar);
        }
        catch(CException *e)
        {
            //write error occured, process here
        }
        ar.Close();
        f.Close();
    }
    else
    {
        //open error occured, process here
    }
}

Autosave Recovery

Rather than writing an entirely new document and view class for recovered files, you can simply modify your Document's Serialize(...) function to check the file extension on serialization. If the extension is that of the autosave file type, perform the appropriate actions.

//helper function to get file extension from a given path
CString MakeExt(CString fname) 
{
    for(int i = fname.GetLength()-1; i >0; i--)
    {
        if(fname.GetAt(i)=='.') return fname.Mid(i);
        if(fname.GetAt(i)=="\\") break;
    }
    return "";
}
.
.
.
CMyDoc::Serialize(CArchive ar)
{
    CString ext=MakeExt(ar.GetFile()->GetFileName());
    ext.MakeLower();
    if(ext == ".mbk" && ar.IsLoading())
    {
        CString s;
        //read path to restore path member variable
        m_restorepath = ar.ReadString(s)
        // read old path to member variable
        m_oldpath = ar.GetFile()->GetFileName();
        CMyBaseDocument::Serialize(); 
          //call the base document's serialize function, 
          // or perform your default serialization here
    }
    // put your default serialization code here
    else CMyBaseDocument::Serialize();
}

Because of the way that the MFC code for document serialization works, the document's view will have to handle changing the document's path to the correct location. (By default, the path will be to the autorestored copy, but you want to restore the original path, so that the user can continue where they left off with as little hassle as possible.) In the OnInitialUpdate function of your view, check to see if the document's restore path has been set. If it has, then you know that the program has just loaded an autorecovered file, and the path and title of the document and view must be changed to the proper location.

CMyDoc *doc=GetDocument();
if(doc->m_restorepath.GetLength() > 0)
{
    doc->SetPathName(doc->m_restorepath,TRUE);
    doc->SetModifiedFlag(TRUE);
    doc->UpdateAllViews(NULL);
    m_autosave_names.push_back(doc->m_oldpath);
}

Your program must autorecover the files each time it loads. This is done by searching the autosave directory for all autosaved files and loading as needed from your InitInstance function. If you allow multiple instances of your program, make sure that the current instance is the only instance before recovering autosaved files.

BOOL bFound = FALSE;
HANDLE hMutexOneInstance = NULL;
#ifdef _WIN32
    hMutexOneInstance = 
      CreateMutex(NULL,TRUE,_T("PreventSecondInstanceMutex"));
    if(GetLastError() == ERROR_ALREADY_EXISTS)
    bFound = TRUE;
#else
    if(m_hPrevInstance != NULL)
    bFound = TRUE;
#endif

#ifdef _WIN32
    if(hMutexOneInstance) ReleaseMutex(hMutexOneInstance);
#endif 
    
.
.
.

if(bFound)
{
    if(CFF.FindFile((gm_autosaveDirectory+"\\"+ 
                   "*.MBK").GetBuffer(1),0)==TRUE)
    {
        if(::MessageBox(NULL,
          "Autosaved files found. Would you like to recover them?\n"
          "(WARNING: IF YOU PRESS NO, YOU WILL NOT BE ABLE TO RECOVER"
          " THE FILES IN THE FUTURE).", "MYDOC", MB_YESNO) != IDNO) 
        {
            while(CFF.FindNextFile() !=0)
            {
                CMyDoc *doc= 
                 (CMyDoc *)OpenDocumentFile(CFF.GetFilePath().GetBuffer(1));
            }
            OpenDocumentFile(CFF.GetFilePath().GetBuffer(1));
        }
        else
        {
            while(CFF.FindNextFile() !=0)
            {
                DeleteFile(CFF.GetFilePath().GetBuffer(1));
            }
            DeleteFile(CFF.GetFilePath().GetBuffer(1));
        }
    }
}

Use class wizard to add the PostNcDestroy member to your view class, and then delete the autosaved file there (it will only be deleted if the window closes normally). The following chunk of code will do that for you.

CFileFind CFF;
if(m_autosave_names.size() > 0)
{
    if(CFF.FindFile(((*m_autosave_names.begin())).c_str())==TRUE)
    {
        ::DeleteFile(((*m_autosave_names.begin())).c_str());
    }
    m_autosave_names.erase(m_autosave_names.begin());
}

Finally, at program close, these files should be deleted and the directory removed. In your ExitInstance function, delete the directory.

CFileFind CFF;
if(CFF.FindFile(gm_autosaveDirectory.GetBuffer(1))==TRUE)
   RemoveDirectory(gm_autosaveDirectory.GetBuffer(1));

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Jesse Ezell

United States United States
No Biography provided

Comments and Discussions

 
Questionerror LNK2019: unresolved external symbol Pinmemberdriver456711-Mar-13 5:40 
Questionan example please? anyone? Pinmembermark gooding25-Oct-05 4:02 
GeneralDoesn't work with COleDocument PinmemberVityusha5-Apr-05 9:03 
QuestionWhat about autosave in background? PinmemberConstructor18-Feb-04 0:16 
GeneralMultiple Views of a doc PinmemberCMYanko12-Mar-03 5:06 
GeneralDon't use the Windows directory... PinmemberDomenic [Geekn]17-Nov-02 18:25 
GeneralON_MESSAGE(...) PinmemberQuentin Pouplard29-Sep-02 12:15 
GeneralRe: ON_MESSAGE(...) PinsussAnonymous16-Jul-03 8:15 
GeneralAutoSave using API in VB PinmemberLakshmi Divya2-Aug-02 2:16 
QuestionHow about AutoSave encapsulated? PinsussBenjamin Ng27-Aug-00 22:45 
GeneralProblem with same filenames in different directories PinsussCarsten Sperber6-Mar-00 10:22 
GeneralUpdate / Notes PinsussJesse Ezell23-Feb-00 20:14 
QuestionWhy not WM_APP instead of WM_USER...? PinsussJames R. Twine23-Feb-00 6:57 
AnswerRe: Why not WM_APP instead of WM_USER...? PinsussJesse Ezell23-Feb-00 7:40 
GeneralRe: Why not WM_APP instead of WM_USER...? PinsussChris Maunder24-Feb-00 17:33 
GeneralMore easily retrieve Temp-path PinsussAlex Marbus22-Feb-00 4:34 
GeneralYour Welcome PinsussJesse Ezell21-Feb-00 7:45 
GeneralWow, thanks PinsussMichael21-Feb-00 1:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 25 Feb 2000
Article Copyright 2000 by Jesse Ezell
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid