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: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds