/*
 * updater.cpp by Matthias Braun <matze@braunis.de>
 *
 * Copyright (C) 2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * 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 (version 2 of the License)
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#include <psconfig.h>

#include <iutil/vfs.h>
#include <iutil/databuff.h>
#include <iutil/objreg.h>
#include <csutil/util.h>
#include <csutil/xmltiny.h>
#include <csutil/thread.h>
#include <iutil/stringarray.h>
#include <iutil/databuff.h>
#include "util/sleep.h"
#include "util/strutil.h"

#include "pawsupdaterwindow.h"
#include "paws/pawsmanager.h"
#include "paws/pawswidget.h"

#include "psupdaterengine.h"

#include "updater.h"
#include "updaterconfig.h"
#include "registry.h"
#include "fileutil.h"
#include "download.h"
#include "registrycreator.h"
#include "change.h"
#include "changelist.h"
#include "registryfile.h"
#include "registry.h"

#include "updaterglobals.h"

#ifdef CS_PLATFORM_WIN32
#include <windows.h>
#endif

using updater::RegistryFile;
using updater::FileList;
using updater::Module;

namespace updater
{

Updater::Updater () : registrycache(NULL), newregistry(NULL)
{
    running = false;
    cleanupNeeded = true;
}

Updater::~Updater ()
{
    if (thread.IsValid())
        thread->Stop();

    // Save our config
    if (vfs)
    {
        configdoc->Write (vfs, configfilename);
    }

    delete downloader;
    
    delete[] configfilename;

    vfs = NULL;
    registrycache = NULL;
    newregistry = NULL;
}

bool Updater::Initialize(iObjectRegistry* object_reg )
{
    //Load stuff
    csString configfile("/this/updaterconfig.xml");

    objreg = object_reg;;
    configfilename = NULL;
    
    // Load our config
    vfs = psupdaterengine->GetVFS();
    if (!vfs)
        psupdaterengine->CriticalError("Couldn't find vfs plugin!");

    csRef<iDocumentSystem> xml = 
    csPtr<iDocumentSystem> (new csTinyDocumentSystem);
    if (!xml)
        psupdaterengine->CriticalError("Couldn't create xml document system!");
    
    //Try to read files
    csRef<iDataBuffer> buf = vfs->ReadFile (configfile.GetData());
    if (!buf || !buf->GetSize())
        psupdaterengine->CriticalError("Couldn't open config file '%s'!", configfile.GetData());

    //Try to parse file
    configdoc = xml->CreateDocument();
    const char* error = configdoc->Parse (buf);
    if (error)
        psupdaterengine->CriticalError("XML Parsing error in file '%s': %s.", configfile.GetData(), error);

    //Try to get root
    csRef<iDocumentNode> root = configdoc->GetRoot ();
    if (!root)
        psupdaterengine->CriticalError("Couldn't get config file rootnode!");
    
    //Try to get node config
    csRef<iDocumentNode> confignode = root->GetNode ("config");
    if (!confignode)
        psupdaterengine->CriticalError("Couldn't find config node in configfile!");

    //Parse the XML stuff in Config
    config = new Config(confignode);

    // Load updater config
    if (!config->Initialize())
        return false;

    configfilename = csStrNew (configfile);

    downloader = new Downloader;

    //Set proxy
    downloader->SetProxy(
                        config->GetProxy()->active,
                        config->GetProxy()->host.GetData(),
                        config->GetProxy()->port
                        );

    RefreshModules();
    return true;
}

void Updater::RefreshModules()
{
    config->ModuleSetup (modulechoice);
}

bool Updater::LoadRegistryCache (const char* filename)
{

    registrycache = csPtr<Registry> (new Registry(objreg));
    registrycache->Load(filename);

    return true;
}

void Updater::SaveRegistryCache (const char* filename)
{
    if (!newregistry)
        psupdaterengine->CriticalError("newregistry==NULL!!!");

    newregistry->Save (filename);
}

bool Updater::DownloadRegistry ()
{
    psupdaterengine->OutToScreen("Downloading registry...");
    // Download server registry
    csRef<iStringArray> thispath = psupdaterengine->GetVFS()->GetRealMountPaths("/this/");

    //As long as we have at least one real directory mounted to /this/ we're happy.
    if (thispath->Length() == 0)
    {
        psupdaterengine->OutToScreen("Error..VFS directory /this/ not mounted.");
        return false;
    }
    
    MakeDirectory("updatertemp");
    if ( !downloader->DownloadFile ("updatertemp/repos.zip",  config->GetCurrentRepositoryURL(), true) )
        return false;

    newregistry = LoadRepositoryZip("updatertemp/repos.zip");
    if (!newregistry.IsValid())
    {
        psupdaterengine->OutToScreen("Couldn't load downloaded registry!");
        return false;
    }

    psupdaterengine->OutToScreen("Loaded registry");

    return true;
}

csPtr<Registry> Updater::LoadRepositoryZip(const char* zip)
{
    //zip is a relative real path, so append /this/
    if (!psupdaterengine->GetVFS()->Mount("/repos",csString("$^")+zip))
    {
        psupdaterengine->CriticalError("Couldn't mount path %s!\n",zip);
        return NULL;
    }

    // Load the repository
    Registry* registry = new Registry(objreg);
    if(!registry->Load ("/repos/repository.xml"))
    {
      printf("Registry load failed!!");
      return false;
    }
    // Unmount and delete zip since we don't need it anymore now
    if (!psupdaterengine->GetVFS()->Unmount("/repos",csString("$^")+zip))
    {
      /*  printf("Couldn't unmount VFS path!\n");
        delete registry;
        return NULL;
      */
    }

    return registry;
}


