Storing Relative File Names

Environment: MS Visual C++ 6

Many MFC documents use CArchive to store the filenames of nested documents or project files. When the user copies the projects into different directories or to a different computer, the paths become invalid unless the programmer painstakingly controls these directory name dependencies. It also frustrates the user quite a lot if the application does not support such project movements. This article describes an easy way to solve this problem.

We will suppose that we are creating an MFC Document/View application and our archived CDocument contains the filenames of project-related files. For instance, the document contains a reference to the measured file somewhere and the settings file somewhere else. Typically, the user holds the measured data and settings data in the same root directory. Frequently, the settings are stored in some subfolder. So, we will obtain something like this (mdc means our measurement document extension):

c:\measurements\berlin\2002-10-12
  meas1.mdc
  data1.dat
  meas2.mdc
  data2.dat
  meas3.mdc
  data3.dat
c:\measurements\berlin\2002-10-12\settings
  setup.dat

Yes, all three mdc files refers to the same setup.dat internally, as you can imagine. Also, what do we do if we can expect that the user will move the project onto a file server? We can handle filenames as relative, but the application will contain a lot of code for name splitting and concatenating. Also, direct references (for instance, if setup.dat is in a completely separate directory, such as "c:\setups\") must be painstakingly controlled.

The following code tries to solve the problem in the root—in archive storing and reading. At that moment, we know the archive path and we can compare which file names are in the archive directory and its subfolders. By feeding _getDocRelativeFileName() with the full path of document archive and the full path of the stored file name, the method can decide whether the file name is relative to the archive. Also, from
  "c:\measurements\berlin\2002-10-12\data1.dat"
we will get
  "data1.dat"
and from
  "c:\measurements\berlin\2002-10-12\settings\setup.dat"
we will get
  "settings\setup.dat"

When reading back the archive, _getDocAbsoluteFileName() will restore the full file names. Also, when the user moves the project into
  "\\ourserver\measurements\berlin_0210",
we will read back the correct, fully qualified file names
  "\\ourserver\measurements\berlin_0210\data1.dat"
  "\\ourserver\measurements\berlin_0210\settings\setup.dat"

The code fragment presented is dirty and could be better, of course, but it works in many situations. Comments are included in the code fragment. Some additional safety checks can be implemented, of course. Currently, the code:

  • Converts only files in the project and nested directories to the path-relative file names
  • Works both for drive locations and server locations ("c:\something\" vs. "\\servername\something")
  • Does not serve directory-up file placement (like "..\settings\setup.dat")
  • Was not tested on a unicode build

You could need more sophisticated file position support; then, it is necessary either to enhance the presented functionality or to create other, similar function pairs. However, placement in the Serialize method is easy and understandable and it frees the rest of the application from lot of stupid work.

To use the code, simply copy it into the application document code (somewhere in xxxdoc.cpp, before the CxxxDoc::Serialize method) or make them methods of the CxxxDoc class. Add function calls to the Serialize method, as shown in the example. No more work is needed; the rest of application from this moment does not depend on current project placement.


// static functions implementation to operate with project-
// relative file names
// an include might be needed if not specified already somewhere

#include <stdlib.h>

// ===============================================================
// compare drive and path of the document and the filename;
// returns only the different part (eg. filename with extension if
// the file is in the same folder as the document file, or subpath
// and filename if file is in nested directory). Use
// _getDocAbsoluteFileName to revert to the full path.
// sDocName is the full file name of the archived CDocument;
// sSubFileName is the full file name of (possibly) project-nested
// data file.

static CString _getDocRelativeFileName( CString& sDocName,
                                        CString& sSubFileName )
{
    CString sResult = sSubFileName;

    char ddrive[_MAX_DRIVE];
    char ddir[_MAX_DIR];
    char dfname[_MAX_FNAME];
    char dext[_MAX_EXT];
    char fdrive[_MAX_DRIVE];
    char fdir[_MAX_DIR];
    char ffname[_MAX_FNAME];
    char fext[_MAX_EXT];

    _splitpath( sDocName, ddrive, ddir, dfname, dext );
    _splitpath( sSubFileName, fdrive, fdir, ffname, fext );

    if ( strlen(ddrive) > 0 || strlen(ddir) > 0 )
    {
        CString s = fdir;
        if ( CString( ddrive ) == CString( fdrive ) &&
            s.Find( ddir ) == 0 )
        {
            sResult = s.Mid( strlen( ddir ));
            sResult += ffname;
            sResult += fext;
        }
    }

    return sResult;
}

// Changes the relative file name from _getDocRelativeFileName
// to the absolute path. Checks for server name
// \\xxx and drive name x:\ to be sure to not expand fully
// qualified names. sDocName is the full file name of the restored
// CDocument. sSubFileName is the optionally shortened file name
// of the project-nested data file.

static CString _getDocAbsoluteFileName( CString& sDocName,
                                        CString& sSubFileName )
{
    CString sResult = sSubFileName;

    char ddrive[_MAX_DRIVE];
    char ddir[_MAX_DIR];
    char dfname[_MAX_FNAME];
    char dext[_MAX_EXT];

    _splitpath( sDocName, ddrive, ddir, dfname, dext );

    // there should not be a server of the disk identifier in
    // the relative name
    // (a little bit of safe expansion was used)
    if ( sSubFileName.Find( "\\\\" ) < 0 &&
         sSubFileName.Find( ":\\" ) < 0 )
    {
        sResult = CString(ddrive) + CString(ddir) + sSubFileName;
    }

    return sResult;
}

// example of the function use

void CMyAppDoc::Serialize(CArchive& ar)
{
    CString sPath;
    CFile* pFile = ar.GetFile();
    if ( pFile )
    {
        sPath = pFile->GetFilePath();
    }
    if (ar.IsStoring())
    {
        // TODO: add storing code here
        ar << _getDocRelativeFileName( sPath, m_sFileName1 );
        ar << _getDocRelativeFileName( sPath, m_sFileName2 );
    }
    else
    {
        // TODO: add loading code here
        CString s;
        ar >> s;
        m_sFileName1 = _getDocAbsoluteFileName( sPath, s );
        ar >> s;
        m_sFileName2 = _getDocAbsoluteFileName( sPath, s );
    }
}

// ===============================================================

Downloads

Please copy the code directly from the article body....


Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

  • Live Event Date: September 16, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you starting an on-premise-to-cloud data migration project? Have you thought about how much space you might need for your online platform or how to handle data that might be related to users who no longer exist? If these questions or any other concerns have been plaguing you about your migration project, check out this eSeminar. Join our speakers Betsy Bilhorn, VP, Product Management at Scribe, Mike Virnig, PowerSucess Manager and Michele …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds