/***************************************************************************
 * $Id: update.cpp,v 1.26 2008/12/08 19:39:02 hoganrobert Exp $
 *   Copyright (C) 2006 - 2008 Robert Hogan                                *
 *   robert@roberthogan.net                                                *
 *                                                                         *
 *   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 <tqtimer.h>
#include <tqdir.h>
#include <tqcheckbox.h>
#include <tqlayout.h>

#include <kstaticdeleter.h>
#include <tdelocale.h>
#include <tdeio/netaccess.h>
#include <tdeaction.h>
#include <kbuttonbox.h>
#include <kurlcompletion.h>
#include <kcombobox.h>
#include <tdemessagebox.h>
#include <klineedit.h>
#include <tdetempfile.h>
#include <ksystemtray.h>
#include <ktar.h>
#include <kprogress.h>
#include <kprocio.h>
#include <knotifyclient.h>
#include <dom/html_misc.h>
#include <tdeapplication.h>
#include <dcopclient.h>
#include <kuser.h>

#include <stdlib.h>

#include "version.h"
#include "update.h"


const char *mirrors[] = {
    "heanet",
    "internap",
    "surfnet",
    "umn",
    "nchc",
    "ufpr",
    "unc",
    "jaist",
    "voxel",
    "citkit",
    "optusnet",
    "ovh",
    "mesh",
    "easynews",
    "switch",
    "kent",
    "puzzle",
    0
};

TorkUpdate::TorkUpdate(TQWidget *parent, const char *name)
    : TQWidget(parent, name),filelist(new TDEHTMLPart(this))
{

}


TorkUpdate::~TorkUpdate()
{
}


void TorkUpdate::downloadComponent(TQString component, TQString version, TQString extension)
{

    upgradeinprogress = true;

    TQString location = getenv("HOME");

    location += "/.tork";
    TQDir torkdir(location);
    if (!torkdir.exists() && !torkdir.mkdir(location))
        location = getenv("HOME");

    TQString currenttor = location + TQString("/%1-%2").arg(component).arg(version);
    TQDir tordir(currenttor);

    TQString configure;
    configure = TQString("%1/%2-%3/configure").arg(location).arg(component).arg(version);

    TQFile configurefile(configure);

    bool download = true;

    if (torkdir.exists() && configurefile.exists()){
        int result = KMessageBox::questionYesNo(0, i18n( "You seem to have downloaded %1-%2 already (in %3/%4-%5). Would you like to skip re-downloading it and just try to compile it?").arg(component).arg(version).arg(location).arg(component).arg(version),i18n( "Compile %1" ).arg(component));

        switch (result) {
            case KMessageBox::Yes : 
                download = true;break;
            case KMessageBox::No :
                download = false;
        }
    }

    TQStringList entry;
    while (download){

        startProgressDialog( i18n( "Downloading %1-%2..." ).arg(component).arg(version) );

        TQString dlLocation;
        TQString dlLocationSig;
        if (component == "tor"){
            dlLocation = TQString("https://torproject.org/dist/%3").arg(highestsofarfilename);
            dlLocationSig = TQString("https://torproject.org/dist/%3.asc").arg(highestsofarfilename);
        }else if (component == "privoxy")
            dlLocation = TQString("http://%1.dl.sourceforge.net/sourceforge%3").arg(getMirror()).arg(highestsofarfilename);
        else if (component == "dante")
            dlLocation = TQString("http://www.mirrors.wiretapped.net/security/firewalls/dante/%3").arg(highestsofarfilename);
        else if (component == "thttpd")
            dlLocation = TQString("http://www.acme.com/software/thttpd/thttpd-2.25b.tar.gz");
        else if (component == "Mixminion")
            dlLocation = TQString("http://www.mixminion.net/dist/0.0.8alpha3/Mixminion-0.0.8alpha3.tar.gz");
        else
            dlLocation = TQString("http://%1.dl.sourceforge.net/sourceforge/%2/%3-%4%5").arg(getMirror()).arg(component).arg(component).arg(version).arg(extension);

        TQString tmpFile;
        if ( !TDEIO::NetAccess::download( dlLocation, tmpFile, 0L ) ){
            KMessageBox::information (0,i18n("Couldn't download %1.").arg(component));
            delete timer;
            timer=0;
            delete progressDialog;
            progressDialog = 0;
            upgradeinprogress = false;
            return;
        }

        TQString tmpFile2;
        if (!dlLocationSig.isEmpty()){
            if ( !TDEIO::NetAccess::download( dlLocationSig, tmpFile2, 0L ) ){
                KMessageBox::information (0,i18n("Couldn't download %1 signature file.").arg(component));
                delete timer;
                timer=0;
                delete progressDialog;
                progressDialog = 0;
                upgradeinprogress = false;
                return;
            }
            int res = KMessageBox::questionYesNo(0, i18n( "<p>Before proceeding you should verify the source package we have just downloaded. You can copy and paste the commands below into a terminal session such as Konsole.<br>"
            "<br>"
            "<b>Step One</b> Import the keys used by the Tor developers to sign the Tor source code:<br>"
            "<b>gpg --keyserver subkeys.pgp.net --recv-keys 0x28988BF5</b><br>"
            "<b>gpg --keyserver subkeys.pgp.net --recv-keys 0x165733EA</b><br>"
            "<br>"
            "<b>Step Two</b> To verify the source package we have just downloaded:<br>"
            "<b>gpg --verify %1 %2</b><br>"
            "<br>"
            "For further info on what you should expect to see, visit: <b>http://wiki.noreply.org/noreply/TheOnionRouter/VerifyingSignatures</b>"
            "<br>"
            "<br> Are you happy that the source file is authentic?</p>").arg(tmpFile2).arg(tmpFile));
            switch (res) {
                case KMessageBox::Yes : 
                    break;
                case KMessageBox::No : 
                default:
                    updateCanceled();
                    delete progressDialog;
                    progressDialog = 0;
                    return;
                    return;
            }

        }
    
        if ((progressDialog) && (progressDialog->wasCancelled())){
            updateCanceled();
            return;
        }
    
            
    
        progressDialog->setLabel( i18n( "Unpacking %1-%2 to %3/%4-%5" ).arg(component).arg(version).arg(location).arg(component).arg(version));
    
        // unpack the tarball
        KTar tar( tmpFile );
        if (!(tar.open( IO_ReadOnly ))){
            int result = KMessageBox::questionYesNo(0, i18n( "The mirror I attempted to download from has not updated yet. Should I try another?"));
            switch (result) {
                case KMessageBox::Yes : 
                    download = true; break;
                case KMessageBox::No : 
                    download = false;
                default:
                    download = false;
            }
        }else{
            download = false;
        } 
        tar.directory()->copyTo( location );
        entry = tar.directory()->entries();
        tar.close();
        // remove the temp file
        //TDEIO::NetAccess::removeTempFile( tmpFile );
    
        delete timer;
        timer=0;
        delete progressDialog;
        progressDialog = 0;
    }

//     if ((progressDialog) && (progressDialog->wasCancelled())){
//         updateCanceled();
//         delete progressDialog;
//         progressDialog = 0;
//         return;
//     }

    //KTar does not honour executable permissions

    if (component == "privoxy"){
        configure = TQString("%1/%2/").arg(location).arg(entry.first());
        configurefile.setName(configure+"configure.in");
    }

    if (component == "Mixminion"){
        configure = TQString("%1/%2/").arg(location).arg(entry.first());
        configurefile.setName(configure+"README");
    }

    if (torkdir.exists() && configurefile.exists()){

        chmod(configure.local8Bit(), 0700);

        int result = KMessageBox::warningContinueCancel(0, i18n( "%1-%2 is ready for compiling and installation. Would you like the wizard to ask you for the root password so it can compile and install it for you? (If not, you can compile it yourself later at %3/%4-%5)").arg(component).arg(version).arg(location).arg(component).arg(version),i18n( "Install %1-%2" ).arg(component).arg(version),i18n( "Use the Wizard" ));

        switch (result) {
            case 2 : 
                KMessageBox::information (0,i18n("Installation of %1 Cancelled.").arg(component));
                upgradeinprogress = false;
                break;
            case 5 : 

                TDEProcess* arkollonproc = new KShellProcess();

                KMessageBox::information (this,i18n( "<p><b>If this the first time you've compiled software then here are a few useful tips:</b><br>"
                    "1. Any error messages in the log file with the words 'TDE', 'TQt','curl' or 'X' in them mean that you need to install "
                    "the appropriate development libraries.<br>"
                    "2. Any package provided by your distribution with 'lib' or 'devel' in the name is a development library, e.g. tqt-devel, libtde.<br>"
                    "</p>" ),"Compilation Tips","compiler");

                 TQString command;
                if (component == "Mixminion")
                    command = TQString("tdesu -i tork -c 'xterm -hold -e \"cd %1;make install;printf \\\"If there were errors try downloading and installing Mixminion from the Mixminion homepage. If the installation looks successful try sending an anonymous mail to yourself. Close the window to continue.\\\"\"'").arg(configure);
                else if (component == "privoxy")
                    command = TQString("cd %1; autoheader;autoconf;torkarkollon %2").arg(configure).arg(configure);
                else
                    command = TQString("torkarkollon %1/%2-%3").arg(location).arg(component).arg(version);

                *arkollonproc << command;

                connect( arkollonproc, TQ_SIGNAL(processExited(TDEProcess *)), TQ_SLOT(torkInstallationExited(TDEProcess *)) );
                arkollonproc->start();
        }
    }
    
}

void TorkUpdate::downloadThttpd()
{
/*    startProgressDialog( i18n( "Downloading thttpd..." ) );*/
    downloadComponent("thttpd", "2.25b", "tar.gz");
}