bool Updater::CheckUpdates(ChangeList& changelist)
{
    //printf("Checking for updates\n");
    
    // Part0: Short Paths, if both registries are the same don't do any
    // updating or expensive md5 calculation
    if (newregistry && registrycache && newregistry == registrycache)
    {
        printf ( "No changes. Skipping MD5 check.\n");
        printf ( "Remove updatercurrent.xml if you want to force a MD5 test.\n");
        return true;
    }
    
    // Part1: Deleted Files
    // If we have a registry cache loaded compare the 2 registries to see if
    // some files have been deleted
    if (newregistry && registrycache)
    {
        for (size_t i=0;i<modulechoice.Length();i++)
        {                         
            const char* module = modulechoice[i];
        
            Module* mod1 = registrycache->GetModule (module);
            // If we didn't use this module the last update, don't use it now
            if(mod1 && !mod1->GetUsed())
                continue;

            Module* mod2 = newregistry->GetModule (module);
            
            
            if (!mod1 || !mod2)
                continue;
            
            // the filelists are in order.
            unsigned int p1=0, p2=0;
            while (p1< mod1->GetRegistryLength() && p2< mod2->GetRegistryLength())
            {
                RegistryFile* file1 = mod1->GetRegistryFile(p1);
                RegistryFile* file2 = mod2->GetRegistryFile(p2);

                if (file2->inZip && file2->removeFromZip)
                {
                    p1++;
                    if (!vfs->Mount("/zip",csString("$^")+file2->parentZip))
                    {
                        printf("Couldn't mount VFS path!\n");
                        continue;
                    }

                    csString path;
                    path = "/zip/";
                    path += file2->file;

                    if (vfs->Exists(path.GetData()))
                    {
                        // If we should remove without whole zips, remove. Otherwise trigger download of new zip
                        if(!GetConfig()->DownloadWholeZips()) 
                            changelist.Push (Change (Change::REMOVEFILE, module, file2));
                        else
                            changelist.Push (Change (Change::UPDATEZIP, module, file2));
                    }

                    if (!vfs->Unmount("/zip",csString("$^")+file2->parentZip))
                    {
                        printf("Couldn't unmount VFS path!\n");
                        continue;
                    }
                    continue;
                }
                
                if (*file1 == *file2)
                {
                    p1++;
                    p2++;
                    continue;
                }
                if (*file1 < *file2)
                {
                    CheckUpdatesFileRemoved (changelist, mod1,mod2, file1);
                    p1++;

                }
                if (*file2 < *file1)
                {
                    // ignore added files here
                    p2++;
                }
                p1++;
                p2++;
            }

            while (p1<mod1->GetRegistryLength())
            {
                RegistryFile* file1 = mod1->GetRegistryFile(p1);
                CheckUpdatesFileRemoved (changelist, mod1,mod2, file1);
                p1++;
            }
        }
    }

    // Part2: Check for updated files
    csArray<csString> skipped;
    for (size_t m=0;m<modulechoice.Length();m++)
    {
        const char* module = modulechoice[m];
        Registry* registry = (newregistry) ? newregistry : registrycache ;
        Module* mod = registry->GetModule (module);
        if (!mod)
        {
            printf (  "Warning: Couldn't find module '%s' in registry.\n", module);
            continue;
        }

        for (unsigned int f=0;f<mod->GetRegistryLength();f++)
        {
            RegistryFile* file = mod->GetRegistryFile(f);
            
            if (file->removeFromZip)
                continue;

            csString path;

            if (file->parentZip != "" && file->inZip)
            {
                // See if the zip is read only
                FileStat* stat = StatFile(file->parentZip);
                if(stat && stat->readonly)
                {
                    // Don't report it twice or more
                    bool found = false;
                    for(size_t i = 0;i < skipped.Length();i++)
                    {
                        if(skipped[i] == file->parentZip)
                        {
                            found = true;
                            break;
                        }
                    }

                    if(!found) // Found, delete duplicate
                    {
                        printf("Skipping file zip %s, because it's readonly\n",
                            file->parentZip.GetData());

                        skipped.Push(file->parentZip);
                        continue;
                    }
                    else
                        continue;
                }

                if (!vfs->Mount("/zip",csString("$^")+file->parentZip))
                {
                    printf("Couldn't mount temporary VFS path (%s)!\n",file->parentZip.GetData());
                    continue;
                }

                path = "/zip/";
                path.Append(file->zipFile);
            }
            else
            {   
                // See if the file is read only
                FileStat* stat = StatFile(file->file);
                if(stat && stat->readonly)
                {
                    printf("Skipping file %s, because it's readonly\n",file->file.GetData());
                    continue;
                }

                path = "/this/" + file->file;
            }

            // Check our md5 sum
            MD5Sum md5;
            bool md5status = md5.ReadFile (path, (file->flags & RegistryFile::FLAG_TEXTFILE) ? true : false);

            //Unmount temp VFS
            if (file->parentZip != "" && file->inZip)
            {
                if (!vfs->Unmount("/zip",csString("$^")+file->parentZip.GetData()))
                {
                    printf("Couldn't unmount temporary VFS path (%s)!\n",file->parentZip.GetData());
                    continue;
                }
            }

            if (!md5status) //assume default error (File not found)
            {
                // If the file didn't exist create a new one
                if(GetConfig()->DownloadWholeZips() && file->parentZip != "" && file->inZip) 
                    changelist.Push (Change (Change::UPDATEZIP, module, file));
                else
                    changelist.Push (Change (Change::CREATEFILE, module, file));   
                continue;
            }

            // See if the file itself is update since the last time, else we
            // will delete the users mods
            if (file->flags & RegistryFile::FLAG_CONFIGFILE && registrycache)
            {
                Module* oldModule = registrycache->GetModule(module);
                if (oldModule && f < oldModule->GetRegistryLength())
                {
                    if (oldModule->GetRegistryFile(f)->md5 == file->md5)
                    {
                        continue;
                    }
                }
            }
            
            if (md5 == file->md5)
            {
            }
            else
            {
                if(GetConfig()->DownloadWholeZips() && file->parentZip != "" && file->inZip) 
                    changelist.Push (Change (Change::UPDATEZIP, module, file));
                else
                    changelist.Push (Change (Change::UPDATEFILE, module, file));                    
            }

        }
    }

    RemoveDuplicatedZips(&changelist);
    DoZipPercentCheck(&changelist);

    return true;
}

bool Updater::CheckUpdatesFileRemoved (ChangeList& changelist,
                       Module* module,
                       Module* old_module,
                       RegistryFile* file)
{
    // Loop through the whole module to be sure
    size_t p=0;
    while (p< old_module->GetRegistryLength())
    {
        RegistryFile* file1 = old_module->GetRegistryFile(p);
                        
        if(file1->file == file->file)
        {
            printf("No action (Moved)\n");
            return false;
        }
        p++;
    }
                    
    // Check if this file is a system file
    for(size_t i = 0;i < GetConfig()->systemFiles.Length();i++)
    {
        if(GetConfig()->systemFiles.Get(i) == file->file)
        {
            printf("No action (System)\n");
            return false;
        }
    }

    // test if our md5 still is the same as the old md5
    if (!psupdaterengine->GetVFS()->Exists("/this/" + file->file))
    {
        printf("No action (Not found)\n");
        // return when no file was found
        return true;
    }

    if (config->MakeBackup())
    {
        printf("Move\n");
        changelist.Push (Change (Change::MOVEFILEAWAY, module->GetName(), file));
    }
    else
    {
        printf("Delete\n");
        changelist.Push (Change (Change::REMOVEFILE, module->GetName(), file));
    }

    return true;

}

