123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
/*******************************************************************************

        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 const(char)[][]        paths;
        private int             index;
        private long            maxSize,
                                fileSize;

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

                A path of "my.log" will be expanded to "my.0.log".

                maxSize is the advisory maximum size of a single log-file,
                in bytes.

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

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

        this (const(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);

                // split the path into components
                auto c = Path.parse (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 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

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

        @property override final const Mask mask ()
        {
                return mask_;
        }

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

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

        @property override final const const(char)[] name ()
        {
                return this.classinfo.name;
        }

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

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

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

                    size_t write (const(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");

        }
}