view mde/file/mergetag/Writer.d @ 151:e785e98d3b78

Updated for compatibility with tango 0.99.8.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 04 Apr 2009 17:32:18 +0200
parents 9f035cd139c6
children
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

/**************************************************************************************************
 * This module contains all writing functions, for both binary and text MergeTag files.
 *
 * Files can be written in a text or binary form; binary is faster and smaller while text allows
 * editing with an ordinary text editor. TextWriter and BinaryWriter are the main classes, both of
 * which implement the interface IWriter. DualWriter is another class implementing IWriter, which
 * contains a private instance of a TextWriter and a BinaryWriter and implements all methods in the
 * interface simply by chaining the appropriate method from each of these classes, thus performing
 * two writes at once.
 *
 * Any of these three classes may be used directly, or makeWriter may be invoked to create an
 * instance of the appropriate class.
 *************************************************************************************************/
 module mde.file.mergetag.Writer;

// package imports
public import mde.file.mergetag.iface.IWriter;
import mde.file.mergetag.DataSet;
import mde.file.mergetag.internal;
import mde.file.mergetag.exception;

// tango imports
import tango.core.Exception;
import tango.io.device.File;
import tango.io.stream.Buffered;
import tango.util.log.Log : Log, Logger;

private Logger logger;
static this () {
    logger = Log.getLogger ("mde.file.mergetag.Writer");
}


/** Method to create and return either a MTTWriter or a MTBWriter.
 *
 * Has two modes of operation: if method is FromExtension, examines the existing extension and
 * creates a MTT/MTB writer if the extension is mtt or mtb (throwing if not).
 *
 * Otherwise, writing format is determined directly by method, and appropriate extensions are
 * added to the file name without checking for an existing extension.
 *
 * Params:
 *  path = File path
 *  dataset = Dataset passed to Writer to write from (if null, must be set before write() is called)
 *  method = $(TABLE
 *      $(TR $(TH Value)            $(TH Writer returned)       $(TH Suffix added))
 *      $(TR $(TD FromExtension)    $(TD MTBWriter or MTTWriter)$(TD $(I none)))
 *      $(TR $(TD Binary)           $(TD MTBWriter)             $(TD .mtb))
 *      $(TR $(TD Text)             $(TD MTTWriter)             $(TD .mtt))
 *      $(TR $(TD Both)             $(TD DualWriter)            $(TD .mtb / .mtt))
 *  )
 *
 * Throws:
 *  MTFileFormatException if neither test can deduce the writing method, the supplied writing
 *  method is invalid or the determined/supplied method is not yet implemented.
 *
 * Use as:
 * -----------------------
 * DataSet dataset; // contains data to write
 * IWriter foo;
 * try {
 *   foo = makeWriter(...);
 *   foo.write();
 * }
 * catch (MTException) {}
 * -----------------------
 * Where the makeWriter line has one of the following forms:
 * -----------------------
 *   foo = makeWriter("foo.mtt", dataset);
 *   foo = makeWriter("foo", dataset, WriterMethod.Text);
 * -----------------------
 *
 * Throws:
 *  MTFileFormatException if unable to determine writing format or use requested format.
 */
//FIXME: separate functions for separate functionality, like in Reader?
IWriter getMTWriter (char[] path, DataSet dataset = null,
                    WriterMethod method = WriterMethod.FromExtension) {
    if (method == WriterMethod.FromExtension) {
        if (path.length > 4 && path[$-4..$] == ".mtt")
            return new MTTWriter (path, dataset);
        else if (path.length > 4 && path[$-4..$] == ".mtb")
            return new MTBWriter (path, dataset);
        else {
            logger.error ("Unable to determine writing format: text or binary");
            throw new MTFileFormatException;
        }
    }
    else {
        if (method == WriterMethod.Binary) return new MTBWriter (path~".mtb", dataset);
        else if (method == WriterMethod.Text) return new MTTWriter (path~".mtt", dataset);
        else if (method == WriterMethod.Both) return new DualWriter (path, dataset);
        else throw new MTFileFormatException;
    }
}


/**
 * Class to write a dataset to a file.
 *
 * Files are only actually open for writing while the write() method is running.
 *
 * Throws:
 *  $(TABLE
 *  $(TR $(TH Exception) $(TH Thrown when))
 *  $(TR $(TD MTNoDataSetException) $(TD No dataset is available to write from))
 *  $(TR $(TD MTFileIOException) $(TD An error occurs while attemting to write the file))
 *  $(TR $(TD MTException) $(TD An unexpected error occurs))
 *  )
 * Note that all exceptions extend MTException; unlike Reader exceptions don't block further calls.
 */