void Updater::CopyZipDir( csString dir, csString directory )
{
    //printf("Looking in: /download/%s\n", dir.GetData() );
    csRef<iStringArray> buff = psupdaterengine->GetVFS()->FindFiles("/downzip/" + dir);
    
    for (size_t i = 0;i < buff->Length();i++)
    {
        csString file = buff->Get(i);
        bool direct = false;

        if (file.GetAt(file.Length()-1) == '/')
            direct = true;

        file = file.Slice(9,file.Length());

        if (direct)
        {          
            csString subpath = buff->Get(i);            
            CopyZipDir(subpath.Slice(9,subpath.Length()), directory);
        }
        else
        {
            if (file)
            {                
                size_t first = file.FindFirst('/');                
                csString myFile = file.Slice( first+1, file.Length() );   
                                        
                csString from;
                from.Format("/downzip/%s", file.GetData() );
                csString to;
                to.Format("/special/%s/%s", directory.GetData(), file.GetData() );
            
                psupdaterengine->GetUpdater()->CopyFile( from, to, true );                
            }                
        }                
      
    
    }
}

bool Updater::PerformUpdates (ChangeList& changelist)
{
    pawsUpdaterWindow* updWnd = NULL;
    
    if (!psupdaterengine->IsSilent())
    {
        float totalSize=0;
        for (size_t i=0; i<changelist.Length(); i++)
        {
            if(changelist[i].type == Change::UPDATEZIP)
                totalSize += changelist[i].file->parentSize;
            else
                totalSize += changelist[i].file->size;
        }

        psupdaterengine->LockStatusMutex();
        updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
        updWnd->SetTotalSize(totalSize);
        psupdaterengine->UnlockStatusMutex();
    }

    psupdaterengine->OutToScreen("==== Update started ====");

    for (size_t i=0;i<changelist.Length();i++)
    {
        csString base = config->GetCurrentFilesDirectory();
        csString serverpath;

        switch (changelist[i].type)
        {
            case Change::MOVEFILEAWAY:
            {           
                csString path("moved/");
                path.Append(changelist[i].file->file);

                CopyFile(changelist[i].file->file,path);
                //remove the file down there
            }
            
            
            case Change::REMOVEFILE:
            {
                UpdateRemoveFile( changelist[i] );
                break;
            }
            
            case Change::UPDATEZIP:
            {
                UpdateZip( changelist[i] );                
                break;
            }
            
            case Change::UPDATEFILE:            
            case Change::CREATEFILE:
            {
                UpdateCreateFile( changelist[i] );
                break;                
            }
            
            default:            
                fprintf (stderr, "Unknown Change Mode ?!?\n");
                break;
        }
    }

    // Unmount zip
    if(prevZip.GetData() != NULL)
    {
        psupdaterengine->OutToScreen("Syncing zip..");
        if (!vfs->Sync())
        {
            printf("Couldn't sync VFS!\n");
        }

        printf("Unmounting ZIP file %s..\n",prevZip.GetData());
        if (!vfs->Unmount( "/zip",prevZip.GetData() ))
        {
            printf("Couldn't unmount VFS path!\n");
        }  
    }

    return true;
}




bool Updater::CopyFile(csString name1, csString name2, bool vfsPath, bool silent)
{
    csString n1;
    csString n2;

    csString file = name2;
    if(vfsPath)
    {
        csRef<iDataBuffer> buff = vfs->GetRealPath(name2);
        if(!buff)
        {
            printf("Couldn't get the real filename for %s!\n",name2.GetData());
            return false;
        }

        file = buff->GetData();
    }

    FileStat* stat = StatFile(file);
    if(stat && stat->readonly)
    {
        printf("Won't write to %s, because it's readonly\n",file.GetData());
        return true; // Return true to bypass DLL checks and stuff
    }

    if (!vfsPath)
    {
        n1 = "/this/" + name1;
        n2 = "/this/" + name2;
    }
    else
    {
        n1= name1;
        n2= name2;
    }

    csRef<iDataBuffer> buffer = vfs->ReadFile(n1.GetData(),true);
     
    if (!buffer)
    {
        if(!silent)
            printf("Couldn't read file %s!\n",n1.GetData());
        return false;
    }
    
  
    if (!vfs->WriteFile(n2.GetData(), buffer->GetData(), buffer->GetSize() ) )
    {
        if(!silent)
            printf("Couldn't write to %s!\n", n2.GetData());
        return false;
    } else {
        printf("Written %s!\n", n2.GetData());
    }

    return true;
}