void TorkUpdate::downloadMixminion()
{
/*    startProgressDialog( i18n( "Downloading thttpd..." ) );*/
    downloadComponent("Mixminion", "0.0.8alpha3", "tar.gz");
}

void TorkUpdate::checkForNewTork()
{

    highestsofarnumeric = 0;
    tork_url = "http://prdownloads.sourceforge.net/tork/";
    getLatestVersionFromSFHack(tork_url);

}

void TorkUpdate::checkForNewPrivoxyDirectly()
{

    checkingDirectly = true;

    highestsofarnumeric = 0;
    tork_url = "http://prdownloads.sourceforge.net/ijbswa/";
    startProgressDialog( i18n( "Checking for new version of Privoxy..." ) );

    getLatestVersionFromSF(tork_url);

}

void TorkUpdate::checkForNewTorkDirectly()
{

    checkingDirectly = true;

    highestsofarnumeric = 0;
    tork_url = "http://downloadtork.anonymityanywhere.com/";
    startProgressDialog( i18n( "Checking for new version of Tork..." ) );

    getLatestVersionFromSF(tork_url);

}


void TorkUpdate::checkForNewTorDirectly(bool alpha)
{

    checkingDirectly = true;
    m_alpha=alpha;
    highestsofarnumeric = 0;
    tork_url = "http://torproject.org/dist/";

    startProgressDialog( i18n( "Checking for new version of Tor..." ) );
    
    getLatestVersionFromSF(tork_url);

}

