Tsaras
Tsaras

Reputation: 493

How to handle saving and loading different versions of a file?

In making an application that saves files with a specific format which in the future will have added or different functionality, requiring the saved file to have a different format, are there any techniques available to handle this "versioning"?

I would be interested in reading into some of them explaining how it is possible to load all the possible formats of the saved file that were created by the different versions of the application.

My idea currently is to save a version indicator in the saved file and use distinct load functions for every "version" that had it's own format, trying to tie them all with the current functionality of the latest version of the app.

Upvotes: 3

Views: 1042

Answers (1)

Spektre
Spektre

Reputation: 51873

This is mostly opinion based so handle it as such... Here are my insights on the topic:

  1. fileformat

    You should have 2 identificators. One for file format sometimes called magic number and second version. Both should be somewhere at the start of file and usually encoded as ASCII so you can easily check them with notepad or whatever.

    Its a good idea to have chunks of data with they own type and version identificators.

  2. loader - single fileformat detector

    I use this to check for specific fileformat. The input is array (small usually 1Kbyte) holding first bytes of file, array size and file size. The function checks if the file is valid file of some type. This is used to autodetect fileformat and not relay on file extension (necessity on Windows and low grade users as they often corrupt the file extention)

    The function returns true/false after checking identificators (and or file logic)

  3. loader - single fileformat

    This should load file into your app. For multi versions you got 2 options. Either have separate code for each version or one function partitioned with if statements like this:

    if (version==???) ... 
    if (version>=???) ... else ...
    if ((version>=???)&&(version<???)) ...
    

    to manage the diferent parts.

    I prefer the partitioned code approach as its usually less code and better manageable because different versions usually adds just some minor changes and most of the code states the same.

  4. loader - multi fileformat

    Simply load first bytes of file into memory and check all the supported fileformats using function from #2. Once succesfully detected fileformat load the file using its loader function from #3. If no fileformat detected then use file extention ...

Here simple C++/VCL example of #4 from my SVG loader class:

bool decode_interface_class::load(AnsiString name)
    {
    int hnd=-1;
    int siz=0,siz0=0;
    BYTE *dat=NULL;
    reset();
    #ifdef decode_interface_log
    decode_id.num=0;
    decode_log="";
    #endif
    decode_cfg =true;
    decode_col =true;
    decode_tool=true;
    decode_ext=ExtractFileExt(name).LowerCase();
    decoded_ext=".";
    decoded_info="";

    decode_emf emf;
    decode_wmf wmf;
    decode_dkr dkr;
    decode_dk3 dk3;
    decode_box box;
    decode_bxl bxl;
    decode_dxf dxf;
    decode_svg svg;
    decode_v2x v2x;
    decode_v2d v2d;

    const int _size=4096;
    BYTE head[_size];
    #ifdef decode_interface_log
    siz=0;  // find minimal size
    if (siz<_decode_emf_hdr) siz=_decode_emf_hdr;
    if (siz<_decode_wmf_hdr) siz=_decode_wmf_hdr;
    if (siz<_decode_dkr_hdr) siz=_decode_dkr_hdr;
    if (siz<_decode_dk3_hdr) siz=_decode_dk3_hdr;
    if (siz<_decode_box_hdr) siz=_decode_box_hdr;
    if (siz<_decode_bxl_hdr) siz=_decode_bxl_hdr;
    if (siz<_decode_dxf_hdr) siz=_decode_dxf_hdr;
    if (siz<_decode_svg_hdr) siz=_decode_svg_hdr;
    if (siz<_decode_v2x_hdr) siz=_decode_v2x_hdr;
    if (siz<_decode_v2d_hdr) siz=_decode_v2d_hdr;
    if (siz>_size)
        {
        decode_log+="Decoding header size too small needed to be "+AnsiString(siz)+" Bytes.\r\n";
        }
    #endif


    hnd=FileOpen(name,fmOpenRead);
    if (hnd<0)
        {
        #ifdef decode_interface_log
        decode_log+="File "+name+" not found.\r\n";
        #endif
        return false;
        }
    siz=FileSeek(hnd,0,2);
        FileSeek(hnd,0,0);
    dat=new BYTE[siz];
    if (dat==NULL)
        {
        #ifdef decode_interface_log
        decode_log+="Not enough memory need: "+AnsiString(siz)+" Bytes.\r\n";
        #endif
        FileClose(hnd);
        return false;
        }
    siz0=siz;
    siz=FileRead(hnd,dat,siz);
    FileClose(hnd);
    if (siz!=siz0)
        {
        #ifdef decode_interface_log
        decode_log+="Disc drive or file system error.\r\n";
        #endif
        }

    this[0].filename=name;

    // file signature detection
    for (int i=0;i<_size;i++) if (i<siz) head[i]=dat[i]; else head[i]=0;
         if (emf.is_header(head,_size,siz)) { decoded_ext=_decode_emf_ext; emf.load(this[0],dat,siz); }
    else if (wmf.is_header(head,_size,siz)) { decoded_ext=_decode_wmf_ext; wmf.load(this[0],dat,siz); }
    else if (dkr.is_header(head,_size,siz)) { decoded_ext=_decode_dkr_ext; dkr.load(this[0],dat,siz); }
    else if (dk3.is_header(head,_size,siz)) { decoded_ext=_decode_dk3_ext; dk3.load(this[0],dat,siz); }
    else if (box.is_header(head,_size,siz)) { decoded_ext=_decode_box_ext; box.load(this[0],dat,siz); }
    else if (bxl.is_header(head,_size,siz)) { decoded_ext=_decode_bxl_ext; bxl.load(this[0],dat,siz); }
    else if (dxf.is_header(head,_size,siz)) { decoded_ext=_decode_dxf_ext; dxf.load(this[0],dat,siz); }
    else if (svg.is_header(head,_size,siz)) { decoded_ext=_decode_svg_ext; svg.load(this[0],dat,siz); }
    else if (v2x.is_header(head,_size,siz)) { decoded_ext=_decode_v2x_ext; v2x.load(this[0],dat,siz); }
    else if (v2d.is_header(head,_size,siz)) { decoded_ext=_decode_v2d_ext; v2d.load(this[0],dat,siz); }
    // if fail use file extension
    else if (decode_ext==_decode_emf_ext)   { decoded_ext=_decode_emf_ext; emf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_wmf_ext)   { decoded_ext=_decode_wmf_ext; wmf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_dkr_ext)   { decoded_ext=_decode_dkr_ext; dkr.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_dk3_ext)   { decoded_ext=_decode_dk3_ext; dk3.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_box_ext)   { decoded_ext=_decode_box_ext; box.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_bxl_ext)   { decoded_ext=_decode_bxl_ext; bxl.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_dxf_ext)   { decoded_ext=_decode_dxf_ext; dxf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_svg_ext)   { decoded_ext=_decode_svg_ext; svg.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_v2x_ext)   { decoded_ext=_decode_v2x_ext; v2x.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    else if (decode_ext==_decode_v2d_ext)   { decoded_ext=_decode_v2d_ext; v2d.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
    // if fail then error
    else{
        #ifdef decode_interface_log
        decode_log+="File "+name+" not recognized.\r\n";
        #endif
        }
    if (decode_cfg)
        {
        if (!decode_col )
            {
            if (decode_tool) set_cfgs  (dk3_charaktool ,33);
                             set_colors(dk3_charakcolor,33);
            }
        if (!decode_tool)    set_tools (dk3_charaktool ,33);
        }
    #ifdef decode_interface_log
    if (decode_ext!=decoded_ext)
        decode_log+="Wrong file extension in "+name+" should be \""+decoded_ext+"\"\r\n";
    hnd=FileCreate(ExtractFilePath(Application->ExeName)+"svg_decode.log");
    FileWrite(hnd,decode_log.c_str(),decode_log.Length());
    FileClose(hnd);
    #endif
    compute();
    compute_objsize();
    if (dat) delete[] dat;
    return true;
    }

Each fileformat has defined its header size _decode_???_hdr in bytes and default file extention _decode_??_ext for the detection of fileformat. Functions ???.is_header(...) are the #2 and ???.load(...) are the #3. I am using loading from memory instead of direct file access because its better suite my needs. However this is not convenient for too big files.

Upvotes: 1

Related Questions