bool Updater::DoUpdate (iObjectRegistry* objreg)
{
    pawsUpdaterWindow* updWnd = NULL;
    if(!psupdaterengine->IsSilent())
        updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");

    // Check the server's version file, and download the registry if needed
    bool newRegistry = CheckRegistryVersion();
    if (newRegistry)
    {
        if (!DownloadRegistry())
        {
            printf("Couldn't download updater registry from server!\n");
            return false;
        }

        psupdaterengine->OutToScreen("Checking for updates...  This may take a while.");
    }
    else
    {
        psupdaterengine->OutToScreen("There are no new updates available.");
        psupdaterengine->OutToScreen("If you want a full update, delete the file version.dat and restart the updater.");
        //psupdaterengine->OutToScreen("Checking for missing/damaged files...  This may take a while.");
        //TOFIX: it doesn't download the registry from server, so this will NOT work!
        //It should ask to the user if he wants to check for damaged files.
        return false;
    }

    // Load cache
    LoadRegistryCache ("/this/updatercurrent.xml");

    // Check if we have a new version of the updater to download
    if (!CheckNewUpdater())
        return false;

    ChangeList list;
    if ( CheckUpdates(list) )
    {
        if (list.Length())
        {
            psupdaterengine->OutToScreen("I'll perform the following updates:");
            csString output = list.CreateList(true);

            // Write log
            csRef<iFile> file = vfs->Open("/this/updater.log",VFS_FILE_WRITE);
            file->Write(output.GetData(),(size_t)output.Length());
            file->Flush();
        }
        else
        {
            psupdaterengine->OutToScreen("All files are up-to-date!");
        }

    }
    else
        return false;

    printf("Updates: %d\n", list.Length() );

    // Let the user see the Total files etc
    if (list.Length())
        psSleep(3000);

    if (list.Length() > 0)
    {
        // Apply changes
        if (!PerformUpdates (list) )
            return false;

        if (failed.Length() > 0)
        {
            csString output;

            output = "Following files failed:";
            psupdaterengine->OutToScreen(output.GetData());

            for (size_t i = 0; i < failed.Length(); i++)
            {
                output = failed.Get(i);
                psupdaterengine->OutToScreen(output.GetData());
            }
            failed.DeleteAll();
        }
    }

    psupdaterengine->OutToScreen("Please wait while saving files..");
    // Disable the exit button to make our point
    if (updWnd) updWnd->SetExit(false);

    // Save the registry and version info
    if (newRegistry) SaveRegistryCache("/this/updatercurrent.xml");
    MoveFile("/this/updatertemp/version.dat","/this/version.dat");
    
    psupdaterengine->OutToScreen("==== Update complete ====");
    if (updWnd) updWnd->SetExit(true);

    if (failed.Length() > 0)
        return false;

    return true;
}

int Updater::CreateRegistry (iObjectRegistry* objreg, const char* path)
{
    printf("Now Creating the Repository in %s\n", path);
    
    RegistryCreator* creator = new RegistryCreator(objreg);

    csString strPath = path;
    if (strPath.GetAt(strPath.Length()-1) != CS_PATH_SEPARATOR)
    {
        strPath += CS_PATH_SEPARATOR;
    }

    csRef<iDataBuffer> fileBuff = psupdaterengine->GetVFS()->GetRealPath(strPath);
    csString vfsPath;

    if (!fileBuff)
        vfsPath = strPath;
    else
        vfsPath = fileBuff->GetData();
    printf("Mounting %s as the VFS path /storage\n", vfsPath.GetData() );
    // Mount copy-to path
    if (!psupdaterengine->GetVFS()->Mount("/storage",vfsPath))
    {
        psupdaterengine->CriticalError("Couldn't mount path %s!\n",path);
        return 0;
    }

    printf ("Scaninning directories..\n");
    
    Registry* registry = creator->CreateRegistry();;
    
    
    // Mount repository zip
    if (!psupdaterengine->GetVFS()->Mount("/repos",vfsPath+"repository.zip"))
    {
        csString path = vfsPath+"repository.zip";
        psupdaterengine->CriticalError("Couldn't mount path %s!\n",path.GetData());
        return 0;
    }
    printf("Writing repository.xml...\n");
    registry->Save ("/repos/repository.xml");

    if (!vfs->Sync())
    {
        printf("Couldn't Sync VFS path!\n");
    } 
    if (!psupdaterengine->GetVFS()->Unmount("/repos",vfsPath+"repository.zip"))
    {
        printf("Couldn't unmount VFS path!\n");
        return 0;
    }

    printf("Writing version.dat...\n");
    csString data = CreateVersionInfo(registry);
    psupdaterengine->GetVFS()->WriteFile("/storage/version.dat", data.GetData(), data.Length() );

    registrycache = registry;

    return 0;

    if (!psupdaterengine->GetVFS()->Unmount("/storage",vfsPath))
    {
        printf("Couldn't unmount VFS path!\n");
        return 0;
    }
}

void Updater::Run()
{
    //Do the stuff
    DoUpdate(objreg);
    
    //Cleanup and stop
    EndUpdate();

    //Quit the updater if we are in silent mode
    //(It will stop the thread in the destructor anyway)
    if (psupdaterengine->IsSilent())
        psupdaterengine->QuitClient();
}

void Updater::EndUpdate()
{
    //Clean out temp dir
    if (cleanupNeeded)
      CleanUp();

    //Reset file name and progress
    if (!psupdaterengine->IsSilent())
    {
        psupdaterengine->LockStatusMutex();
        pawsUpdaterWindow* updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
        updWnd->UpdateFileSize("",0);
        updWnd->SetUpdateButtonText("Update");  // Change button from "Cancel" back to "Update"
        psupdaterengine->UnlockStatusMutex();
    }

    running = false;

    thread->Stop();
    thread = NULL;
}

void Updater::StartUpdate()
{
    if (!running)
    {
        if (!thread.IsValid())
            thread = csThread::Create (this);

        if (thread.IsValid())
        {
            thread->Start();
            running = true;
        }
    }
}

void Updater::Die()
{
    if(thread.IsValid())
    {
        thread->Stop();
        running = false;
    }
}

bool Updater::CheckNewUpdater()
{
    if (GetConfig()->GetMirror()->NoVersion() ||
        psupdaterengine->GetUpdater()->GetConfig()->GetSkipVerCheck() )
    {
        psupdaterengine->OutToScreen("Skipping version check, suit yourself");        
        return true;
    }

    if (newregistry) printf("Using newregistry\n");
    else printf("Using registrycache\n");
    Registry* registry = (newregistry) ? newregistry : registrycache ;

    if (registry->GetUpdaterVersion() < UPDATER_VERSION)
    {
        // If we are in active mode, print to console too
        psupdaterengine->OutToScreen("Your updater version %d is newer than the server's %d... Server is out of date; stopping.",UPDATER_VERSION,registry->GetUpdaterVersion());
        if (!psupdaterengine->IsSilent())
            printf("Your updater version %d is newer than the server's %d... Server is out of date; stopping.",UPDATER_VERSION,registry->GetUpdaterVersion());

        return false;
    }
    else if (registry->GetUpdaterVersion() > UPDATER_VERSION)
    {
        // If we are in active mode, print to console too
        psupdaterengine->OutToScreen("Your updater version %d is older than the server's %d..",UPDATER_VERSION,registry->GetUpdaterVersion());        
        psupdaterengine->OutToScreen("Updating halted");        
        if (!psupdaterengine->IsSilent())
            printf("Your updater version %d is older than the server's %d..\n",UPDATER_VERSION,registry->GetUpdaterVersion());
    }
    else
    {
        if (GetConfig()->UpdateUpdaterByMD5())
        {
            MD5Sum updater;
            updater.ReadFile(csString("/this/") + registry->GetUpdaterPath(),false);
            csString updaterMD5 = registry->GetUpdaterMD5();

            if( updaterMD5.GetData() == NULL || updaterMD5 == updater.Get() )
                return true;
            else {
                psupdaterengine->OutToScreen("Server has an updater with same version, but different checksum, going to fetch it...");
                printf("Server has an updater with same version, but different checksum, going to fetch it.\n");
                printf("%s - %s",updaterMD5.GetData(),updater.Get());
            }
        }
        else
            return true;
    }

    // Prepare to enter selfupdating mode
#ifdef CS_PLATFORM_WIN32
    UpdateUpdater();
#else
    // ToDo: Add *NIX here

    csString mesg;
    mesg = "WARNING: New updater can't be fetched since your OS isn't yet supported for automatic updating. ";
    mesg += "You should be able to fetch and install it manually either from the mainsite or from the forums";

    PawsManager::GetSingleton().CreateWarningBox(mesg);
    psupdaterengine->OutToScreen("Sleeping for 10 seconds for you to read the message...");
    csSleep(10000);
    psupdaterengine->OutToScreen("Going to continue with the update, unexpected things may occurr");
    return true;
    // ToDo: Add *NIX and some MacOS stuff here

#endif


    //Quit this updater
    psupdaterengine->QuitClient();

    // avoid the clean up or the self-update will not work
    cleanupNeeded = false;

    return false;

}