void TorkUpdate::checkForNewDanteDirectly()
{

    checkingDirectly = true;

    highestsofarnumeric = 0;
    tork_url = "http://www.mirrors.wiretapped.net/security/firewalls/dante/";

    startProgressDialog( i18n( "Checking for new version of Dante..." ) );
    
    getLatestVersionFromSF(tork_url);

}

void TorkUpdate::completedSearchForUpdates(const TQString &component, const TQString &extension)
{

    double currentversion;


    if (component == "Tork")
        currentversion = VERSION_TORK;
    else{
        if (currentTorVersion.mid(currentTorVersion.length() - 2,1) == ".")
            currentversion = currentTorVersion.replace(currentTorVersion.length() - 2,1,"").toDouble();
        else
            currentversion = currentTorVersion.toDouble();
    }

    if (highestsofarnumeric > currentversion){
        int result = KMessageBox::warningContinueCancel(0, i18n( "The newest version of %1 available is  %2-%3. Would you like Tork to download and compile it for you?").arg(component).arg(component).arg(highestsofarraw),i18n( "Download and Install %1-%2" ).arg(component).arg(highestsofarraw),i18n( "Download and Install %1-%2" ).arg(component).arg(highestsofarraw));
    
        switch (result) {
            case 2 : 
                break;
            case 5 : 
                downloadComponent(component.lower(), highestsofarraw, extension);
        }
    }else if (checkingDirectly){
        KMessageBox::information (this,i18n("Your installation of %1 is already up-to-date!").arg(component));
    }
    checkingDirectly = false;
    m_alpha=false;
//     tork_options->setEnabled(true);
//     tork_options->setEnabled(true);

//     if (filelist)
//         filelist->deleteLater();

}



