/***************************************************************************
 *   Copyright (C) 2004 by Raul Fernandes                                  *
 *   rgfbr@yahoo.com.br                                         *
 *                                                                         *
 *   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 Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/

#include <qcstring.h>
#include <qsocket.h>
#include <qdatetime.h>
#include <qbitarray.h>
#include <qfile.h>

#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <kinstance.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <klocale.h>
#include <kurl.h>
#include <ksock.h>

#include <kprocio.h>
#include <kprocess.h>
#include <sys/stat.h>

#include "p7zip.moc"

using namespace KIO;


kio_p7zipProtocol::kio_p7zipProtocol(const QCString &pool_socket, const QCString &app_socket)
    : SlaveBase("kio_p7zip", pool_socket, app_socket)
{
    kdDebug() << "kio_p7zipProtocol::kio_p7zipProtocol()" << endl;
    p7zipProgram =  KGlobal::dirs()->findExe( "7za" );
    if( p7zipProgram.isNull() )// Check if 7za is available
      error( KIO::ERR_SLAVE_DEFINED, i18n( "The 7za binary was not found in PATH. You should install one of them to work with this kioslave" ) );
    archive = 0;
    archiveTime = 0;
}


kio_p7zipProtocol::~kio_p7zipProtocol()
{
    kdDebug() << "kio_p7zipProtocol::~kio_p7zipProtocol()" << endl;
}


void kio_p7zipProtocol::get(const KURL& url )
{
    kdDebug() << "kio_p7zip::get(const KURL& url)" << endl ;

    KURL archiveUrl;
    KURL fileUrl;
    if( !checkName( url, archiveUrl, fileUrl ) )
    {
      // it is not a 7zip archive
      // send the path to konqueror to redirect to file:/ kioslave
      redirection( url.path() );
      finished();
      return;
    }

    proc1 = new KProcess();
    processed = 0;
    connect( proc1, SIGNAL( receivedStdout( KProcess*, char*, int ) ), this, SLOT( receivedData( KProcess*, char*, int ) ) );

    proc1->setEnvironment( "LC_ALL", KGlobal::locale()->language() );
    *proc1 << p7zipProgram << "e" << "-so" << "-bd" << "-y" << archiveUrl.path() << fileUrl.path().remove( 0, 1 );

    infoMessage( i18n( "Unpacking file..." ) );
    proc1->start( KProcess::Block, KProcess::AllOutput );
    if( !proc1->normalExit() ) error( KIO::ERR_CANNOT_LAUNCH_PROCESS, url.path() );
    else if( proc1->exitStatus() != 0 ) error( KIO::ERR_SLAVE_DEFINED, i18n( "A error occurred when unpacking %1" ).arg( url.path() ) );

    data( QByteArray() ); // empty array means we're done sending the data
    finished();

    delete proc1;
    proc1 = 0;
}


//void kio_p7zipProtocol::mimetype(const KURL & /*url*/)
/*{
    mimeType("text/plain");
    finished();
}*/


extern "C"
{
    int kdemain(int argc, char **argv)
    {
        KInstance instance( "kio_p7zip" );
        
        kdDebug() << "*** Starting kio_p7zip " << endl;
        
        if (argc != 4) {
            kdDebug() << "Usage: kio_p7zip  protocol domain-socket1 domain-socket2" << endl;
            exit(-1);
        }
        
        kio_p7zipProtocol slave(argv[2], argv[3]);
        slave.dispatchLoop();
        
        kdDebug() << "*** kio_p7zip Done" << endl;
        return 0;
    }
} 


/*!
    \fn kio_p7zipProtocol::receivedData( KProcess *, char* buffer, int len )
 */
void kio_p7zipProtocol::receivedData( KProcess *, char* buffer, int len )
{
  QByteArray d(len);
  d.setRawData(buffer,len);
  data(d);
  d.resetRawData(buffer,len);
  processed += len;
  processedSize( processed );
}


/*!
    \fn kio_p7zipProtocol::checkName( const KURL &url, KURL &archiveUrl, KURL &fileUrl )
 */
bool kio_p7zipProtocol::checkName( const KURL &url, KURL &archiveUrl, KURL &fileUrl )
{
    if( url.path().find( ".7z", false ) == -1 ) return false;
    archiveUrl = url.path().section( ".7z", 0, 0 ) + ".7z";
    if( url.path().endsWith( ".7z" ) ) fileUrl = "/";
    else fileUrl = url.path().section( ".7z", 1 );
    return true;
}


/*!
    \fn kio_p7zipProtocol::stat( const KURL & url )
 */