void Updater::CopyDirectory(const char* vfsPath,const char* to)
{
    csRef<iStringArray> files = vfs->FindFiles(vfsPath);

    csString root = vfsPath;

    // Copy all files
    for (size_t i = 0;i < files->Length();i++)
    {
        csString fileName = files->Get(i);
        fileName = fileName.Slice(root.Length(),fileName.Length()-root.Length());

        if(fileName.Slice(fileName.Length()-1,1) == "/")
        {
            CopyDirectory(root + fileName,to + fileName);
            continue;
        }

        if(!CopyFile(root + fileName,to + fileName,true))
            printf("ERROR! Couldn't copy selfupdating file (%s)!\n",fileName.GetData());
    }
}

bool Updater::UpdateUpdater()
{
#ifdef CS_PLATFORM_WIN32

    int version = newregistry->GetUpdaterVersion();
    csString fileName = "updater";

    fileName += version;
    fileName +=".zip";

    csString ver;
    ver = version;
    for(size_t i = 1;i < ver.Length();i++)
    {
        ver.Insert(i,'.');
        i++; // Jump twice
    }

    if(version != UPDATER_VERSION)
        psupdaterengine->OutToScreen( "Updating updater to version " + ver );
    else
        psupdaterengine->OutToScreen( "Updating updater to another build");
    
    bool error = false;

    MakeDirectory("updatertemp");
    if (downloader->DownloadFile("updatertemp/updater.zip",config->GetCurrentFilesDirectory() + csString("/") + fileName,false))
    {
        // Copy all files in the zip
        if (!vfs->Mount("/zip","updatertemp/updater.zip" ))
        {
            printf("Couldn't mount VFS path!\n");
            error=true;
        }
   
        if(!error)
        {
            // Copy all files
            CopyDirectory("/zip/","/this/updatertemp/");
            Sleep(5000);

            if (!vfs->Unmount( "/zip","updatertemp/updater.zip" ))
            {
                printf("Couldn't unmount VFS path!\n");
                error = true;
            }
        }
    }
    else
    {
        error = true;
    }

    if(error)
    {
        MessageBox(0,"Updating failed!\nPlease try again later","Updater - Selfupdating",MB_ICONWARNING);
        psupdaterengine->QuitClient(); 
        return false;
    }

    // Delete the temp file
    psupdaterengine->GetVFS()->DeleteFile("/this/updatertemp/updater.zip");

    // Delete unwanted files
    psupdaterengine->GetVFS()->DeleteFile("/this/updatertemp/version.dat");

    // Launch the new updater
    csString p;
    p = "\"updatertemp\\updater.exe\" -install=..";
    printf("Launching updatertemp\\updater.exe -install=..");
    psupdaterengine->LaunchProgram(p,"updatertemp");
    psupdaterengine->QuitClient();

#else
    printf("Can't selfupdate on other systems than Windows, sorry");
#endif

    return true;
}

void Updater::CleanUp()
{
    if (vfs->Exists("/this/updatertemp"))
    {
#ifdef CS_PLATFORM_WIN32
        // 9x doesn't have rd /S
        csRef<iStringArray> files = vfs->FindFiles("/this/updatertemp/");
        // remove all files
        for (size_t i = 0;i < files->Length();i++)
        {
            csString fileName = files->Get(i);
            fileName = fileName.Slice(6,fileName.Length()-6);
            fileName = ConvertToSystemPath(fileName);
            DeleteFileA(fileName.GetData()); // Win32 api to remove zip files, VFS can't do that
        }
        RemoveDirectory("updatertemp");
#else
        system("rm -fr updatertemp");
#endif
    }
}

void Updater::Compare()
{
    if (!DownloadRegistry ())
    {
        printf("Couldn't download updateregistry from server!\n");
        return;
    }

    ChangeList list;
    if (CheckUpdates (list) )
    {
        if (list.Length())
        {
            for (size_t i=0; i<list.Length(); i++)
            {
                csString entry;
                Change change = list.Get(i);
                switch (change.type)
                {
                    case Change::REMOVEFILE:
                    entry = "Create   ";
                    break;
                    case Change::UPDATEFILE:
                    entry = "Update   ";
                    break;
                    default:
                        continue;
                }
                entry += change.file->file;

                if (change.file->inZip)
                {
                    entry += " (ZIP)";
                }

                psupdaterengine->OutToScreen(entry);
            }
        }
        else
        {
            psupdaterengine->OutToScreen("No changed files found");
        }

    }
}

bool Updater::IsRunning()
{
    return running;
}

