/* partspace.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2008,2010 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "partspace.h"
#include "aguix/lowlevelfunc.h"
#include "aguix/util.h"

static void *PS_slavestart( void *arg )
{
    PartSpace *ps = (PartSpace*)arg;
    ps->slavehandler();
    return NULL;
}

PartSpace::PartSpace()
{
    m_slave.running = 0;
    m_slave.stop = false;
    if ( pthread_create( &m_slave.th, NULL, PS_slavestart, this ) != 0 ) {
        fprintf( stderr, "Worker: Can't create thread, aborting!\n" );
        exit( EXIT_FAILURE );
    }
    m_info_lifetime = 10.0;  // force update after this time
    m_maxsize = 100;
}

PartSpace::~PartSpace()
{
    m_requests_lock.lock();
    m_slave.stop = true;
    m_requests_lock.signal();
    m_requests_lock.unlock();
    pthread_join( m_slave.th, NULL );
}

int PartSpace::readSpace( const char* name )
{
    time_t ct;
    bool do_update = false;
    worker_struct_stat buf1;
    dev_t name_device;

    if ( name == NULL ) return EFAULT;
    if ( worker_stat( name, &buf1 ) != 0 ) return EFAULT;
    if ( buf1.st_dev == 0 ) return EFAULT;
    name_device = buf1.st_dev;

    ct = time( NULL );

    blocksize = 0;
    space = 0;
    freespace = 0;

    int ret = EFAULT;

    m_data_lock.lock();
    if ( m_space_data.count( name_device  ) > 0 ) {
        switch ( m_space_data[name_device].m_stats_valid ) {
            case SpaceData::VALID:
            case SpaceData::PENDING_VALID:
#ifdef HAVE_STATVFS
                blocksize = m_space_data[name_device].m_fsstatistic.f_frsize;
                space = m_space_data[name_device].m_fsstatistic.f_blocks;
                freespace = m_space_data[name_device].m_fsstatistic.f_bavail;
#elif defined( HAVE_STATFS )
                blocksize = m_space_data[name_device].m_fsstatistic.f_bsize;
                space = m_space_data[name_device].m_fsstatistic.f_blocks;
                freespace = m_space_data[name_device].m_fsstatistic.f_bavail;
#else
                blocksize = 0;
                space = 0;
                freespace = 0;
#endif
                ret = 0;
                break;
            case SpaceData::UNAVAIL:
                ret = EFAULT;
                break;
            default:
                ret = EAGAIN;
                break;
        }

        if ( ( ct - m_space_data[name_device].m_last_update ) >= m_info_lifetime ) {
            do_update = true;
        }
    } else {
        do_update = true;
        ret = EAGAIN;
    }
    if ( do_update == true ) {
        m_space_data[name_device].m_last_update = ct;
        if ( m_space_data[name_device].m_stats_valid == SpaceData::VALID ) {
            m_space_data[name_device].m_stats_valid = SpaceData::PENDING_VALID;
        } else {
            m_space_data[name_device].m_stats_valid = SpaceData::PENDING_INVALID;
        }
    }

    if ( (int)m_space_data.size() > m_maxsize ) {
        //TODO shrink it down
    }

    m_data_lock.unlock();

    if ( do_update == true ) {
        m_requests_lock.lock();

        m_requests.push_back( std::pair< dev_t, std::string >( name_device, name ) );

        m_requests_lock.signal();
        m_requests_lock.unlock();
    }

    return ret;
}

loff_t PartSpace::getBlocksize()
{
    return blocksize;
}

loff_t PartSpace::getFreeSpace()
{
    return freespace;
}

loff_t PartSpace::getSpace()
{
    return space;
}

// H stands for human readable

std::string PartSpace::getFreeSpaceH() const
{
    double dfree;

    dfree = (double)freespace;
    dfree *= (double)blocksize;

    return AGUIXUtils::bytes_to_human_readable_f( dfree, 10, 0 );
}

std::string PartSpace::getSpaceH() const
{
    double dsize;

    dsize = (double)space;
    dsize *= (double)blocksize;

    return AGUIXUtils::bytes_to_human_readable_f( dsize, 10, 0 );
}

void PartSpace::slavehandler()
{
    bool ende;
  
    if ( m_slave.running != 0 ) {
        fprintf( stderr, "Worker: another thread already running!\n");
        return;
    }
    m_slave.running = 1;
#ifdef DEBUG
    printf("entering slave handler\n");
#endif

    m_requests_lock.lock();
    for( ende = false; ende == false; ) {
        // wait for next element or stop
        while ( m_requests.empty() &&
                m_slave.stop == false ) {
            m_requests_lock.wait();
        }

#ifdef DEBUG
        printf("wait finished\n");
#endif
        if ( m_slave.stop == false ) {

            while ( ! m_requests.empty() ) {
                dev_t name_device = m_requests.front().first;
                std::string name = m_requests.front().second;
                m_requests.pop_front();
                m_requests_lock.unlock();

#ifdef HAVE_STATVFS
                worker_struct_statvfs fsstatistic;
#elif defined( HAVE_STATFS )
                struct statfs fsstatistic;
#else
                int fsstatistic;
#endif
                int error = getSpace( name.c_str(), &fsstatistic );

                m_data_lock.lock();

                if ( error == 0 ) {
                    m_space_data[name_device].m_fsstatistic = fsstatistic;
                    m_space_data[name_device].m_stats_valid = SpaceData::VALID;
                } else {
                    m_space_data[name_device].m_stats_valid = SpaceData::UNAVAIL;
                }

                m_data_lock.unlock();

                m_requests_lock.lock();
            }
        } else {
            ende = true;
        }
    }
    m_requests_lock.unlock();
    
#ifdef DEBUG
    printf("leaving slave handler\n");
#endif
}

#ifdef HAVE_STATVFS
int PartSpace::getSpace( const char *name, worker_struct_statvfs *statbuf )
#elif defined( HAVE_STATFS )
int PartSpace::getSpace( const char *name, struct statfs *statbuf )
#else
int PartSpace::getSpace( const char *name, int *statbuf )
#endif
{
    if ( name == NULL ) return EFAULT;
    if ( statbuf == NULL ) return EFAULT;

#ifdef HAVE_STATVFS
    return worker_statvfs( name, statbuf );
#elif defined( HAVE_STATFS )
    return statfs( name, statbuf );
#else
    return 0;
#endif
}

void PartSpace::setLifetime( double t )
{
    // at least one second is minimum
    if ( t < 1.0 ) return;
  
    m_info_lifetime = t;
}