void kio_p7zipProtocol::stat( const KURL & url )
{
    KURL archiveUrl;
    KURL fileUrl;
    if( !checkName( url, archiveUrl, fileUrl ) )
    {
      // it is not a 7zip archive
      // send the path to konqueror to redirect to file:/ kioslave
      redirection( url.path() );
      finished();
      return;
    }
    //messageBox( Information, QString( "stat\nurl: %1"/*\ndir: %2\nfileName: %3\nfile: %4"*/ ).arg( /*archiveName*/url.path() )/*.arg( dirName ).arg( fileName ).arg( file )*/ );

    UDSEntry entry;
    UDSAtom atom;

    // check if it is root directory
    if( fileUrl.path() == "/" )
    {
      atom.m_uds = KIO::UDS_NAME;
      atom.m_str = "/";
      entry.append(atom);
      atom.m_uds = KIO::UDS_FILE_TYPE;
      atom.m_long = S_IFDIR;
      entry.append(atom);
      statEntry(entry);
      finished();
      return;
    }

    listArchive( archiveUrl );

    QString linha;

    const QString file = fileUrl.path( -1 ).remove( 0, 1 );
    QString fileName;

    for( QStringList::Iterator it = archiveList.begin(); it != archiveList.end(); ++it )
    {
      linha = *it;
      if( linha.find( "----" ) != -1 )
      {
        error( KIO::ERR_DOES_NOT_EXIST, url.path() );
        return;
      }
      fileName = linha.mid( 53 ).stripWhiteSpace();
      if( fileName == file ) break;
    }

    // name of file
    atom.m_uds = KIO::UDS_NAME;
    atom.m_str = fileName.section( '\\', -1 );
    entry.append(atom);

    QStringList list = QStringList::split( ' ', linha );

    // type of file
    fileName = list[ 2 ];
    if( fileName[ 0 ] == 'D' )
    {
      atom.m_uds = KIO::UDS_FILE_TYPE;
      atom.m_long = S_IFDIR;
      entry.append( atom );
    }else
    {
      atom.m_uds = KIO::UDS_FILE_TYPE;
      atom.m_long = S_IFREG;
      entry.append( atom );
    }

    // size
    atom.m_uds = KIO::UDS_SIZE;
    atom.m_long = list[ 3 ].toLong();
    entry.append(atom);

    statEntry(entry);
    finished();
}


/*!
    \fn kio_p7zipProtocol::listDir( const KURL & url )
 */
void kio_p7zipProtocol::listDir( const KURL & url )
{
    KURL archiveUrl;
    KURL fileUrl;
    if( !checkName( url, archiveUrl, fileUrl ) )
    {
      // it is not a 7zip archive
      // send the path to konqueror to redirect to file:/ kioslave
      redirection( url.path() );
      finished();
      return;
    }
    //messageBox( Information, QString( "listDir\nurl: %1\ndir: %2\nprogram: %3" ).arg( archiveUrl.path() ).arg( archiveUrl.path() ).arg( p7zipProgram ) );//.arg( fileName ).arg( file ) );

    infoMessage( i18n( "Listing directory..." ) );
    listArchive( archiveUrl );

    UDSEntry entry;
    UDSAtom atom;
    int pos;
    QString linha;

    const QString file = fileUrl.path( -1 ).remove( 0, 1 );
    QString fileName;

    for( QStringList::Iterator it = archiveList.begin(); it != archiveList.end(); ++it )
    {
      linha = *it;
      if( linha.find( "----" ) != -1 ) break;

      entry.clear();

      fileName = linha.mid( 53 ).stripWhiteSpace();
      kdDebug() << "listDir\nlinha: " << fileName << endl;
      if( !file.isEmpty() )
        if( fileName.find( file ) == 0 )
        {
          if( fileName == file ) continue;
          fileName = fileName.mid( file.length() + 1 );
        }else continue;

      pos = fileName.find( '/' );
      if( pos != -1 ) continue;

      // name of file
      atom.m_uds = KIO::UDS_NAME;
      atom.m_str = fileName.section( '/', -1 );
      entry.append(atom);

      QStringList list = QStringList::split( ' ', linha );

      // type of file
      fileName = list[ 2 ];
      if( fileName[ 0 ] == 'D' )
      {
        atom.m_uds = KIO::UDS_FILE_TYPE;
        atom.m_long = S_IFDIR;
        entry.append( atom );
      }else
      {
        atom.m_uds = KIO::UDS_FILE_TYPE;
        atom.m_long = S_IFREG;
        entry.append( atom );
      }

      // size
      atom.m_uds = KIO::UDS_SIZE;
      atom.m_long = list[ 3 ].toLong();
      entry.append(atom);

      // time
      atom.m_uds = KIO::UDS_MODIFICATION_TIME;
      QDate date( list[ 0 ].mid( 0, 4 ).toInt(), list[ 0 ].mid( 5, 2 ).toInt(), list[ 0 ].mid( 8, 2 ).toInt() );
      QTime time( list[ 1 ].mid( 0, 2 ).toInt(), list[ 1 ].mid( 3, 2 ).toInt(), list[ 1 ].mid( 6, 2 ).toInt() );
      atom.m_long = QDateTime( date, time ).toTime_t();
      entry.append(atom);

      // send entry
      listEntry(entry, false);
    }

    listEntry( entry, true);
    finished();
}