void Updater::CreateToDoList(ChangeList* changelist,int mirror)
{
    // Print all address to the files to download
    Mirror* mir = GetConfig()->GetMirror(mirror);
    if(!mir)
    {
        psupdaterengine->CriticalError("Couldn't find mirror %d!",mirror);
        return;
    }

    csString todo;

    for(size_t i = 0;i < changelist->Length();i++)
    {
        Change change = changelist->Get(i);
        
        if(change.type == Change::UPDATEFILE ||change.type == Change::CREATEFILE  ||change.type == Change::UPDATEZIP )
        {
            todo += mir->GetBaseURL();
            todo += mir->GetFilesDirectory();
            todo.Append ("/");
            todo.Append (change.module);
            todo.Append ("/");
            if(change.type == Change::UPDATEZIP)
            {
                csString zip = change.file->parentZip;
                zip = zip.Slice(0,zip.FindLast('.')) + "_all.zip";
                todo.Append (zip);
            }
            else
                todo.Append (change.file->file);
            todo.Append ("\n");
        }
    }

    printf("ToDo:\n%s",todo.GetData());
    psupdaterengine->QuitClient();
}

void Updater::RemoveDuplicatedZips(ChangeList* changelist)
{
    // Remove duplicated ZIPs if we are downloading whole zips
    if(GetConfig()->DownloadWholeZips())
    {
        csArray<csString> zips;

        for(size_t z = 0;z < changelist->Length();z++)
        {
            Change chg = changelist->Get(z);
            if(chg.type == Change::UPDATEZIP)
            {
                // Don't add it twice or more
                bool found = false;
                for(size_t i = 0;i < zips.Length();i++)
                {
                    if(zips[i] == chg.file->parentZip)
                    {
                        found = true;
                        break;
                    }
                }

                if(found) // Found, delete duplicate
                {
                    //printf("Got duplicate of zip %s (%d) removing..\n",zips[i].GetData(),z);
                    changelist->DeleteIndex(z);
                    z--;
                }
                else // Not found, add zip to list
                {
                    //printf("Zip to be updated: %s (%d)\n",chg.file->parentZip.GetData(),z);
                    zips.Push(chg.file->parentZip);
                }
                
                continue;
            }
        }
    }
}

size_t Updater::GetBytesInZip(const char* zip)
{
    if(!zip) return 0;

    // Mount ZIP to check
    if (!vfs->Mount("/zip",csString("$^")+zip))
    {
        printf("Couldn't mount temporary VFS path (%s)!\n",zip);
        return 0;
    }

    // Get em
    size_t files = GetBytesInZipDir("");
                
    if (!vfs->Unmount("/zip",csString("$^")+zip))
        printf("Couldn't unmount temporary VFS path (%s)!\n",zip);

    return files;
}

size_t Updater::GetBytesInZipDir(const char* dir)
{
    if(!dir) return 0;

    csString zipDir = dir;

    size_t files = 0;

    // Get files
    csRef<iStringArray> filesArr = psupdaterengine->GetVFS()->FindFiles("/zip/" + zipDir);

    // Count em baby!
    for(size_t i = 0; i < filesArr->Length(); i++)
    {
        bool dir = false;
        csString fileName = filesArr->Get(i);

        if (fileName.GetAt(fileName.Length()-1) == '/')
            dir = true;

        if(dir)
            files += GetBytesInZipDir(fileName.Slice(5,fileName.Length()));
        else
        {
            size_t size = 0;
            psupdaterengine->GetVFS()->GetFileSize(fileName,size);
            files += size;
        }
    }

    return files;
}

void Updater::DoZipPercentCheck(ChangeList* changelist)
{
    // This is a quite time consuming function, but the user has got time
    csPDelArray<Updater::ZipPercent> zips;
    
    for(size_t i = 0; i < changelist->Length();i++)
    {
        size_t z;
        if(changelist->Get(i).file->parentZip == "")
            continue;

        if(changelist->Get(i).type != Change::UPDATEFILE && changelist->Get(i).type != Change::CREATEFILE)
            continue;

        bool found = false;
        for(z = 0; z < zips.Length();z++)
        {
            if(zips[z]->zip == changelist->Get(i).file->parentZip)
            {
                found = true;
                break;
            }
        }

        if(!found)
        {
            Updater::ZipPercent* zipPer = new Updater::ZipPercent;
            zipPer->zip = changelist->Get(i).file->parentZip;
            zipPer->update = changelist->Get(i).file->size;
            zipPer->bytes = GetBytesInZip(zipPer->zip);
            zipPer->oneFile = changelist->Get(i).file;
            zipPer->module = changelist->Get(i).module;

            zips.Push(zipPer);
        }
        else
        {
            Updater::ZipPercent* zipPer = zips[z];
            zipPer->update += changelist->Get(i).file->size;
        }
    }
    

    // Check our %s
    for(size_t z = 0; z < zips.Length();z++)
    {
        Updater::ZipPercent* zipPer = zips[z];

        double result = zipPer->update;
        if(zipPer->bytes != 0)
        {
            result /= zipPer->bytes;
            result *= 100;
        }
        else
            result = 100;

        if(result > GetConfig()->GetWholeZipPrecent())
        {
            for(size_t i = 0; i < changelist->Length();i++)
            {
                if(changelist->Get(i).file->parentZip != zipPer->zip)
                    continue;

                changelist->DeleteIndex(i);
                i--;
            }

            changelist->Push(Change (Change::UPDATEZIP, zipPer->module, zipPer->oneFile));
        }
    }
}

void Updater::Size( size_t size, float& fsize, csString& unit )
{
    //Make it not so many numbers..
    if (size > 1024 && size < (1024 * 1024))
    {
        fsize = size / 1024;
        unit = "KB";
    }
    else if ( size > (1024*1024) )
    {
        fsize = float(size / 1024) / 1024;
        unit = "MB";
    }
    else
    {
        fsize = size;
        unit = "B";
    }
}


bool Updater::IsSpecialZip( csString&  zip )
{
    size_t findSubDir = zip.FindFirst('/');
    return (findSubDir != (size_t)-1);
}



