123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
/*******************************************************************************

        copyright:      Copyright (c) 2004 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)
      
        version:        Initial release: May 2004
        
        author:         Kris

*******************************************************************************/

module tango.util.log.AppendFiles;

private import  tango.time.Time;

private import  Path = tango.io.Path,
                tango.io.device.File;

private import  tango.io.model.IFile;

private import  tango.util.log.Log,
                tango.util.log.AppendFile;

/*******************************************************************************

        Append log messages to a file set. 

*******************************************************************************/

public class AppendFiles : Filer
{
        private Mask            mask_;
        private char[][]        paths;
        private int             index;
        private long            maxSize,
                                fileSize;

        /***********************************************************************
                
                Create a RollingFileAppender upon a file-set with the 
                specified path and optional layout. The minimal file
                count is two and the maximum is 1000. Note that files
                are numbered starting with zero rather than one.

                Where a file set already exists, we resume appending to 
                the one with the most recent activity timestamp.

        ***********************************************************************/

        this (char[] path, int count, long maxSize, Appender.Layout how = null)
        {
                --count;
                assert (path);
                assert (count > 0 && count < 1000);

                // Get a unique fingerprint for this instance
                mask_ = register (path);

                char[3] x;
                Time mostRecent;

                for (int i=0; i <= count; ++i)
                    {
                    x[0] = cast(char)('0' + i/100);
                    x[1] = cast(char)('0' + i/10%10);
                    x[2] = cast(char)('0' + i%10);
                    auto c = Path.parse (path);
                    auto p = c.toString[0..$-c.suffix.length] ~ x ~ c.suffix;
                    paths ~= p;

                    // use the most recent file in the set
                    if (Path.exists(p))
                       {
                       auto modified = Path.modified(p);
                       if (modified > mostRecent)
                          {
                          mostRecent = modified;
                          index = i;
                          }
                       }
                    }

                // remember the maximum size 
                this.maxSize = maxSize;

                // adjust index and open the appropriate log file
                --index; 
                nextFile (false);

                // set provided layout (ignored when null)
                layout (how);
        }

        /***********************************************************************
                
                Return the fingerprint for this class

        ***********************************************************************/

        final Mask mask ()
        {
                return mask_;
        }

        /***********************************************************************
                
                Return the name of this class

        ***********************************************************************/

        final char[] name ()
        {
                return this.classinfo.name;
        }

        /***********************************************************************
                
                Append an event to the output.
                 
        ***********************************************************************/

        final synchronized void append (LogEvent event)
        {
                char[] msg;

                // file already full?
                if (fileSize >= maxSize)
                    nextFile (true);

                size_t write (void[] content)
                {
                        fileSize += content.length;
                        return buffer.write (content);
                }

                // write log message and flush it
                layout.format (event, &write);
                write (FileConst.NewlineString);
                buffer.flush; 
        }

        /***********************************************************************
                
                Switch to the next file within the set

        ***********************************************************************/

        private void nextFile (bool reset)
        {
                // select next file in the set
                if (++index >= paths.length)
                    index = 0;
                
                // close any existing conduit
                close;

                // make it shareable for read
                auto style = File.WriteAppending;
                style.share = File.Share.Read;
                auto conduit = new File (paths[index], style);

                configure (conduit);

                // reset file size
                if (reset)
                    conduit.truncate (fileSize = 0);
                else
                   fileSize = conduit.length;
        }
}

/*******************************************************************************

*******************************************************************************/

debug (AppendFiles)
{
        void main()
        {
                Log.root.add (new AppendFiles ("foo", 5, 6));
                auto log = Log.lookup ("fu.bar");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");
                log.trace ("hello {}", "world");

        }
}