void TorkUpdate::startProgressDialog( const TQString & text )
{


    progressDialog = new KProgressDialog( 0, "progress_dialog", TQString::null, text, false );

    progressDialog->setAllowCancel( true );
    progressDialog->showCancelButton( true );
    progressDialog->setPlainCaption( i18n( "Please Wait" ) );

    progressDialog->progressBar()->setTotalSteps( 0 );
    progressDialog->progressBar()->setPercentageVisible( false );

    progressDialog->setMinimumDuration( 500 );
    progressDialog->show();

    connect( progressDialog, TQ_SIGNAL( cancelClicked() ), this,
		TQ_SLOT( updateCanceled() ) );

    timer = new TQTimer( this );
    connect( timer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( slotProg() ) );

    timer->start( 200, FALSE );
}

void TorkUpdate::slotProg()
{

    if (progressDialog)
        progressDialog->progressBar()->setProgress(progressDialog->progressBar()->progress() + 4 );
}


void TorkUpdate::torkInstallationExited(TDEProcess* arkollonproc)
{

    arkollonproc = 0;
    delete arkollonproc;
    upgradeinprogress = false;

    KMessageBox::information (this,i18n( "If the installation completed successfully you should restart the component for the new version to take effect." ));

}

void
TorkUpdate::checkInternet() //SLOT
{
    m_url.setHost( "prdownloads.sourceforge.net" ); 
    if ( !m_url.port() ) m_url.setPort( 80 );

    connect( &m_resolver,   TQ_SIGNAL( finished( KResolverResults ) ), TQ_SLOT( resolved( KResolverResults ) ) );
    connectToHost();
}

void
TorkUpdate::connectToHost() //SLOT
{
        m_resolver.setNodeName( m_url.host() );
        m_resolver.setFamily( KResolver::InetFamily );
        m_resolver.start();
}


void
TorkUpdate::resolved( KResolverResults result) //SLOT
{
    if ((!( result.error() != KResolver::NoError || result.isEmpty() )) 
       /*&& tork_box->isChecked()*/)
        checkForNewTork();
    else
        kdDebug() << "network error or not set to check for new tork" << endl;
}



void TorkUpdate::getLatestVersionFromSF(KURL url)
{

    connect( filelist, TQ_SIGNAL( completed() ), this,
		TQ_SLOT( parseSFPage() ) );

    filelist->setStatusMessagesEnabled(false);
    filelist->openURL(url);

}

void TorkUpdate::getLatestVersionFromSFHack(KURL url)
{


    if (!filelist)
        filelist = new TDEHTMLPart();

    connect( filelist, TQ_SIGNAL( completed() ), this,
		TQ_SLOT( parseSFPageHack() ) );

    filelist->setStatusMessagesEnabled(false);
    filelist->openURL(url);

}



void TorkUpdate::updateCanceled()
{

    filelist->closeURL();
//     tork_options->setEnabled(true);
//     tork_options->setEnabled(true);

}