bool Updater::HandleSpecialZipUpdate( Change& change )
{   
    RegistryFile* file = change.file;
   
    printf("Registery File: %s\n", file->file.GetData() );
    // Figures out the name of the zip file we need.  For example something inside the 
    // maps cache dir this generates the name cache.zip
    csString specialZipDownload = file->file.Slice(file->parentZip.Length()+1);
    size_t endPos = specialZipDownload.FindFirst('/');
    specialZipDownload = specialZipDownload.Slice(0,endPos);
    specialZipDownload.Append(".zip");
    
    printf("Downloading Zip: %s\n", specialZipDownload.GetData() );
    
    // Figure out where in the repository this file actually is. 
    csString serverpath =  config->GetCurrentFilesDirectory();        
    serverpath.Append("/");
    serverpath.Append(change.module);
    serverpath.Append("/");
    serverpath.Append(file->parentZip);
    serverpath.Append("/");
    serverpath.Append(specialZipDownload);
    
    printf("Download: %s URL: %s\n", config->GetTempFile().GetData(), serverpath.GetData() );                    


    if ( specialsDownloaded.Contains(serverpath) != csArrayItemNotFound )
    {
        return true;
    }        
                       
    if (!downloader->DownloadFile (config->GetTempFile().GetData(),  serverpath, true))
    {
        printf("Failed to download!\n");
        failed.Push(file->file);
        return false;
    }
                        
    specialsDownloaded.Push( serverpath );
    
   
    // Mount the zip file that this special zip is for. Mount it as /special/
    printf("Mounting ZIP file %s..\n",file->parentZip.GetData());
    if (!vfs->Mount("/special",csString("$^")+file->parentZip.GetData() ))
    {
        printf("Couldn't mount VFS path!\n");
        return false;
    }                                  
    
    // Take the name and remove the .zip part from it to get the sub directory name. 
    csString directory = specialZipDownload.Slice(0, specialZipDownload.Length() - 4 );
    
    // Mount the file that was just downloaded as /downzip/
    printf("Mounting /downzip /this/%s\n", config->GetTempFile().GetData() );    
    if (!vfs->Mount("/downzip", config->GetTempFile().GetData()))
    {
        printf("Couldn't mount downloaded file\n");
        return false;
    }                    
    
    // Copy all the files from one mount point to the other in the correct location.
    csRef<iStringArray> buff = vfs->FindFiles("/downzip/");
    for ( size_t z = 0; z < buff->Length(); z++ )
    {
        bool isDirectory = false;
        csString fileName = buff->Get(z);
        
        if ( fileName.GetAt(fileName.Length()-1) == '/' )
        {
            isDirectory = true;
        }
        fileName = fileName.Slice(9, fileName.Length());
        
        if ( isDirectory )
        {
            csString subPath = buff->Get(z);
            CopyZipDir(subPath.Slice(9, subPath.Length()), directory);
        }
        else
        {
            //printf("Copy /downzip/%s to /special/%s/%s \n", fileName.GetData(), directory.GetData(), fileName.GetData() );                                                                                              
                            
            csString from;
            from.Format("/downzip/%s", fileName.GetData() );
            csString to;
            to.Format("/special/%s/%s", directory.GetData(), fileName.GetData() );            
            psupdaterengine->GetUpdater()->CopyFile( from, to, true );                            
        }            
    } 
   
    vfs->Sync();
    vfs->Unmount( "/downzip/", config->GetTempFile() );
    vfs->Unmount( "/special", csString("$^")+file->parentZip.GetData() );
    return true;       
}


void Updater::UpdateCreateFile( Change& change )
{
    csString serverpath = config->GetCurrentFilesDirectory();  
    serverpath.Append ("/");
    serverpath.Append (change.module);
    serverpath.Append ("/");
    serverpath.Append (change.file->file);

    size_t size = change.file->size;
    float fsize;
    csString unit;

    Size( size, fsize, unit );
    
    csString output;
    output.Format("%s (%0.2f %s)",(const char*) change.file->file,    fsize,unit.GetData());

    // Notify the user
    if (!psupdaterengine->IsSilent())
    {
        pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
        psupdaterengine->LockStatusMutex();
        updWnd->UpdateFileSize(output.GetData(),change.file->size);
        psupdaterengine->UnlockStatusMutex();
    }
    else 
        printf("%s ",output.GetData());

    bool isSpecial = IsSpecialZip( change.file->zipFile  );
                                                       
    if ( isSpecial )      
    {
        HandleSpecialZipUpdate( change );                                                               
        return;
    }
                 
    // Now do the download
    if (!downloader->DownloadFile (config->GetTempFile().GetData(),  serverpath, true))
    {
        failed.Push(change.file->file);
        return;
    } 
                  
    // Check MD5
    MD5Sum md5;
    md5.ReadFile ("/this/" + config->GetTempFile(), false);
    if (! (md5 == change.file->md5))
    {
        printf("MD5 sum doesn't match after downloading %s. Skipping\n", (const char*) change.file->file);

        failed.Push(change.file->file);
        return;
    }

    //Merge file with ZIP
    if (change.file->parentZip != "")
    {
        csString path = change.file->parentZip;

        FileStat* stat = StatFile(path);
        if(stat && stat->readonly)
        {
            printf("Won't write to %s, because it's readonly\n",path.GetData());
            return;
        }

        // Keeper of the sync number
        static int syncNumber = 0;

        // Was this the prev zip?
        if(prevZip != change.file->parentZip)
        {      
            // Sync
            if(prevZip != "")
            {
                psupdaterengine->OutToScreen("Syncing zip..");
                if (!vfs->Sync())
                {
                    printf("Couldn't sync VFS!\n");
                    return;
                }
                syncNumber = 0;
            }

            // Unmount prev zip
            if(prevZip.GetData() != NULL)
            {
                printf("Unmounting ZIP file %s..\n",prevZip.GetData());
                if (!vfs->Unmount( "/zip",prevZip.GetData() ))
                {
                    printf("Couldn't unmount VFS path!\n");
                    return;
                }  
            }

            // Mount new zip
            printf("Mounting ZIP file %s..\n",change.file->parentZip.GetData());
            if (!vfs->Mount("/zip",csString("$^")+change.file->parentZip.GetData() ))
            {
                printf("Couldn't mount VFS path!\n");
                return;
            }

            // Update prev zip
            prevZip = csString("$^")+change.file->parentZip;
        }

        if(!CopyFile( "/this/" + config->GetTempFile() ,"/zip/" + change.file->zipFile, true))
        {
            failed.Push(change.file->file);
            return;
        }

        // Check the sync number if it's time to sync the zip now
        if(syncNumber == GetConfig()->ZipSyncNumber())
        {
            psupdaterengine->OutToScreen("Syncing zip..");
            if (!vfs->Sync())
            {
                printf("Couldn't sync VFS!\n");
                return;
            }
            syncNumber = 0;
        }

        syncNumber++;

        csString doneStr("Merged   ");
        doneStr += change.file->file;
        psupdaterengine->OutToScreen(doneStr.GetData());
        return;
    }
    
    //Copy temp file to real location
    if(!CopyFile(config->GetTempFile(),change.file->file,false,true))
    {
#ifdef CS_PLATFORM_WIN32

        printf("Assuming file in use: %s... ",change.file->file.GetData());

        // If we failed to copy file, it might be in use

        if(!psupdaterengine->GetVFS()->Exists("/this/updatertemp"))
            MakeDirectory("updatertemp");

        if(!CopyFile(config->GetTempFile(),change.file->file))
        {
            printf("Failed\n");
            system("move updatertemp\\" + change.file->file);
        }
        else
        {
            printf("Success\n");
            return;
        }
#endif
        printf("Failed to update %s!\n",change.file->file.GetData());
        failed.Push(change.file->file);
        return;
    }

    csString doneStr("Updated  ");
    doneStr += change.file->file;

    psupdaterengine->OutToScreen(doneStr.GetData());

    //downloadedbytes += changelist[i].file->size;
}