/*!
    \fn kio_p7zipProtocol::put(const KURL& url,int permissions,bool overwrite,bool resume)
 */
void kio_p7zipProtocol::put(const KURL& url,int /*permissions*/,bool /*overwrite*/,bool /*resume*/)
{
    KURL archiveUrl;
    KURL fileUrl;
    if( !checkName( url, archiveUrl, fileUrl ) )
    {
      // it is not a 7zip archive
      // return the error that cannot enter the directory
      error(ERR_CANNOT_ENTER_DIRECTORY,url.path());
      return;
    }

    infoMessage( i18n( "Adding file..." ) );
    //messageBox( Information, QString( "put\nurl: %1\narchiveUrl: %2\nfileUrl: %3" ).arg( url.path() ).arg( archiveUrl.path() ).arg( fileUrl.path().remove( 0, 1 ) ) );

    // receive the data
    QByteArray buffer;
    int length;
    QFile file( "/tmp/" + fileUrl.fileName() );
    file.open( IO_WriteOnly );
    do
    {
      dataReq();
      length = readData( buffer );
      file.writeBlock( buffer );
    }while( length > 0 );
    file.close();

    proc = new KProcIO();
    //proc->setUseShell( true );//-t7z -m0=lzma 
    proc->setEnvironment( "LC_ALL", KGlobal::locale()->language() );
    *proc << p7zipProgram << "a" << archiveUrl.path() << file.name();
    proc->start( KProcess::Block, KProcess::NoCommunication );
    if( !proc->normalExit() ) error( KIO::ERR_CANNOT_LAUNCH_PROCESS, url.path() );
    else if( proc->exitStatus() != 0 ) error( KIO::ERR_SLAVE_DEFINED, i18n( "A error occurred when adding %1" ).arg( url.path() ) );

    //delete the temporary file
    file.remove();

    delete proc;
    proc = 0;

    finished();
}


/*!
    \fn kio_p7zipProtocol::listArchive( const KURL &archiveUrl )
 */
bool kio_p7zipProtocol::listArchive( const KURL &archiveUrl )
{
    if( archive == archiveUrl )
    {  // Has it changed ?
      struct stat statbuf;
      if ( ::stat( QFile::encodeName( archiveUrl.path() ), &statbuf ) == 0 )
      {
        if ( archiveTime == statbuf.st_mtime )
          return true;
      }
    }
    archive = archiveUrl;
    proc = new KProcIO();
    proc->setEnvironment( "LC_ALL", KGlobal::locale()->language() );
    *proc << p7zipProgram << "l" << archive.path();
    proc->start( KProcess::Block );
    //if( !proc->normalExit() ) error( KIO::ERR_CANNOT_LAUNCH_PROCESS, archive.path() );

    archiveList.clear();
    QString linha;
    while( proc->readln( linha ) != -1 )
      if( linha.find( "----" ) != -1 ) break;

    while( proc->readln( linha ) != -1 )
    {
      if( linha.find( "----" ) != -1 ) break;
      archiveList.append( linha );
    }

    delete proc;
    proc = 0;
    return true;
}


/*!
    \fn kio_p7zipProtocol::del( const KURL &url, bool isFile )
 */
void kio_p7zipProtocol::del( const KURL &url, bool /*isFile*/ )
{
    KURL archiveUrl;
    KURL fileUrl;
    if( !checkName( url, archiveUrl, fileUrl ) )
    {
      // it is not a 7zip archive
      // return the error that cannot enter the directory
      error(ERR_CANNOT_ENTER_DIRECTORY,url.path());
      return;
    }

    infoMessage( i18n( "Deleting file..." ) );
    //messageBox( Information, QString( "del\nurl: %1\narchiveUrl: %2\nfileUrl: %3" ).arg( url.path() ).arg( archiveUrl.path() ).arg( fileUrl.path().remove( 0, 1 ) ) );

    proc1 = new KProcess();
    proc1->setUseShell( true );
    proc1->setEnvironment( "LC_ALL", KGlobal::locale()->language() );
    *proc1 << p7zipProgram << "d" << "\"" + archiveUrl.path() + "\"" << "\"" + fileUrl.path().remove( 0, 1 ) + "\"";
    proc1->start( KProcess::Block, KProcess::NoCommunication );
    if( !proc1->normalExit() ) error( KIO::ERR_CANNOT_LAUNCH_PROCESS, url.path() );
    else if( proc1->exitStatus() != 0 ) error( KIO::ERR_SLAVE_DEFINED, i18n( "A error occurred when deleting %1" ).arg( url.path() ) );

    finished();
}