void TorkUpdate::parseSFPage()
{

    const DOM::HTMLCollection links = filelist->htmlDocument().links();

    if (links.length() == 0){
            KMessageBox::information (this,i18n( "Could not contact update server!" ));
            updateCanceled();
            delete progressDialog;
            progressDialog = 0;
            return;
    }
    
    disconnect( filelist, TQ_SIGNAL( completed() ), this,
	TQ_SLOT( parseSFPage() ) );

    for (unsigned int j=0; j != links.length(); j++ ){
        const DOM::Node linkNode = links.item( j );
        getVersionFromLink( linkNode );
    }

    delete progressDialog;
    progressDialog = 0;
    
    if (highestsofarfilename.contains("tork"))
        completedSearchForUpdates("Tork", ".tar.bz2");
    else if (highestsofarfilename.contains("tor"))
            completedSearchForUpdates("Tor", ".tar.gz");
    else if (highestsofarfilename.contains("privoxy"))
            completedSearchForUpdates("Privoxy", ".tar.gz");
    else if (highestsofarfilename.contains("dante"))
            completedSearchForUpdates("Dante", ".tar.gz");


}

void TorkUpdate::parseSFPageHack()
{

    const DOM::HTMLCollection links = filelist->htmlDocument().links();

    if (links.length() == 0){
            return;
    }
    
    disconnect( filelist, TQ_SIGNAL( completed() ), this,
	TQ_SLOT( parseSFPageHack() ) );

    for (unsigned int j=0; j != links.length(); j++ ){
        const DOM::Node linkNode = links.item( j );
        getVersionFromLink( linkNode );
    }

    //delete progressDialog;
    //progressDialog = 0;

    if (highestsofarfilename.contains("tork"))
        completedSearchForUpdates("Tork", "tar.bz2");
    else if (highestsofarfilename.contains("tor"))
        completedSearchForUpdates("Tor", "tar.gz");


}


void TorkUpdate::getVersionFromLink( const DOM::Node &n )
{
        double numericversion;
        TQString tmpversion;

        if ( n.isNull() || n.nodeType() != DOM::Node::ELEMENT_NODE )
            return;
        
        DOM::Element elem = static_cast<DOM::Element>( n );
        
        KURL href ( elem.getAttribute( "href" ).string() );
        
        TQString name = elem.getAttribute( "href" ).string();
        TQString version = name.replace(".tar.bz2","").replace(".tar.gz","");
        version = version.replace("tork-","").replace("tor-","")
                          .replace("/ijbswa/privoxy-","")
                          .replace("-stable-src","")
                          .replace("dante-","");
        
        TQString rawversion = version;
        if (m_alpha) {
          version.remove("-alpha").remove("-beta").remove("-rc");
        }
        numericversion = numericizeVersion(version);

        if (numericversion > highestsofarnumeric){
            highestsofarnumeric = numericversion;
            highestsofarraw = rawversion;
            highestsofarfilename = elem.getAttribute( "href" ).string();
        }


}

TQString TorkUpdate::getMirror()
{
    int r = 1+(int) (16.0*rand()/(RAND_MAX+1.0));
    static int previousmirror = 0;

    while (r == previousmirror)
        r=1+(int) (16.0*rand()/(RAND_MAX+1.0));

    previousmirror = r;

    return mirrors[r];
}

double TorkUpdate::numericizeVersion(TQString &version)
{
        TQString tmpversion = version;
        TQStringList tokens = TQStringList::split(".",tmpversion);
        for ( TQStringList::Iterator it = tokens.begin(); it != tokens.end(); it++ )
        {
            if ((*it).length() < 2)
                (*it).prepend("0");
        }
        tmpversion = tokens.join(".");

        unsigned int firstdot = tmpversion.find(".");
        for (unsigned int j=tmpversion.length(); j != firstdot; j-- ){
            if (tmpversion.mid(j,1) == "."){
                tmpversion.remove(j,1);
                j--;
            }
        }

        return tmpversion.toDouble();
}
#include "update.moc"

// forward-socks4a / localhost:9050 .