class MTTWriter : IWriter
{
//BEGIN DATA
    /// Get or set the DataSet (i.e. the container from which all data is written).
    DataSet dataset () {	return _dataset;	}
    void dataset (DataSet ds)	/// ditto
    {	_dataset = ds;	}
        
    
private:
    /* The container where data is written from. */
    DataSet _dataset;
    
    char[] _path;
//END DATA
    
//BEGIN CTOR / DTOR
    /** Prepares to open file path for writing.
    *
    * The call doesn't actually execute any code so cannot fail (unless out of memory).
    *
    * Params:
    * path = The name of the file to open.
    *     Standard extensions are .mtt and .mtb for text and binary files respectively.
    * dataset_ = If null create a new DataSet, else use existing DataSet *dataset_ and merge read
    *     data into it.
    */
    public this (char[] path, DataSet ds = null) {
        _path = path;
        _dataset = ds;
    }
//END CTOR / DTOR
    
    /** Writes the header and all DataSections.
     *
     * Firstly writes the header unless it has already been written. Then writes all DataSections
     * to the file. Thus if write is called more than once with or without changing the DataSet the
     * header should be written only once. This behaviour could, for instance, be used to write
     * multiple DataSets into one file without firstly merging them. Note that this behaviour may
     * be changed when binary support is added.
     */
    public void write ()
    {
        if (!_dataset) throwMTErr ("write(): no Dataset available to write from!", new MTNoDataSetException ());
        
        try {
            scope File conduit;		// actual conduit; don't use directly when there's content in the buffer
            scope BufferedOutput buffer;	// write strings directly to this (use opCall(void[]) )
            
            // Open a conduit on the file:
            conduit = new File (_path, File.WriteCreate);
            scope(exit) conduit.close();
            
            buffer = new BufferedOutput(conduit);	// And a buffer
            scope(exit) buffer.flush();
            
            // Write the header:
            buffer.append ("{" ~ CurrentVersionString ~ "}" ~ Eol);
            if (_dataset.header !is null) writeSection (buffer, _dataset.header);
        
            // Write the rest:
            foreach (ID id, IDataSection sec; _dataset.sec) {
                writeSectionIdentifier (buffer, id);
                writeSection (buffer, sec);
            }
            
            buffer.flush();
            
        }
        catch (IOException e) {
            throwMTErr ("Error writing to file: " ~ e.msg, new MTFileIOException);
        }
        catch (Exception e) {
            throwMTErr ("Unexpected exception when writing file: " ~ e.msg);
        }
    }
        
    private void writeSectionIdentifier (BufferedOutput buffer, ID id) {
        buffer.append ("{" ~ id ~ "}" ~ Eol);
    }
    
    private void writeSection (BufferedOutput buffer, IDataSection sec) {
        void writeItem (char[] tp, ID id, char[] dt) {	// actually writes an item
            buffer.append ("<" ~ tp ~ "|" ~ id ~"=" ~ dt ~ ">" ~ Eol);
        }
        sec.writeAll (&writeItem);
        
        buffer.append (Eol);			// blank line at end of each section
    }
    
    private void throwMTErr (char[] msg, Exception exc = new MTException) {
        logger.error (msg);		// report the error
        throw exc;			// and signal our error
    }
}

/*
* Implement MTBWriter (and move both writers to own modules?).
*/
class MTBWriter : IWriter {
    public this (char[] path, DataSet ds = null) {
        throw new MTNotImplementedException;
        
        /+_path = path;
        _dataset = ds;+/
    }
    
    DataSet dataset () {
        return null;
    }
    void dataset (DataSet) {}
    
    void write () {}
}

/* Basic implementation for mtt only.
*
*Implement std CTORs which add extensions to each filename and extra CTORs which take two filenames.
*/
class DualWriter : IWriter {
    /** The individual writers.
    *
    * Potentially could be used directly, but there should be no need. */
    MTTWriter mtt;
    //MTBWriter mtb;  /** ditto */
    
    public this (char[] path, DataSet ds = null) {
        mtt = new MTTWriter (path~".mtt", ds);
    }
    
    DataSet dataset () {
        return mtt.dataset;
    }
    void dataset (DataSet ds) {
        mtt.dataset = ds;
    }
    
    /** Write.
    *
    * Write text then binary, so the mtb file will be the most recent.
    */
    void write () {
        mtt.write();
    }
}