void Updater::UpdateZip( Change& change )
{
    // We got orders to download a whole zip file, let's do it
    RegistryFile* downFile = change.file;
    
    // Create the URL
    csString serverpath  = config->GetCurrentFilesDirectory();                 
    serverpath.Append ("/");
    serverpath.Append (change.module);
    serverpath.Append ("/");

    size_t size = change.file->parentSize;

    float fsize;
    csString unit;
    Size( size, fsize, unit );
    
    csString output;
    output.Format("%s (%0.2f %s)",(const char*) downFile->parentZip, fsize,unit.GetData());

    // Notify the user
    if (!psupdaterengine->IsSilent())
    {
        pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
        psupdaterengine->LockStatusMutex();
        updWnd->UpdateFileSize(output,change.file->parentSize);
        psupdaterengine->UnlockStatusMutex();
    }
    else 
        printf("%s ",downFile->parentZip.GetData());

    csString zip = downFile->parentZip;
    zip = zip.Slice(0,zip.FindLast('.')) + "_all.zip";
    // Add the final path to the serverpath
    serverpath.Append (zip);

    // Now do the download
    if (!downloader->DownloadFile (config->GetTempFile().GetData(),  serverpath, true))
    {
        failed.Push(zip);
        return;
    }

    // Check MD5
    MD5Sum md5;
    md5.ReadFile ("/this/" + config->GetTempFile(), false);
    if (! (md5 == change.file->parentMD5))
    {
        printf("MD5 sum doesn't match after downloading %s. Skipping\n", (const char*) change.file->parentZip);

        failed.Push(change.file->parentZip);
        return;        
    }

    //Copy temp file to real location
    if(!CopyFile(config->GetTempFile(),downFile->parentZip))
    {
        failed.Push(zip);
        return;
    }

    csString doneStr("Got Zip  ");
    doneStr += downFile->parentZip;

    psupdaterengine->OutToScreen(doneStr.GetData());    
}



void Updater::UpdateRemoveFile( Change& change )
{
    csString path;

    // Figure out the path of the file. Either inside a zip or on /this/    
    if ( change.file->inZip )
    {
        path = "/zip/" + change.file->file;
        
        if ( !vfs->Mount("/zip", csString("$^")+change.file->parentZip.GetData()) )
        {
            printf("Could not mount VFS path\n");
            return;
        }
    }
    else
    {
        path = "/this/" + change.file->file;
    }
    
    RemoveFile( path );
    
    if ( change.file->inZip )
    {
        if ( !vfs->Unmount("/zip", csString("$^")+change.file->parentZip.GetData()) )        
        {
            printf("Could not unmount VFS path\n");
            return;
        }
    }
    
    // Report what was done.
    csString output;
    if ( change.type == Change::MOVEFILEAWAY )
        output = "Moved    ";
    else
        output = "Removed  ";
        
    output.AppendFmt( "%s", change.file->file.GetData() );
    
    psupdaterengine->OutToScreen( output );
    if (!psupdaterengine->IsSilent())
    {
        pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater");
        psupdaterengine->LockStatusMutex();
        updWnd->UpdateFileSize(output.GetData(), change.file->size);
        psupdaterengine->UnlockStatusMutex();
    }    
}

bool Updater::CheckRegistryVersion()
{
    psupdaterengine->OutToScreen("Checking registry version...");

    // Get last version data downloaded
    unsigned int oldVersion = GetVersionFromFile("/this/version.dat");

    MakeDirectory("updatertemp");
    if ( !downloader->DownloadFile("/updatertemp/version.dat", config->GetCurrentVersionURL(), false, true) )
        return false;

    // Get version data just downloaded
    unsigned int newVersion = GetVersionFromFile("/this/updatertemp/version.dat");

    //psupdaterengine->OutToScreen("%u -> %u",oldVersion,newVersion);

    if (oldVersion > newVersion)
      psupdaterengine->OutToScreen("Your version seems newer than server, if you want a clean update, delete version.dat and restart updater.");

    return oldVersion < newVersion;
}

unsigned int Updater::GetVersionFromFile(const char* file)
{
    csRef<iDataBuffer> data = psupdaterengine->GetVFS()->ReadFile(file);
    if (!data)
        return 0;

    if ( !ValidateVersionInfo(data->GetData()) )
    {
        printf("Bad version info in file: %s\n", file);
        psupdaterengine->GetVFS()->DeleteFile(file);
        return 0;
    }

    csString tmp(data->GetData());
    tmp.DeleteAt(0,33);
    return atoi( tmp.GetDataSafe() );
}

bool Updater::ValidateVersionInfo(const char* info)
{
    WordArray data(info);
    return data.GetCount() == 2 &&    // Exactly 2 segments
           data[0].Length() == 32 &&  // First is the 32 character version stamp
           data.GetInt(1) > 0;        // Second is the version number
}

csString Updater::CreateVersionInfo(Registry* reg)
{
    csString versionStamp( reg->GetReposVersion() );
    csString timeStamp;
    timeStamp.Format("%u", (unsigned int)time(0) );
    return versionStamp + " " + timeStamp;
}

void Updater::MoveFile(const char* from, const char* to)
{
    csRef<iDataBuffer> data = psupdaterengine->GetVFS()->ReadFile(from);
    if ( data && psupdaterengine->GetVFS()->WriteFile(to, data->GetData(), data->GetSize()) )
    {
        psupdaterengine->GetVFS()->DeleteFile(from);
    }
}


} // end of namespace updater

